diff --git a/.clang-tidy b/.clang-tidy index 56b7e40a28916..82681fda39923 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -15,6 +15,7 @@ Checks: ' bugprone-suspicious-string-compare, bugprone-swapped-arguments, bugprone-tautological-type-limits, + bugprone-unsafe-functions, bugprone-unused-return-value, misc-header-include-cycle, misc-include-cleaner, @@ -50,6 +51,23 @@ CheckOptions: varlink-io\.systemd\..*; varlink-idl-common\.h; unistd\.h +' + bugprone-unsafe-functions.ReportDefaultFunctions: false + bugprone-unsafe-functions.CustomFunctions: ' + ^fgets$,read_line(),is potentially dangerous; + ^strtok$,extract_first_word(),is potentially dangerous; + ^strsep$,extract_first_word(),is potentially dangerous; + ^dup$,fcntl() with F_DUPFD_CLOEXEC,is potentially dangerous; + ^htonl$,htobe32(),is confusing; + ^htons$,htobe16(),is confusing; + ^ntohl$,be32toh(),is confusing; + ^ntohs$,be16toh(),is confusing; + ^strerror$,STRERROR() or printf %m,is not thread-safe; + ^accept$,accept4(),is not O_CLOEXEC-safe; + ^dirname$,path_extract_directory(),is icky; + ^basename$,path_extract_filename(),is icky; + ^setmntent$,libmount_parse_fstab(),libmount parser should be used instead; + ^getmntent$,mnt_table_next_fs(),libmount parser should be used instead ' misc-header-include-cycle.IgnoredFilesList: 'glib-2.0' WarningsAsErrors: '*' diff --git a/.clangd b/.clangd index 24886efe9e86a..7ce59c6002f41 100644 --- a/.clangd +++ b/.clangd @@ -1,4 +1,16 @@ # SPDX-License-Identifier: LGPL-2.1-or-later +# Strip GCC-only flags from compile_commands.json before clang sees them. +# clangd reports these as driver-level "unknown argument" errors which can't +# be silenced via Diagnostics.Suppress, so they must be removed instead. +# -fwide-exec-charset: used by EFI boot code to make L"..." literals UTF-16 +# -maccumulate-outgoing-args: GCC x86 codegen flag, no clang equivalent +CompileFlags: + Remove: [-fwide-exec-charset=*, -maccumulate-outgoing-args] + Diagnostics: UnusedIncludes: Strict + # __no_reorder__ is a GCC-only attribute (see _no_reorder_ in + # src/fundamental/macro-fundamental.h). Meson detects it during configure + # with GCC and enables it unconditionally, so clangd flags every use. + Suppress: [unknown-attributes] diff --git a/.gitattributes b/.gitattributes index dae59aa844a2e..6c6c4a8beaab1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ *.[ch] whitespace=tab-in-indent,trailing-space +src/include/uapi/**/*.[ch] whitespace=trailing-space *.gpg binary generated *.bmp binary *.base64 generated diff --git a/.github/codeql-config.yml b/.github/codeql-config.yml deleted file mode 100644 index 7c01d32caa31c..0000000000000 --- a/.github/codeql-config.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- -# vi: ts=2 sw=2 et: -# SPDX-License-Identifier: LGPL-2.1-or-later -name: "CodeQL config" - -disable-default-queries: false - -queries: - - name: Enable possibly useful queries which are disabled by default - uses: ./.github/codeql-custom.qls - - name: systemd-specific CodeQL queries - uses: ./.github/codeql-queries/ diff --git a/.github/codeql-custom.qls b/.github/codeql-custom.qls deleted file mode 100644 index d35fbe3114b93..0000000000000 --- a/.github/codeql-custom.qls +++ /dev/null @@ -1,44 +0,0 @@ ---- -# vi: ts=2 sw=2 et syntax=yaml: -# SPDX-License-Identifier: LGPL-2.1-or-later -# -# Note: it is not recommended to directly reference the respective queries from -# the github/codeql repository, so we have to "dance" around it using -# a custom QL suite -# See: -# - https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#running-additional-queries -# - https://github.com/github/codeql-action/issues/430#issuecomment-806092120 -# - https://codeql.github.com/docs/codeql-cli/creating-codeql-query-suites/ - -# Note: the codeql/-queries pack name can be found in the CodeQL repo[0] -# in /ql/src/qlpack.yml. The respective codeql-suites are then -# under /ql/src/codeql-suites/. -# -# [0] https://github.com/github/codeql -- import: codeql-suites/cpp-lgtm.qls - from: codeql/cpp-queries -- import: codeql-suites/python-lgtm.qls - from: codeql/python-queries -- include: - id: - - cpp/bad-strncpy-size - - cpp/declaration-hides-variable - - cpp/include-non-header - - cpp/inconsistent-null-check - - cpp/mistyped-function-arguments - - cpp/nested-loops-with-same-variable - - cpp/sizeof-side-effect - - cpp/suspicious-pointer-scaling - - cpp/suspicious-pointer-scaling-void - - cpp/suspicious-sizeof - - cpp/unsafe-strcat - - cpp/unsafe-strncat - - cpp/unsigned-difference-expression-compared-zero - - cpp/unused-local-variable - tags: - - "security" - - "correctness" - severity: "error" -- exclude: - id: - - cpp/fixme-comment diff --git a/.github/codeql-queries/PotentiallyDangerousFunction.ql b/.github/codeql-queries/PotentiallyDangerousFunction.ql deleted file mode 100644 index abd3f87a3425b..0000000000000 --- a/.github/codeql-queries/PotentiallyDangerousFunction.ql +++ /dev/null @@ -1,68 +0,0 @@ -/** - * vi: sw=2 ts=2 et syntax=ql: - * - * Borrowed from - * https://github.com/Semmle/ql/blob/master/cpp/ql/src/Security/CWE/CWE-676/PotentiallyDangerousFunction.ql - * - * @name Use of potentially dangerous function - * @description Certain standard library functions are dangerous to call. - * @id cpp/potentially-dangerous-function - * @kind problem - * @problem.severity error - * @precision high - * @tags reliability - * security - */ -import cpp - -predicate potentiallyDangerousFunction(Function f, string message) { - ( - f.getQualifiedName() = "fgets" and - message = "Call to fgets() is potentially dangerous. Use read_line() instead." - ) or ( - f.getQualifiedName() = "strtok" and - message = "Call to strtok() is potentially dangerous. Use extract_first_word() instead." - ) or ( - f.getQualifiedName() = "strsep" and - message = "Call to strsep() is potentially dangerous. Use extract_first_word() instead." - ) or ( - f.getQualifiedName() = "dup" and - message = "Call to dup() is potentially dangerous. Use fcntl(fd, FD_DUPFD_CLOEXEC, 3) instead." - ) or ( - f.getQualifiedName() = "htonl" and - message = "Call to htonl() is confusing. Use htobe32() instead." - ) or ( - f.getQualifiedName() = "htons" and - message = "Call to htons() is confusing. Use htobe16() instead." - ) or ( - f.getQualifiedName() = "ntohl" and - message = "Call to ntohl() is confusing. Use be32toh() instead." - ) or ( - f.getQualifiedName() = "ntohs" and - message = "Call to ntohs() is confusing. Use be16toh() instead." - ) or ( - f.getQualifiedName() = "strerror" and - message = "Call to strerror() is not thread-safe. Use printf()'s %m format string or STRERROR() instead." - ) or ( - f.getQualifiedName() = "accept" and - message = "Call to accept() is not O_CLOEXEC-safe. Use accept4() instead." - ) or ( - f.getQualifiedName() = "dirname" and - message = "Call dirname() is icky. Use path_extract_directory() instead." - ) or ( - f.getQualifiedName() = "basename" and - message = "Call basename() is icky. Use path_extract_filename() instead." - ) or ( - f.getQualifiedName() = "setmntent" and - message = "Libmount parser is used instead, specifically libmount_parse_fstab()." - ) or ( - f.getQualifiedName() = "getmntent" and - message = "Libmount parser is used instead, specifically mnt_table_next_fs()." - ) -} - -from FunctionCall call, Function target, string message -where - call.getTarget() = target and - potentiallyDangerousFunction(target, message) -select call, message diff --git a/.github/codeql-queries/UninitializedVariableWithCleanup.ql b/.github/codeql-queries/UninitializedVariableWithCleanup.ql deleted file mode 100644 index e514111f282c0..0000000000000 --- a/.github/codeql-queries/UninitializedVariableWithCleanup.ql +++ /dev/null @@ -1,110 +0,0 @@ -/** - * vi: sw=2 ts=2 et syntax=ql: - * - * Based on cpp/uninitialized-local. - * - * @name Potentially uninitialized local variable using the cleanup attribute - * @description Running the cleanup handler on a possibly uninitialized variable - * is generally a bad idea. - * @id cpp/uninitialized-local-with-cleanup - * @kind problem - * @problem.severity error - * @precision high - * @tags security - */ - -import cpp -import semmle.code.cpp.controlflow.StackVariableReachability - -/** Auxiliary predicate: List cleanup functions we want to explicitly ignore - * since they don't do anything illegal even when the variable is uninitialized - */ -predicate cleanupFunctionDenyList(string fun) { - fun = "erase_char" -} - -/** - * A declaration of a local variable using __attribute__((__cleanup__(x))) - * that leaves the variable uninitialized. - */ -DeclStmt declWithNoInit(LocalVariable v) { - result.getADeclaration() = v and - not v.hasInitializer() and - /* The variable has __attribute__((__cleanup__(...))) set */ - v.getAnAttribute().hasName("cleanup") and - /* Check if the cleanup function is not on a deny list */ - not cleanupFunctionDenyList(v.getAnAttribute().getAnArgument().getValueText()) -} - -class UninitialisedLocalReachability extends StackVariableReachability { - UninitialisedLocalReachability() { this = "UninitialisedLocal" } - - override predicate isSource(ControlFlowNode node, StackVariable v) { node = declWithNoInit(v) } - - /* Note: _don't_ use the `useOfVarActual()` predicate here (and a couple of lines - * below), as it assumes that the callee always modifies the variable if - * it's passed to the function. - * - * i.e.: - * _cleanup_free char *x; - * fun(&x); - * puts(x); - * - * `useOfVarActual()` won't treat this as an uninitialized read even if the callee - * doesn't modify the argument, however, `useOfVar()` will - */ - override predicate isSink(ControlFlowNode node, StackVariable v) { useOfVar(v, node) } - - override predicate isBarrier(ControlFlowNode node, StackVariable v) { - /* only report the _first_ possibly uninitialized use */ - useOfVar(v, node) or - ( - /* If there's a return statement somewhere between the variable declaration - * and a possible definition, don't accept is as a valid initialization. - * - * E.g.: - * _cleanup_free_ char *x; - * ... - * if (...) - * return; - * ... - * x = malloc(...); - * - * is not a valid initialization, since we might return from the function - * _before_ the actual initialization (emphasis on _might_, since we - * don't know if the return statement might ever evaluate to true). - */ - definitionBarrier(v, node) and - not exists(ReturnStmt rs | - /* The attribute check is "just" a complexity optimization */ - v.getFunction() = rs.getEnclosingFunction() and v.getAnAttribute().hasName("cleanup") | - rs.getLocation().isBefore(node.getLocation()) - ) - ) - } -} - -pragma[noinline] -predicate containsInlineAssembly(Function f) { exists(AsmStmt s | s.getEnclosingFunction() = f) } - -/** - * Auxiliary predicate: List common exceptions or false positives - * for this check to exclude them. - */ -VariableAccess commonException() { - /* If the uninitialized use we've found is in a macro expansion, it's - * typically something like va_start(), and we don't want to complain. */ - result.getParent().isInMacroExpansion() - or - result.getParent() instanceof BuiltInOperation - or - /* Finally, exclude functions that contain assembly blocks. It's - * anyone's guess what happens in those. */ - containsInlineAssembly(result.getEnclosingFunction()) -} - -from UninitialisedLocalReachability r, LocalVariable v, VariableAccess va -where - r.reaches(_, v, va) and - not va = commonException() -select va, "The variable $@ may not be initialized here, but has a cleanup handler.", v, v.getName() diff --git a/.github/codeql-queries/qlpack.yml b/.github/codeql-queries/qlpack.yml deleted file mode 100644 index a1a2dec6d6efe..0000000000000 --- a/.github/codeql-queries/qlpack.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -# vi: ts=2 sw=2 et syntax=yaml: -# SPDX-License-Identifier: LGPL-2.1-or-later - -library: false -name: systemd/cpp-queries -version: 0.0.1 -dependencies: - codeql/cpp-all: "*" - codeql/suite-helpers: "*" -extractor: cpp diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 193935f3e6b10..0000000000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,53 +0,0 @@ -# systemd AI Coding Agent Instructions - -## Project Overview - -systemd is a system and service manager for Linux, written in C (GNU17 with extensions). The project is built with Meson and consists of ~140 components including PID 1, journald, udevd, networkd, and many other system daemons. - -## Key Files & Directories - -Always include the following files in the context: - -- [code organization details](../docs/ARCHITECTURE.md) -- [development workflow deep dive](../docs/HACKING.md) -- [full style guide](../docs/CODING_STYLE.md) - -Include any other files from the [documentation](../docs) in the context as needed based on whether you think it might be helpful to solve your current task or help to review the current PR. - -## Build + Test instructions - -**CRITICAL: Read and follow these instructions exactly.** - -- **NEVER** compile individual files or targets. **ALWAYS** run `mkosi -f box meson compile -C build` to build the entire project. Meson handles incremental compilation automatically. -- **NEVER** run `meson compile` followed by `meson test` as separate steps. **ALWAYS** run `mkosi -f box meson test -C build -v ` directly. Meson will automatically rebuild any required targets before running tests. -- **NEVER** invent your own build commands or try to optimize the build process. -- **NEVER** use `head`, `tail`, or pipe (`|`) the output of build or test commands. Always let the full output display. This is critical for diagnosing build and test failures. -- When asked to build and test code changes: - - **CORRECT**: Run `mkosi -f box -- meson test -C build -v ` (this builds and runs tests in one command) - - **WRONG**: Run `mkosi -f box -- meson compile -C build` followed by `mkosi -f box -- meson test -C build -v ` - - **WRONG**: Run `mkosi -f box -- meson compile -C build src/core/systemd` or similar individual target compilation - - **WRONG**: Run `mkosi -f box -- meson test -C build -v 2>&1 | tail -100` or similar piped commands - -## Pull Request review instructions - -- Focus on making sure the coding style is followed as documented in `docs/CODING_STYLE.md` -- Only leave comments for logic issues if you are very confident in your deduction -- Frame comments as questions -- Always consider you may be wrong -- Do not argue with contributors, assume they are right unless you are very confident in your deduction -- Be extremely thorough. Every single separate coding style violation should be reported - -## Testing Expectations - -- Unit tests for self contained functions with few dependencies -- Integration tests for system-level functionality -- CI must pass (build + unit + integration tests) -- Code coverage tracked via Coveralls - -## Integration with Development Tools - -- **clangd**: Use `mkosi.clangd` script to start a C/C++ LSP server for navigating C source and header files. Run `mkosi -f box -- meson setup build && mkosi -f box -- meson compile -C build gensources` first to prepare the environment. - -## AI Contribution Disclosure - -Per project policy: If you use AI code generation tools, you **must disclose** this in commit messages and PR descriptions. All AI-generated output requires thorough human review before submission. diff --git a/.github/labeler.yml b/.github/labeler.yml index 65ac975025214..5e90662c284ea 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,6 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # vi: sw=2 ts=2 et: +changed-files-labels-limit: 5 analyze: - changed-files: - any-glob-to-any-file: 'src/analyze/*' diff --git a/.github/workflows/build-test.sh b/.github/workflows/build-test.sh index 3c6c6c50feebe..506479a55845d 100755 --- a/.github/workflows/build-test.sh +++ b/.github/workflows/build-test.sh @@ -12,7 +12,7 @@ success() { echo >&2 -e "\033[32;1m$1\033[0m"; } ARGS=( "--optimization=0 -Dopenssl=disabled -Dtpm=true -Dtpm2=enabled" "--optimization=s -Dutmp=false -Dc_args='-DOPENSSL_NO_UI_CONSOLE=1'" - "--optimization=2 -Dc_args=-Wmaybe-uninitialized -Ddns-over-tls=openssl" + "--optimization=2 -Ddns-over-tls=openssl" "--optimization=3 -Db_lto=true -Ddns-over-tls=false" "--optimization=3 -Db_lto=false -Dtpm2=disabled -Dlibfido2=disabled -Dp11kit=disabled -Defi=false -Dbootloader=disabled" "--optimization=3 -Dfexecve=true -Dstandalone-binaries=true -Dstatic-libsystemd=true -Dstatic-libudev=true" @@ -108,6 +108,11 @@ elif [[ "$COMPILER" == gcc ]]; then CFLAGS="" CXXFLAGS="" + # -Wmaybe-uninitialized works badly in old gcc versions + if [[ "$COMPILER_VERSION" -lt 14 ]]; then + CFLAGS="$CFLAGS -Wno-maybe-uninitialized" + fi + if ! apt-get -y install --dry-run "gcc-$COMPILER_VERSION" >/dev/null; then # Latest gcc stack deb packages provided by # https://launchpad.net/~ubuntu-toolchain-r/+archive/ubuntu/test @@ -162,11 +167,6 @@ ninja --version for args in "${ARGS[@]}"; do SECONDS=0 - if [[ "$COMPILER" == clang && "$args" =~ Wmaybe-uninitialized ]]; then - # -Wmaybe-uninitialized is not implemented in clang - continue - fi - info "Checking build with $args" # shellcheck disable=SC2086 if ! AR="$AR" \ diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index bb301793ca1b7..d352b2c7b4028 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -60,7 +60,7 @@ jobs: sanitizer: ${{ matrix.sanitizer }} output-sarif: true - name: Upload Crash - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f if: failure() && steps.build.outcome == 'success' with: name: ${{ matrix.sanitizer }}-${{ matrix.architecture }}-artifacts diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml new file mode 100644 index 0000000000000..e319214bf087f --- /dev/null +++ b/.github/workflows/claude-review.yml @@ -0,0 +1,616 @@ +# Integrates Claude Code as an AI assistant for reviewing pull requests. +# Mention @claude in any PR comment to request a review. Claude authenticates +# via AWS Bedrock using OIDC — no long-lived API keys required. +# +# Architecture: The workflow is split into three jobs for least-privilege: +# 1. "setup" — fetches PR context, posts/updates tracking comment (write permissions) +# 2. "review" — runs Claude with read-only permissions, produces structured JSON +# 3. "post" — reads the JSON and posts comments to the PR (write permissions) + +name: Claude Review + +on: + pull_request_target: + types: [opened, synchronize, reopened, labeled] + # Strangely enough you have to use issue_comment to react to regular comments on PRs. + # See https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_comment-use-issue_comment. + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + pull_request_review: + types: [submitted] + +concurrency: + group: claude-review-${{ github.event.pull_request.number || github.event.issue.number }} + +jobs: + setup: + runs-on: ubuntu-latest + env: + PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} + + if: | + github.repository_owner == 'systemd' && + ((github.event_name == 'pull_request_target' && + (github.event.action == 'labeled' && github.event.label.name == 'claude-review' && github.event.sender.login != 'github-actions[bot]' || + github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'claude-review') || + github.event.action == 'opened' && + github.event.pull_request.base.ref == 'main' && + contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.pull_request.author_association) && + github.event.pull_request.user.login != 'YHNdnzj')) || + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + contains(github.event.comment.body, '@claude review') && + contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || + (github.event_name == 'pull_request_review_comment' && + contains(github.event.comment.body, '@claude review') && + contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || + (github.event_name == 'pull_request_review' && + contains(github.event.review.body, '@claude review') && + contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.review.author_association))) + + permissions: + contents: read + pull-requests: write + + outputs: + pr_number: ${{ steps.context.outputs.pr_number }} + comment_id: ${{ steps.context.outputs.comment_id }} + + steps: + - name: Auto-add claude-review label for trusted contributors + if: github.event_name == 'pull_request_target' && github.event.action == 'opened' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh pr edit --repo "${{ github.repository }}" "$PR_NUMBER" --add-label claude-review + + - name: Fetch PR context and create tracking comment + id: context + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const prNumber = parseInt(process.env.PR_NUMBER, 10); + const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; + const MARKER = ""; + + /* Fetch all PR data in parallel. */ + const [pr, reviews, issueComments, reviewComments] = await Promise.all([ + github.rest.pulls.get({ owner, repo, pull_number: prNumber }), + github.paginate(github.rest.pulls.listReviews, { owner, repo, pull_number: prNumber, per_page: 100 }), + github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: prNumber, per_page: 100 }), + github.paginate(github.rest.pulls.listReviewComments, { owner, repo, pull_number: prNumber, per_page: 100 }), + ]); + + /* Find or create tracking comment. */ + const existing = issueComments.find((c) => c.body && c.body.includes(MARKER)); + let commentId; + let trackingCommentBody = null; + + if (existing) { + console.log(`Updating existing tracking comment ${existing.id}.`); + /* Prepend a re-reviewing banner but keep the previous review visible. */ + const prevBody = existing.body.replace(/\n\n\[Workflow run\]\([^)]*\)$/, ""); + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body: `> **Claude is re-reviewing this PR…** ([workflow run](${runUrl}))\n\n${prevBody}`, + }); + commentId = existing.id; + trackingCommentBody = prevBody; + } else { + console.log("Creating new tracking comment."); + const {data: created} = await github.rest.issues.createComment({ + owner, + repo, + issue_number: prNumber, + body: `Claude is reviewing this PR… ([workflow run](${runUrl}))\n\n${MARKER}`, + }); + commentId = created.id; + } + + /* Build context JSON for Claude. */ + const prContext = { + pr: pr.data, + reviews, + issue_comments: issueComments, + tracking_comment: trackingCommentBody, + review_comments: reviewComments, + }; + + core.setOutput("pr_number", prNumber); + core.setOutput("comment_id", commentId); + + const fs = require("fs"); + fs.writeFileSync("pr-context.json", JSON.stringify(prContext)); + + # archive: false makes upload-artifact use the file's basename + # (pr-context.json) as the artifact name, ignoring the name input. + - name: Upload PR context + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f + with: + path: pr-context.json + archive: false + retention-days: 7 + + review: + runs-on: ubuntu-latest + needs: setup + timeout-minutes: 60 + + permissions: + contents: read + id-token: write # Authenticate with AWS via OIDC + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + # Need full history for git worktree add to work on all PR commits. + fetch-depth: 0 + persist-credentials: false + + - name: Download PR context + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: pr-context.json + + - name: Prettify PR context + run: | + jq . pr-context.json > pr-context-pretty.json + mv pr-context-pretty.json pr-context.json + + - name: Prepare PR worktrees + env: + PR_NUMBER: ${{ needs.setup.outputs.pr_number }} + run: | + git fetch origin "pull/${PR_NUMBER}/head" + for sha in $(git log --reverse --format=%H HEAD..FETCH_HEAD); do + git worktree add "worktrees/$sha" "$sha" + git -C "worktrees/$sha" diff HEAD~..HEAD > "worktrees/$sha/commit.patch" + git -C "worktrees/$sha" log -1 --format='%B' HEAD > "worktrees/$sha/commit-message.txt" + done + + - name: Install sandbox dependencies + run: | + sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + sudo apt-get update && sudo apt-get install -y bubblewrap socat + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 + with: + role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }} + role-session-name: GitHubActions-Claude-${{ github.run_id }} + aws-region: us-east-1 + + - name: Install Claude Code + run: curl -fsSL https://claude.ai/install.sh | bash + + - name: Run Claude Code + env: + CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1" + CLAUDE_CODE_USE_BEDROCK: "1" + run: | + mkdir -p ~/.claude + + cat > ~/.claude/settings.json << 'SETTINGS' + { + "permissions": { + "allow": [ + "Bash", + "Read", + "Edit(/${{ github.workspace }}/**)", + "Write(/${{ github.workspace }}/**)", + "Grep", + "Glob", + "Agent", + "Task", + "TaskOutput", + "ToolSearch" + ] + }, + "sandbox": { + "enabled": true, + "autoAllowBashIfSandboxed": true, + "allowUnsandboxedCommands": false, + "filesystem": { + "allowWrite": ["/tmp", "/var/tmp", "${{ github.workspace }}"] + } + } + } + SETTINGS + + cat > review-schema.json << 'SCHEMA' + { + "type": "object", + "required": ["summary", "comments"], + "properties": { + "summary": { "type": "string" }, + "comments": { + "type": "array", + "items": { + "type": "object", + "required": ["path", "line", "severity", "body", "commit"], + "properties": { + "path": { "type": "string" }, + "line": { "type": "integer" }, + "side": { "enum": ["LEFT", "RIGHT"] }, + "start_line": { "type": "integer" }, + "start_side": { "enum": ["LEFT", "RIGHT"] }, + "severity": { "enum": ["must-fix", "suggestion", "nit"] }, + "body": { "type": "string" }, + "commit": { "type": "string" } + } + } + }, + "resolve": { "type": "array", "items": { "type": "integer" } } + } + } + SCHEMA + + cat > /tmp/review-prompt.txt << 'PROMPT' + You are a code reviewer for the ${{ github.repository }} project. + Review this pull request. All required context has been + pre-fetched into local files. + + ## Phase 1: Review commits + + First, list the directories in `worktrees/` and read `review-schema.json`. + Then, spawn exactly one review subagent per worktree directory, all in a + single message so they run concurrently. Do NOT batch or group multiple + commits into a single agent. Do NOT read any other files before spawning — + the subagents will do that themselves. + + Each worktree at `worktrees//` contains the full source tree checked + out at that commit, plus `commit.patch` (the diff) and `commit-message.txt` + (the commit message). + + Each reviewer reviews design, code quality, style, potential bugs, and + security implications. + + Each subagent must be spawned with `model: "opus"`. + + Each subagent prompt must include: + - Instructions to read `pr-context.json` in the repository root for additional + context. + - The contents of `review-schema.json` (paste it into each prompt so the + agent doesn't have to read it separately). + - The worktree path. + - Instructions to read `commit-message.txt` and `commit.patch` in the + worktree for the commit message and diff. + - Instructions to verify every `line` and `start_line` value + against the hunk ranges in `commit.patch` before returning. + - Instructions to return ONLY a raw JSON array of findings. No markdown, + no explanation, no code fences — just the JSON array. If there are no + findings, return `[]`. + - Instructions that `severity` is a separate structured field — do NOT + repeat it inside `body` (no "must-fix:", "**suggestion**:", etc. + prefix). The posting step adds the severity label itself, so + including it in `body` produces duplicates. + + ## Phase 2: Collect, deduplicate, and summarize + + After all reviews are done, read `pr-context.json` from the repository root. + It contains PR metadata from the GitHub API. Rules for its `review_comments` + field: + - Only look at your own comments (user.login == "github-actions[bot]" and + body starts with "Claude: "). Ignore all other comments. + - Items checked off in the `tracking_comment` (`- [x]`) are resolved. + - You will need the `id` fields of your own unresolved comments to + populate the `resolve` array. + - If `tracking_comment` is non-null, use it as the basis for your summary. + + Trust the subagent findings — do NOT re-verify them by running your own + bash, grep, sed, or awk commands against the source code. Phase 2 should + only read `pr-context.json` and then produce the structured output. + + Then: + 1. Collect all issues. Merge duplicates across agents (same file, same + problem, lines within 3 of each other). + 2. Drop issues that already have a review comment on the same file about + the same problem, or where the PR author replied disagreeing. + 3. Populate the `resolve` array with the `id` of your own review comment + threads (user.login == "github-actions[bot]", body starts with + "Claude: ") that should be resolved — either because the issue was + fixed or because the author dismissed it. Use the first comment `id` + in each thread. Do not resolve threads from human reviewers. + 4. Write a `summary` field in markdown for a top-level tracking comment. + + **If no existing tracking comment was found (first run):** + Use this format: + + ``` + ## Claude review of PR # () + + + + ### Must fix + - [ ] **short title** — `path:line` — brief explanation + + ### Suggestions + - [ ] **short title** — `path:line` — brief explanation + + ### Nits + - [ ] **short title** — `path:line` — brief explanation + ``` + + Omit empty sections. Each checkbox item must correspond to an entry in `comments`. + If there are no issues at all, write a short message saying the PR looks good. + + **If an existing tracking comment was found (subsequent run):** + Use the existing comment as the starting point. Preserve the order and wording + of all existing items. Then apply these updates: + - Update the HEAD SHA in the header line. + - For each existing item, re-check whether the issue is still present in the + current diff. If it has been fixed, mark it checked: `- [x]`. + - If the PR author replied dismissing an item, mark it: + `- [x] ~~short title~~ (dismissed)`. + - Preserve checkbox state that was already set by previous runs or by hand. + - Append any new issues found in this run that aren't already listed, + in the appropriate severity section, after the existing items. + - Do not reorder, reword, or remove existing items. + + ## Error tracking + + If any errors prevented you from doing your job fully (tools that were + not available, git commands that failed, etc.), append a `### Errors` + section to the summary listing each failed action and the error message. + + ## Output formatting + + Do NOT escape characters in `body` or `summary`. Write plain markdown — no + backslash escaping of `!` or other characters. In particular, HTML comments + like `` must be written verbatim, never as `<\!-- ... -->`. + + ## Review result + + Produce your review result as structured output. The fields are: + - `summary`: The markdown summary for the tracking comment. + - `comments`: Array of review comments (same schema as the reviewer output above). + - `resolve`: REST API IDs of review comment threads to resolve. + PROMPT + + claude \ + --model us.anthropic.claude-opus-4-6-v1 \ + --effort max \ + --max-turns 200 \ + --setting-sources user \ + --output-format stream-json \ + --json-schema "$(cat review-schema.json)" \ + --verbose \ + -p "$(cat /tmp/review-prompt.txt)" \ + | tee claude.json + + jq '.structured_output | select(. != null)' claude.json > review-result.json + + - name: Upload review result + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f + with: + path: review-result.json + if-no-files-found: ignore + archive: false + retention-days: 7 + + post: + runs-on: ubuntu-latest + needs: [setup, review] + if: always() && needs.setup.result == 'success' + + permissions: + pull-requests: write + + steps: + - name: Download review result + if: needs.review.result == 'success' + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: review-result.json + + - name: Post review comments + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd + env: + REVIEW_RESULT: ${{ needs.review.result }} + PR_NUMBER: ${{ needs.setup.outputs.pr_number }} + COMMENT_ID: ${{ needs.setup.outputs.comment_id }} + with: + script: | + const fs = require("fs"); + const owner = context.repo.owner; + const repo = context.repo.repo; + const prNumber = parseInt(process.env.PR_NUMBER, 10); + const commentId = parseInt(process.env.COMMENT_ID, 10); + const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; + const MARKER = ""; + + /* If the review job failed or was cancelled, update the tracking + * comment to reflect that and bail out. */ + if (process.env.REVIEW_RESULT !== "success") { + const verb = process.env.REVIEW_RESULT === "cancelled" ? "was cancelled" : "failed"; + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: commentId, + body: `Claude review ${verb} — see [workflow run](${runUrl}) for details.\n\n${MARKER}`, + }); + core.setFailed("Review job did not succeed."); + return; + } + + /* Parse Claude's review result from the downloaded artifact. */ + let raw = ""; + try { + raw = fs.readFileSync("review-result.json", "utf8"); + } catch (e) { + console.log(`Failed to read review-result.json: ${e.message}`); + } + console.log("Review result from Claude:"); + console.log(raw || "(empty)"); + + let comments = []; + let resolveIds = []; + let summary = ""; + if (raw) { + try { + const review = JSON.parse(raw); + if (Array.isArray(review.comments)) + comments = review.comments; + if (Array.isArray(review.resolve)) + resolveIds = review.resolve; + if (typeof review.summary === "string") + summary = review.summary; + } catch (e) { + core.warning(`Failed to parse structured output: ${e.message}`); + } + } + + console.log(`Claude produced ${comments.length} review comment(s).`); + + /* Post each inline comment individually. Deduplication against existing + * comments is handled by Claude in the prompt, so we just post whatever + * it returns. Using individual comments (rather than a review) means + * re-runs only add new comments instead of creating a whole new review. */ + const inlineComments = comments.filter((c) => c.path && c.line); + const skipped = comments.length - inlineComments.length; + if (skipped > 0) + console.log(`Skipping ${skipped} comment(s) missing path or line number.`); + + let posted = 0; + for (const c of inlineComments) { + console.log(` Posting comment on ${c.path}:${c.line}`); + try { + await github.rest.pulls.createReviewComment({ + owner, + repo, + pull_number: prNumber, + commit_id: c.commit, + path: c.path, + line: c.line, + ...(c.side != null && { side: c.side }), + ...(c.start_line != null && { start_line: c.start_line }), + ...(c.start_side != null && { start_side: c.start_side }), + body: `Claude: **${c.severity}**: ${c.body}`, + }); + posted++; + } catch (e) { + /* GitHub rejects comments on lines outside the diff context. Log + * and continue — the tracking comment still contains all findings. */ + console.log(` Warning: failed to post comment on ${c.path}:${c.line}: ${e.message}`); + } + } + + if (posted > 0) + console.log(`Posted ${posted}/${inlineComments.length} inline comment(s).`); + else if (inlineComments.length > 0) + console.log(`Could not post any of ${inlineComments.length} inline comment(s) — see warnings above.`); + else + console.log("No inline comments to post."); + + /* Resolve review threads that Claude identified as addressed or dismissed. */ + if (resolveIds.length > 0) { + const resolveSet = new Set(resolveIds); + + /* Fetch all review threads and map first-comment database IDs to thread IDs. */ + let threads = []; + try { + let threadCursor = null; + do { + const threadQuery = ` + query($owner: String!, $repo: String!, $number: Int!, $cursor: String) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $number) { + reviewThreads(first: 100, after: $cursor) { + pageInfo { hasNextPage endCursor } + nodes { + id + isResolved + comments(first: 1) { + nodes { + databaseId + } + } + } + } + } + } + } + `; + + const threadResult = await github.graphql(threadQuery, { owner, repo, number: prNumber, cursor: threadCursor }); + const page = threadResult.repository.pullRequest.reviewThreads; + threads.push(...page.nodes); + threadCursor = page.pageInfo.hasNextPage ? page.pageInfo.endCursor : null; + } while (threadCursor); + } catch (e) { + console.log(`Warning: failed to fetch review threads, skipping resolution: ${e.message}`); + threads = []; + } + + let resolved = 0; + let alreadyResolved = 0; + const matchedIds = new Set(); + for (const thread of threads) { + const firstCommentId = thread.comments.nodes[0]?.databaseId; + if (!firstCommentId || !resolveSet.has(firstCommentId)) continue; + + matchedIds.add(firstCommentId); + + if (thread.isResolved) { + alreadyResolved++; + continue; + } + + try { + await github.graphql(` + mutation($threadId: ID!) { + resolveReviewThread(input: { threadId: $threadId }) { + thread { id } + } + } + `, { threadId: thread.id }); + resolved++; + console.log(` Resolved thread for comment ${firstCommentId}`); + } catch (e) { + console.log(` Warning: failed to resolve thread for comment ${firstCommentId}: ${e.message}`); + } + } + + const requested = resolveSet.size; + const unmatched = [...resolveSet].filter(id => !matchedIds.has(id)); + if (resolved > 0) + console.log(`Resolved ${resolved}/${requested} review thread(s)${alreadyResolved > 0 ? ` (${alreadyResolved} already resolved)` : ""}.`); + else if (alreadyResolved === requested) + console.log(`All ${requested} review thread(s) were already resolved.`); + else if (alreadyResolved > 0) + console.log(`${alreadyResolved}/${requested} review thread(s) were already resolved; could not resolve the rest — see warnings above.`); + else if (threads.length > 0) + console.log(`Could not resolve any of ${requested} review thread(s) — see warnings above.`); + if (unmatched.length > 0) + console.log(` ${unmatched.length} comment ID(s) not found in any thread: ${unmatched.join(", ")}`); + } else { + console.log("No review threads to resolve."); + } + + /* Update the tracking comment with Claude's summary. */ + if (!summary) + summary = "Claude review: no issues found :tada:\n\n" + MARKER; + else if (!summary.includes(MARKER)) + summary += "\n\n" + MARKER; + summary += `\n\n[Workflow run](${runUrl})`; + + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: commentId, + body: summary, + }); + + console.log("Tracking comment updated successfully."); + + if (inlineComments.length > 0 && posted === 0) + core.setFailed(`Could not post any of ${inlineComments.length} inline comment(s) — see warnings above.`); + else if (posted < inlineComments.length) + core.warning(`${inlineComments.length - posted}/${inlineComments.length} inline comment(s) could not be posted.`); diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index c7b687c1fcace..0000000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,68 +0,0 @@ ---- -# vi: ts=2 sw=2 et: -# SPDX-License-Identifier: LGPL-2.1-or-later -# -name: "CodeQL" - -on: - pull_request: - branches: - - main - - v[0-9]+-stable - paths: - - '**/meson.build' - - '.github/**/codeql*' - - 'src/**' - - 'test/**' - - 'tools/**' - push: - branches: - - main - - v[0-9]+-stable - -permissions: - contents: read - -jobs: - analyze: - name: Analyze - if: github.repository != 'systemd/systemd-security' - runs-on: ubuntu-24.04 - concurrency: - group: ${{ github.workflow }}-${{ matrix.language }}-${{ github.ref }} - cancel-in-progress: true - permissions: - actions: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: ['cpp', 'python'] - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - persist-credentials: false - - - name: Initialize CodeQL - uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e - with: - languages: ${{ matrix.language }} - config-file: ./.github/codeql-config.yml - - - run: | - sudo -E .github/workflows/unit-tests.sh SETUP - # TODO: drop after we switch to ubuntu 26.04 - bpftool_binary=$(find /usr/lib/linux-tools/ /usr/lib/linux-tools-* -name 'bpftool' -perm /u=x 2>/dev/null | sort -r | head -n1) - if [ -n "$bpftool_binary" ]; then - sudo rm -f /usr/{bin,sbin}/bpftool - sudo ln -s "$bpftool_binary" /usr/bin/ - fi - - - name: Autobuild - uses: github/codeql-action/autobuild@89a39a4e59826350b863aa6b6252a07ad50cf83e - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 7ebb7491506a7..c2b9493f6d8ba 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -130,7 +130,7 @@ jobs: --quiet - name: Archive failed test journals - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 if: failure() && (github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable') with: name: ci-coverage-${{ github.run_id }}-${{ github.run_attempt }}-arch-rolling-failed-test-journals diff --git a/.github/workflows/development-freeze.yml b/.github/workflows/development-freeze.yml index be75a2c421c58..25f6c1e92cfb9 100644 --- a/.github/workflows/development-freeze.yml +++ b/.github/workflows/development-freeze.yml @@ -25,7 +25,7 @@ jobs: steps: - id: artifact name: Download Pull Request Metadata artifact - uses: redhat-plumbers-in-action/download-artifact@103e5f882470b59e9d71c80ecb2d0a0b91a7c43b + uses: redhat-plumbers-in-action/download-artifact@03d5b806a9dca9928eb5628833fe81a0558f23bb with: name: Pull Request Metadata diff --git a/.github/workflows/gather-pr-metadata.yml b/.github/workflows/gather-pr-metadata.yml index f9cfd9154e61c..2ae9a098a6949 100644 --- a/.github/workflows/gather-pr-metadata.yml +++ b/.github/workflows/gather-pr-metadata.yml @@ -25,7 +25,7 @@ jobs: uses: redhat-plumbers-in-action/gather-pull-request-metadata@b86d1eaf7038cf88a56b26ba3e504f10e07b0ce5 - name: Upload Pull Request Metadata artifact - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with: name: Pull Request Metadata path: ${{ steps.metadata.outputs.metadata-file }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 49b6d1fb36734..48d926a62b9a4 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -36,7 +36,7 @@ jobs: persist-credentials: false - name: Label PR based on policy in labeler.yml - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b + uses: actions/labeler@c5dadc2a45784a4b6adfcd20fea3465da3a5f904 if: startsWith(github.event_name, 'pull_request') && github.base_ref == 'main' && github.event.action != 'closed' with: repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index ba293cf8be135..775b4f3f9d6fd 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -80,6 +80,9 @@ jobs: - name: Run clang-tidy run: mkosi box -- meson test -C build --suite=clang-tidy --print-errorlogs --no-stdsplit --quiet + - name: Run coccinelle checks + run: mkosi box -- meson test -C build --suite=coccinelle --print-errorlogs --no-stdsplit + - name: Build with musl run: | mkosi box -- \ diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml index 72daed60ef293..3aa169f55ad5c 100644 --- a/.github/workflows/make-release.yml +++ b/.github/workflows/make-release.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Release - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b + uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe with: prerelease: ${{ contains(github.ref_name, '-rc') }} draft: ${{ github.repository == 'systemd/systemd' }} diff --git a/.github/workflows/mkosi.yml b/.github/workflows/mkosi.yml index e011c146231d9..859e50a34ccc8 100644 --- a/.github/workflows/mkosi.yml +++ b/.github/workflows/mkosi.yml @@ -60,7 +60,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-O2 -D_FORTIFY_SOURCE=3" - relabel: no vm: 1 no_qemu: 0 no_kvm: 0 @@ -71,7 +70,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: no vm: 0 no_qemu: 0 no_kvm: 0 @@ -82,7 +80,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: no vm: 0 no_qemu: 0 no_kvm: 0 @@ -93,7 +90,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: no vm: 0 no_qemu: 1 no_kvm: 1 @@ -104,7 +100,16 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: no + vm: 0 + no_qemu: 0 + no_kvm: 0 + shim: 0 + - distro: ubuntu + release: resolute + runner: ubuntu-24.04 + sanitizers: "" + llvm: 0 + cflags: "-Og" vm: 0 no_qemu: 0 no_kvm: 0 @@ -115,7 +120,6 @@ jobs: sanitizers: address,undefined llvm: 1 cflags: "-Og" - relabel: yes vm: 0 no_qemu: 0 no_kvm: 0 @@ -126,7 +130,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: yes vm: 0 no_qemu: 0 no_kvm: 0 @@ -137,7 +140,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: yes vm: 0 no_qemu: 0 no_kvm: 0 @@ -148,7 +150,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: yes vm: 0 no_qemu: 0 no_kvm: 0 @@ -159,7 +160,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: yes vm: 0 no_qemu: 0 no_kvm: 0 @@ -222,9 +222,6 @@ jobs: LLVM=${{ matrix.llvm }} SYSEXT=1 - [Content] - SELinuxRelabel=${{ matrix.relabel }} - [Runtime] RAM=4G EOF @@ -313,7 +310,7 @@ jobs: "${MAX_LINES[@]}" - name: Archive failed test journals - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 if: failure() && (github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable') with: name: ci-mkosi-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.distro }}-${{ matrix.release }}-${{ matrix.runner }}-failed-test-journals diff --git a/.github/workflows/requirements.txt b/.github/workflows/requirements.txt index 08af02c80fcb4..95f1bf1a6a5ad 100644 --- a/.github/workflows/requirements.txt +++ b/.github/workflows/requirements.txt @@ -1,21 +1,23 @@ -meson==1.10.1 \ - --hash=sha256:c42296f12db316a4515b9375a5df330f2e751ccdd4f608430d41d7d6210e4317 \ - --hash=sha256:fe43d1cc2e6de146fbea78f3a062194bcc0e779efc8a0f0d7c35544dfb86731f -ninja==1.11.1.4 \ - --hash=sha256:055f386fb550c2c9d6157e45e20a84d29c47968876b9c5794ae2aec46f952306 \ - --hash=sha256:096487995473320de7f65d622c3f1d16c3ad174797602218ca8c967f51ec38a0 \ - --hash=sha256:2ab67a41c90bea5ec4b795bab084bc0b3b3bb69d3cd21ca0294fc0fc15a111eb \ - --hash=sha256:4617b3c12ff64b611a7d93fd9e378275512bb36eff8babff7c83f5116b4f8d66 \ - --hash=sha256:5713cf50c5be50084a8693308a63ecf9e55c3132a78a41ab1363a28b6caaaee1 \ - --hash=sha256:6aa39f6e894e0452e5b297327db00019383ae55d5d9c57c73b04f13bf79d438a \ - --hash=sha256:9c29bb66d2aa46a2409ab369ea804c730faec7652e8c22c1e428cc09216543e5 \ - --hash=sha256:b33923c8da88e8da20b6053e38deb433f53656441614207e01d283ad02c5e8e7 \ - --hash=sha256:c3b96bd875f3ef1db782470e9e41d7508905a0986571f219d20ffed238befa15 \ - --hash=sha256:cede0af00b58e27b31f2482ba83292a8e9171cdb9acc2c867a3b6e40b3353e43 \ - --hash=sha256:cf4453679d15babc04ba023d68d091bb613091b67101c88f85d2171c6621c6eb \ - --hash=sha256:cf554e73f72c04deb04d0cf51f5fdb1903d9c9ca3d2344249c8ce3bd616ebc02 \ - --hash=sha256:cfdd09776436a1ff3c4a2558d3fc50a689fb9d7f1bdbc3e6f7b8c2991341ddb3 \ - --hash=sha256:d3090d4488fadf6047d0d7a1db0c9643a8d391f0d94729554dbb89b5bdc769d7 \ - --hash=sha256:d4a6f159b08b0ac4aca5ee1572e3e402f969139e71d85d37c0e2872129098749 \ - --hash=sha256:ecce44a00325a93631792974659cf253a815cc6da4ec96f89742925dfc295a0d \ - --hash=sha256:f6186d7607bb090c3be1e10c8a56b690be238f953616626f5032238c66e56867 +meson==1.10.2 \ + --hash=sha256:5f84ef186e6e788d9154db63620fc61b3ece69f643b94b43c8b9203c43d89b36 \ + --hash=sha256:7890287d911dd4ee1ebd0efb61ed0321bfcd87c725df923a837cf90c6508f96b +ninja==1.13.0 \ + --hash=sha256:11be2d22027bde06f14c343f01d31446747dbb51e72d00decca2eb99be911e2f \ + --hash=sha256:1c97223cdda0417f414bf864cfb73b72d8777e57ebb279c5f6de368de0062988 \ + --hash=sha256:3c0b40b1f0bba764644385319028650087b4c1b18cdfa6f45cb39a3669b81aa9 \ + --hash=sha256:3d00c692fb717fd511abeb44b8c5d00340c36938c12d6538ba989fe764e79630 \ + --hash=sha256:3d7d7779d12cb20c6d054c61b702139fd23a7a964ec8f2c823f1ab1b084150db \ + --hash=sha256:4a40ce995ded54d9dc24f8ea37ff3bf62ad192b547f6c7126e7e25045e76f978 \ + --hash=sha256:4be9c1b082d244b1ad7ef41eb8ab088aae8c109a9f3f0b3e56a252d3e00f42c1 \ + --hash=sha256:5f8e1e8a1a30835eeb51db05cf5a67151ad37542f5a4af2a438e9490915e5b72 \ + --hash=sha256:60056592cf495e9a6a4bea3cd178903056ecb0943e4de45a2ea825edb6dc8d3e \ + --hash=sha256:6739d3352073341ad284246f81339a384eec091d9851a886dfa5b00a6d48b3e2 \ + --hash=sha256:8cfbb80b4a53456ae8a39f90ae3d7a2129f45ea164f43fadfa15dc38c4aef1c9 \ + --hash=sha256:aa45b4037b313c2f698bc13306239b8b93b4680eb47e287773156ac9e9304714 \ + --hash=sha256:b4f2a072db3c0f944c32793e91532d8948d20d9ab83da9c0c7c15b5768072200 \ + --hash=sha256:be7f478ff9f96a128b599a964fc60a6a87b9fa332ee1bd44fa243ac88d50291c \ + --hash=sha256:d741a5e6754e0bda767e3274a0f0deeef4807f1fec6c0d7921a0244018926ae5 \ + --hash=sha256:e8bad11f8a00b64137e9b315b137d8bb6cbf3086fbdc43bf1f90fd33324d2e96 \ + --hash=sha256:fa2a8bfc62e31b08f83127d1613d10821775a0eb334197154c4d6067b7068ff1 \ + --hash=sha256:fb46acf6b93b8dd0322adc3a4945452a4e774b75b91293bafcc7b7f8e6517dfa \ + --hash=sha256:fb8ee8719f8af47fed145cced4a85f0755dd55d45b2bddaf7431fa89803c5f3e diff --git a/.github/workflows/unit-tests-musl.yml b/.github/workflows/unit-tests-musl.yml index 2120eddeeb1dc..a5b619796f2b6 100644 --- a/.github/workflows/unit-tests-musl.yml +++ b/.github/workflows/unit-tests-musl.yml @@ -53,7 +53,6 @@ jobs: iproute2 iptables-dev kbd - kexec-tools kmod kmod-dev libapparmor-dev diff --git a/.gitignore b/.gitignore index c5d98a4ece9a4..a6aec324960a2 100644 --- a/.gitignore +++ b/.gitignore @@ -24,13 +24,16 @@ __pycache__/ /ID /build* /install-tree -/mkosi/mkosi.key /mkosi/mkosi.crt +/mkosi/mkosi.key +/mkosi/mkosi.local.conf +/mkosi/mkosi.tools +/mkosi/mkosi.tools.manifest /mkosi.tools/ /mkosi.tools.manifest -/mkosi/mkosi.local.conf /tags .dir-locals-2.el .vscode/ /pkg/ .aider* +/worktrees diff --git a/.mailmap b/.mailmap index 9d35e1efd6496..a9c0ec7e87971 100644 --- a/.mailmap +++ b/.mailmap @@ -180,6 +180,7 @@ Salvo Tomaselli Sandy Carter Scott James Remnant Scott James Remnant +Sebastian Bernardt Seraphime Kirkovski Shawn Landden Shawn Landden diff --git a/.packit.yml b/.packit.yml index 499b28f7c47fd..97e1e58855048 100644 --- a/.packit.yml +++ b/.packit.yml @@ -39,7 +39,7 @@ jobs: trigger: pull_request fmf_url: https://src.fedoraproject.org/rpms/systemd # This is automatically updated by tools/fetch-distro.py --update fedora - fmf_ref: 23a1c1fed99e152d9c498204175a7643371a822c + fmf_ref: 207e2d004468bf79a8bd78182d9b10956edf45c7 targets: - fedora-rawhide-x86_64 # testing-farm in the Fedora repository is explicitly configured to use testing-farm bare metal runners as diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index 42df0f648f5ec..baa6ecfa4a02d 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -7,7 +7,7 @@ name: Debian autopkgtest (LXC) agent: machine: type: e1-standard-2 - os_image: ubuntu2004 + os_image: ubuntu2404 # Cancel any running or queued job for the same ref auto_cancel: diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000000..ffc47c05b0562 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,43 @@ +# AGENTS.md + +This file provides guidance to AI coding agents when working with code in this repository. Only add +instructions to this file if you've seen an AI agent mess up that particular bit of logic in practice. + +## Key Documentation + +Always consult these files as needed: + +- `docs/ARCHITECTURE.md` — code organization and component relationships +- `docs/HACKING.md` — development workflow with mkosi +- `docs/CODING_STYLE.md` — full style guide (must-read before writing code) +- `docs/CONTRIBUTING.md` — contribution guidelines and PR workflow + +## Running arbitrary commands + +- Never use `mkosi box` to wrap commands. You are either already running inside an mkosi box environment or +running outside of it — use the tools available in your current environment directly. + +## Build and Test Commands + +- Never compile individual files. Always run `meson compile -C build ` to build the target you're +working on. Meson handles incremental compilation automatically. +- Never run `meson compile` followed by `meson test` as separate steps. Always run +`meson test -C build -v ` directly. Meson will automatically rebuild any required targets before +running tests. +- Never invent your own build commands or try to optimize the build process. +- Never use `head`, `tail`, or pipe (`|`) the output of build or test commands. Always let the full output +display. This is critical for diagnosing build and test failures. + +## Integration Tests + +- Never use `grep -q` in pipelines; use `grep >/dev/null` instead (avoids `SIGPIPE`) + +## Pull Request Review Instructions + +- Always check out the PR in a git worktree in `worktrees/`, review it locally and remove the worktree when finished. + +## AI Contribution Disclosure + +Per project policy: if you use AI code generation tools, you **must disclose** this in commit messages +by adding e.g. `Co-developed-by: Claude Opus 4.6 `. +All AI-generated output requires thorough human review before submission. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000000000..47dc3e3d863cf --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/LICENSES/README.md b/LICENSES/README.md index 5522a08e10910..9bd26baa0dc67 100644 --- a/LICENSES/README.md +++ b/LICENSES/README.md @@ -57,6 +57,7 @@ The following exceptions apply: * the following sources are licensed under the **MIT-0** license: - all examples under man/ - config files and examples under /network + - src/systemd/sd-dlopen.h * the following sources are under **Public Domain** (LicenseRef-murmurhash2-public-domain): - src/basic/MurmurHash2.c - src/basic/MurmurHash2.h diff --git a/NEWS b/NEWS index f858e9119f346..451e3f1b79603 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,78 @@ systemd System and Service Manager -CHANGES WITH 260 in spe: +CHANGES WITH 261 in spe: + + Announcements of Future Feature Removals and Incompatible Changes: + + * systemd-logind's integration with the UAPI.1 Boot Loader + Specification (which allows systemctl reboot --boot-loader-entry= + switch to work) so far has supported a special directory + /run/boot-loader-entries/ which allowed defining boot loader entries + outside of the ESP/XBOOTLDR partition for compatibility with legacy + systems that do not natively implement UAPI.1. However, it appears + that (to our knowledge) it is not actually being used by any project + (quite unlike UAPI.1 itself, which found adoption far beyond + systemd), and its implementation is incomplete. With the future 262 + release we intend to remove support for /run/boot-loader-entries/ and + related interfaces, in order to simplify our codebase. Support for + UAPI.1 is – of course – kept in place. + + Feature Removals and Incompatible Changes: + + * systemd-nspawn's --user= option has been renamed to --uid=. The -u + short option continues to work. The old --user NAME and --user=NAME + form (with and without "=") are still accepted but deprecated; a warning + is emitted suggesting --uid=NAME. The --user option (without an argument) + has been repurposed as a standalone switch (without argument) to select + the user service manager scope, matching --system. + + * Several configuration fields in the io.systemd.Unit varlink interface + that were previously exposed as plain strings have been converted to + proper enum types. This adds type safety and IDL-level validation. + The output wire format now uses underscores instead of dashes and + plus signs in enum values (e.g. "tty-force" becomes "tty_force", + "kmsg+console" becomes "kmsg_console"). The previous use of plain + strings for these well-defined enumerations is considered a bug. + Affected enum types: ExecInputType, ExecOutputType, ProtectHome, + CGroupController, CollectMode, EmergencyAction, JobMode. + + * It was discovered that systemd-stub does not measure all the events + it measures to the TPM to the hardware CC registers (e.g. Intel TDX + RTMRs) using EFI_CC_MEASUREMENT_PROTOCOL. In particular, devicetree, + initrd, ucode addons and the UKI profile were only measured to the + TPM. The missing measurements got added, however, the expected + register values are now changed. This may need to be reflected in the + attestation environments which use hardware CC registers and not the + TPM quote. + + * systemd-nspawn gained a new --restrict-address-families= option (and + corresponding RestrictAddressFamilies= setting in .nspawn files) to + restrict which socket address families may be used in the container. + This is currently opt-in. In a future version, the default will be + changed to restrict socket address families to AF_INET, AF_INET6 and + AF_UNIX. + + New features: + + * A new tmpfiles.d/root.conf has been added that sets permissions + on the root directory (/) to 0555 + + * Networking to cloud IMDS services may be locked down for recognized + clouds. This is recommended for secure installations, but typically + conflicts with traditional IMDS clients such as cloud-init, which + require direct IMDS access. The new meson option "-Dimds-network=" + can be used to change the default mode to "locked" at build-time. + + Changes in systemd-sysext/systemd-confext: + + * New initrd services systemd-sysext-sysroot.service and + systemd-confext-sysroot.service are provided. These services are + used to merge system and configuration extensions for the main system + from the initrd. This overcomes the limitation that system and + configuration extensions merged from the main system itself cannot be + used to modify the resources which are used in the early boot. + +CHANGES WITH 260: Feature Removals and Incompatible Changes: @@ -68,12 +140,17 @@ CHANGES WITH 260 in spe: * The org.systemd.login1.Manager D-Bus interface has a minor API break. The CanPowerOff(), CanReboot(), CanSuspend(), etc. family of methods have introduced new return values which may break downstream - consumers, such as desktop environments. The new return values more + consumers such as desktop environments. The new return values more precisely communicate the status of inhibitors: 'inhibited', 'inhibitor-blocked', and 'challenge-inhibitor-blocked'. This allows desktops to differentiate between system administrator policy and temporary restrictions imposed by inhibitors. + * In systemd-260-rc1, the sd_varlink_field_type_t enum was extended in + a way that changed the numerical values of existing fields. This was + reverted for -rc2. Programs using sd-varlink and compiled with the + headers from -rc1 must be recompiled. + New system interfaces and components: * The os-release(5) gained a new field FANCY_NAME= that is similar to @@ -133,6 +210,18 @@ CHANGES WITH 260 in spe: Changes in the system and service manager: + * A new unit setting RootMStack= has been introduced, to support the + new "mstack" feature for services (see above). + + * The unit setting PrivateUsers= gained a new possible value "managed", + which automatically assigns a dynamic and transient range of 65536 + UIDs/GIDs to the unit, acquired via systemd-nsresourced. + + * The implementation for PrivateUsers=full has been updated to map the + full range of IDs. The workaround to allow nested systemd older than + 257 to correctly detect that it is under such a mapping has been + dropped. + * systemd now uses the CSI 18 terminal sequence to query terminal size. This allows the query to be made without changing the position of the cursor. Terminal emulators which do not yet support the @@ -151,21 +240,17 @@ CHANGES WITH 260 in spe: can be used to skip or fail the unit if the given path is not a socket. - * A new unit setting RootMStack= has been introduced, to support the - new "mstack" feature for services (see above). - - * The unit setting PrivateUsers= gained a new possible value "managed", - which automatically assigns a dynamic and transient range of 65536 - UIDs/GIDs to the unit, acquired via systemd-nsresourced. - - * The implementation for PrivateUsers=full has been updated to map the - full range of IDs. The workaround to allow nested systemd older than - 257 to correctly detect that it is under such a mapping has been - dropped. + * For units which specify PrivateTmp=yes and DefaultDependencies=no + without an explicit requirement for /tmp/, a disconnected /tmp/ will + be used, as if PrivateTmp=disconnected was specified. Also, if there + is no explicit ordering for /var/, the private mount for /var/tmp/ + will not be created. Those changes avoid race conditions with + creation of those private directories during early boot and may + result in changes to unit ordering. * EnqueueMarkedJobs() D-Bus method now has a Varlink counterpart. - * systemctl gained a new 'enqueue-marked-jobs' verb, which calls the + * systemctl gained a new 'enqueue-marked' verb, which calls the EnqueueMarkedJobs() D-Bus method. The '--marked' parameter, which was previously used for the same purpose, is now deprecated. @@ -218,16 +303,21 @@ CHANGES WITH 260 in spe: ID_INTEGRATION= because it was never used and the new variable covers the idea that variable was intended for better. + * A new udev builtin "tpm2_id" is now available which will extract + vendor/model identification from connected TPM2 devices as they are + probed. This is then used to import data from the udev database, + possibly containing quirk and other information about specific TPMs. + Changes in systemd-networkd: * MultiPathRoute= option now supports interface-bound ECMP routes. * systemd-networkd gained integration with ModemManager via the "simple - connect" protocol. A new [ModemManager] section has been added with - SimpleConnectProperties= (currently apn=, allowed-auth=, user=, - password=, ip-type=, allow-roaming=, pin=, and operator-id=), - RouteMetric=, and UseGateway= settings. This allows systemd-networkd - to establish a cellular modem connection to a broadband network. + connect" protocol. A new [MobileNetwork] section has been added with + APN=, AllowedAuthenticationMechanisms=, User=, Password=, IPFamily=, + AllowRoaming=, PIN=, OperatorId=, RouteMetric=, and UseGateway= + settings. This allows systemd-networkd to establish a cellular modem + connection to a broadband network. * systemd-networkd gained a pair of varlink methods io.systemd.Network.Link.Up()/Down(). 'networkctl up/down' now @@ -316,8 +406,8 @@ CHANGES WITH 260 in spe: the same switch in systemd-nspawn. * systemd-vmspawn gained a new switch --image-format= for selecting the - image format (i.e. support qcow2 in additin to raw) to boot - from. --extra-drive= now takes the image format as a colon separated + image format (i.e. support qcow2 in additin to raw) to boot from. + Also --extra-drive= now takes the image format as a colon separated parameter. Changes in systemd-nsresourced/systemd-mountfsd: @@ -456,6 +546,9 @@ CHANGES WITH 260 in spe: system components to synchronously hook into the OOM killing logic, by registering a Varlink socket in a special directory. + * systemd-analyze learnt a new verb "identify-tpm2" which shows + vendor/model information extracted from the system's TPM. + Changes in units: * runlevel[0-6].target units that were removed in v258 have been @@ -466,36 +559,47 @@ CHANGES WITH 260 in spe: * getty@.service gained an [Install] and must now be explicitly enabled to be active. - Contributions from: Adam Williamson, Adrian Vovk, Alessandro Astone, - Alexis-Emmanuel Haeringer, Allison Karlitskaya, André Paiusco, - Antonio Alvarez Feijoo, Artur Kowalski, AshishKumar Mishra, - Baurzhan Muftakhidinov, Ben Boeckel, Betacentury, - Carlos Peón Costa, Carolina Jubran, Cathy Hu, Chris Down, - Chris Lindee, Christian Brauner, Christian Glombek, - Christian Hesse, Christopher Head, Daan De Meyer, Daniel Foster, - Daniel Rusek, David Santamaría Rogado, David Tardon, - Derek J. Clark, Dirk Su, Dmitry V. Levin, Dmytro Bagrii, - Ettore Atalan, Florian Klink, Franck Bui, Govind Venugopal, + Contributions from: A S Alam, Adam Williamson, Adrian Vovk, + Alessandro Astone, Alexis-Emmanuel Haeringer, Allison Karlitskaya, + Américo Monteiro, Andrii Zora, André Paiusco, Anton Tiurin, + Antonio Alvarez Feijoo, Arjun-C-S, Artur Kowalski, + AshishKumar Mishra, Baurzhan Muftakhidinov, Ben Boeckel, + Betacentury, Bouke van der Bijl, Carlos Peón Costa, + Carolina Jubran, Cathy Hu, Chris Down, Chris Lindee, + Christian Brauner, Christian Glombek, Christian Hesse, + Christopher Cooper, Christopher Head, + Copilot Autofix powered by AI, Cyrus Xi, Daan De Meyer, + Dan McGregor, Daniel Foster, Daniel Nylander, Daniel Rusek, + David Santamaría Rogado, David Tardon, Derek J. Clark, Dirk Su, + Dmitry V. Levin, Dmytro Bagrii, Dylan M. Taylor, + Efstathios Iosifidis, Eisuke Kawashima, Ettore Atalan, Fergus Dall, + Florian Klink, Franck Bui, Frantisek Sumsal, Govind Venugopal, Graham Reed, Guiorgy, Han Sol Jin, Hans de Goede, Heran Yang, - Ivan Kruglov, Ivan Shapovalov, James Le Cuirot, Jeff Layton, - Jeremy Kerr, Jian Wen, Jim Spentzos, Julian Sparber, - Jörg Behrmann, Kai Lüke, Lennart Poettering, Louis Stagg, - Luca Boccassi, Lucas Werkmeister, Luiz Amaral, Marc Pervaz Boocha, - Mario Limonciello (AMD), Matt Fleming, Matteo Croce, - Matthijs Kooijman, Max Gautier, Maximilian Bosch, Miao Wang, - Michael Vogt, Michal Sekletár, Mike Gilbert, Mike Yuan, + IntenseWiggling, Ivan Kruglov, Ivan Shapovalov, James Le Cuirot, + Jan Kuparinen, Jeff Layton, Jeremy Kerr, Jesse Guo, Jian Wen, + Jim Spentzos, Julian Sparber, Jörg Behrmann, Kai Lüke, + Lennart Poettering, Louis Stagg, Luca Boccassi, Lucas Werkmeister, + Luiz Amaral, Léane GRASSER, Malcolm Frazier, Marc Pervaz Boocha, + Marcel Leismann, Mario Limonciello, Mario Limonciello (AMD), + Martin Srebotnjak, Matt Fleming, Matteo Croce, Matthijs Kooijman, + Max Gautier, Maximilian Bosch, Miao Wang, Michael Vogt, + Michal Sekletár, Mike Gilbert, Mike Yuan, Mikhail Novosyolov, Nandakumar Raghavan, Nick Rosbrook, Nicolas Dorier, Oblivionsage, - Oleksandr Andrushchenko, Pablo Fraile Alonso, Peter Oliver, - Philip Withnall, Popax21, Ryan Zeigler, Sriman Achanta, - Tabis Kabis, Thorsten Kukuk, Tobias Heider, Tobias Stoeckmann, - Ulrich Ölmann, Usama Arif, Vitaly Kuznetsov, Vunny Sodhi, - Yaping Li, Yaron Shahrabani, Yu Watanabe, ZauberNerd, - Zbigniew Jędrzejewski-Szmek, Zongyuan He, andre4ik3, calm329, cdown, - cyclopentane, francescoza6, gvenugo3, kiamvdd, nikstur, novenary, - r-vdp, safforddr, scarlet-storm, sd416, seidlerv, smosia, tuhaowen, - zefr0x - - — Edinburgh, 2026/02/25 + Oleksandr Andrushchenko, Oğuz Ersen, Pablo Fraile Alonso, + Peter Oliver, Philip Withnall, Pontus Lundkvist, Popax21, + Rito Rhymes, Rodrigo Campos, Ronan Pigott, Ryan Zeigler, + Salvatore Cocuzza, Sergey A., Skye Soss, Sriman Achanta, + Tabis Kabis, Temuri Doghonadze, The-An0nym, Thomas Weißschuh, + Thorsten Kukuk, Tobias Heider, Tobias Stoeckmann, Ulrich Ölmann, + Usama Arif, Val Markovic, Vitaly Kuznetsov, Vunny Sodhi, + Weixie Cui, Yaping Li, Yaron Shahrabani, Yu Watanabe, + Yuri Chornoivan, ZauberNerd, Zbigniew Jędrzejewski-Szmek, + Zongyuan He, andre4ik3, calm329, cdown, cyclopentane, davidak, + dongshengyuan, francescoza6, gvenugo3, joo es, kiamvdd, lumingzh, + naly zzwd, nikstur, novenary, noxiouz, patrick, ppkramer-hub, r-vdp, + safforddr, scarlet-storm, sd416, seidlerv, smosia, tuhaowen, zefr0x + + — Edinburgh, 2026/03/17 CHANGES WITH 259: @@ -629,9 +733,9 @@ CHANGES WITH 259: systemd-sysext/systemd-confext: * systemd-sysext and systemd-confext now support configuration files - /etc/systemd/systemd-sysext.conf and /etc/systemd/systemd-confext.conf, - which can be used to configure mutability or the image policy to - apply to DDI images. + /etc/systemd/sysext.conf and /etc/systemd/confext.conf, which can be + used to configure mutability or the image policy to apply to DDI + images. * systemd-sysext's and systemd-confext's --mutable= switch now accepts a new value "help" for listing available mutability modes. @@ -14219,7 +14323,7 @@ CHANGES WITH 235: the "utmp" group already, and it appears to be generally understood that members of "utmp" can modify/flush the utmp/wtmp/lastlog/btmp databases. Previously this was implemented correctly for all these - databases excepts btmp, which has been opened up like this now + databases except btmp, which has been opened up like this now too. Note that while the other databases are world-readable (i.e. 0644), btmp is not and remains more restrictive. diff --git a/README b/README index 0b2d53de1c895..ddcb863f4ada2 100644 --- a/README +++ b/README @@ -30,7 +30,7 @@ LICENSE: REQUIREMENTS: Linux kernel ≥ 3.15 for timerfd_create() CLOCK_BOOTTIME support - ≥ 3.17 for memfd_create() and getrandom() + ≥ 3.17 for memfd_create(), getrandom(), and kexec_file_load() (x86-64) ≥ 4.3 for ambient capabilities ≥ 4.5 for pids controller in cgroup v2 ≥ 4.6 for cgroup namespaces @@ -60,7 +60,7 @@ REQUIREMENTS: Linux kernel ≥ 5.11 for epoll_pwait2() ≥ 5.12 for idmapped mount (mount_setattr()) - ≥ 5.14 for cgroup.kill and quotactl_fd() + ≥ 5.14 for cgroup.kill, quotactl_fd(), and MOUNT_ATTR_NOSYMFOLLOW ⚠️ Kernel versions below 5.14 ("recommended baseline") have significant gaps in functionality and are not recommended for use with this version @@ -77,7 +77,7 @@ REQUIREMENTS: ≥ 6.10 for fcntl(F_DUPFD_QUERY), unprivileged linkat(AT_EMPTY_PATH), and block device 'partscan' sysfs attribute ≥ 6.12 for AT_HANDLE_MNT_ID_UNIQUE - ≥ 6.13 for PIDFD_GET_INFO and {set,remove}xattrat() and + ≥ 6.13 for PIDFD_GET_INFO, {set,remove}xattrat(), and FSCONFIG_SET_FD support for overlayfs layers ≥ 6.16 for coredump pattern '%F' (pidfd) specifier and SO_PASSRIGHTS @@ -264,9 +264,9 @@ REQUIREMENTS: During runtime, you need the following additional dependencies: - util-linux >= v2.41 required (including but not limited to: mount, - umount, swapon, swapoff, sulogin, - agetty, fsck) + util-linux >= v2.27.1 required (including but not limited to: mount, + umount, swapon, swapoff, sulogin, + agetty, fsck) dbus >= 1.4.0 (strictly speaking optional, but recommended) NOTE: If using dbus < 1.9.18, you should override the default policy directory (--with-dbuspolicydir=/etc/dbus-1/system.d). diff --git a/TODO b/TODO.md similarity index 59% rename from TODO rename to TODO.md index 072ce83f0aeee..d77225c5720ca 100644 --- a/TODO +++ b/TODO.md @@ -1,40 +1,47 @@ -Bugfixes: +--- +title: TODO +category: Contributing +layout: default +SPDX-License-Identifier: LGPL-2.1-or-later +--- -* Many manager configuration settings that are only applicable to user +# TODO + +## Bugfixes + +- Many manager configuration settings that are only applicable to user manager or system manager can be always set. It would be better to reject them when parsing config. -* Jun 01 09:43:02 krowka systemd[1]: Unit user@1000.service has alias user@.service. +- Jun 01 09:43:02 krowka systemd[1]: Unit user@1000.service has alias user@.service. Jun 01 09:43:02 krowka systemd[1]: Unit user@6.service has alias user@.service. Jun 01 09:43:02 krowka systemd[1]: Unit user-runtime-dir@6.service has alias user-runtime-dir@.service. -External: +## External -* Fedora: add an rpmlint check that verifies that all unit files in the RPM are listed in %systemd_post macros. +- Fedora: add an rpmlint check that verifies that all unit files in the RPM are listed in %systemd_post macros. -* dbus: - - natively watch for dbus-*.service symlinks (PENDING) - - teach dbus to activate all services it finds in /etc/systemd/services/org-*.service +- **dbus:** + - natively watch for dbus-*.service symlinks (PENDING) + - teach dbus to activate all services it finds in /etc/systemd/services/org-*.service -* fedora: suggest auto-restart on failure, but not on success and not on coredump. also, ask people to think about changing the start limit logic. Also point people to RestartPreventExitStatus=, SuccessExitStatus= +- fedora: suggest auto-restart on failure, but not on success and not on coredump. also, ask people to think about changing the start limit logic. Also point people to RestartPreventExitStatus=, SuccessExitStatus= -* neither pkexec nor sudo initialize environ[] from the PAM environment? +- neither pkexec nor sudo initialize environ[] from the PAM environment? -* fedora: update policy to declare access mode and ownership of unit files to root:root 0644, and add an rpmlint check for it +- fedora: update policy to declare access mode and ownership of unit files to root:root 0644, and add an rpmlint check for it -* missing shell completions: - - systemd-hwdb +- **missing shell completions:** + - **zsh:** + - ` -` should complete options, but currently does not + - systemctl add-wants,add-requires + - systemctl reboot --boot-loader-entry= -* zsh shell completions: - - - should complete options, but currently does not - - systemctl add-wants,add-requires - - systemctl reboot --boot-loader-entry= - -* systemctl status should know about 'systemd-analyze calendar ... --iterations=' -* If timer has just OnInactiveSec=..., it should fire after a specified time +- systemctl status should know about 'systemd-analyze calendar ... --iterations=' +- If timer has just OnInactiveSec=..., it should fire after a specified time after being started. -* write blog stories about: +- **write blog stories about:** - hwdb: what belongs into it, lsusb - enabling dbus services - how to make changes to sysctl and sysfs attributes @@ -52,570 +59,623 @@ External: - instantiated apache, dovecot and so on - hooking a script into various stages of shutdown/early boot -Regularly: +## Regularly -* look for close() vs. close_nointr() vs. close_nointr_nofail() +- look for close() vs. close_nointr() vs. close_nointr_nofail() -* check for strerror(r) instead of strerror(-r) +- check for strerror(r) instead of strerror(-r) -* pahole +- pahole -* set_put(), hashmap_put() return values check. i.e. == 0 does not free()! +- set_put(), hashmap_put() return values check. i.e. == 0 does not free()! -* link up selected blog stories from man pages and unit files Documentation= fields +- link up selected blog stories from man pages and unit files Documentation= fields -Janitorial Clean-ups: +## Janitorial Cleanups -* machined: make remaining machine bus calls compatible with unpriv machined + +- machined: make remaining machine bus calls compatible with unpriv machined + unpriv npsawn: GetAddresses(), GetSSHInfo(), GetOSRelease(), OpenPTY(), OpenLogin(), OpenShell(), BindMount(), CopyFrom(), CopyTo(), OpenRootDirectory(). Similar for images: GetHostname(), GetMachineID(), GetMachineInfo(), GetOSRelease(). -* rework mount.c and swap.c to follow proper state enumeration/deserialization +- rework mount.c and swap.c to follow proper state enumeration/deserialization semantics, like we do for device.c now -* Replace our fstype_is_network() with a call to libmount's mnt_fstype_is_netfs()? +- Replace our fstype_is_network() with a call to libmount's mnt_fstype_is_netfs()? Having two lists is not nice, but maybe it's now worth making a dependency on libmount for something so trivial. -* drop set_free_free() and switch things over from string_hash_ops to +- drop set_free_free() and switch things over from string_hash_ops to string_hash_ops_free everywhere, so that destruction is implicit rather than explicit. Similar, for other special hashmap/set/ordered_hashmap destructors. -* generators sometimes apply C escaping and sometimes specifier escaping to +- generators sometimes apply C escaping and sometimes specifier escaping to paths and similar strings they write out. Sometimes both. We should clean this up, and should probably always apply both, i.e. introduce unit_file_escape() or so, which applies both. -* xopenat() should pin the parent dir of the inode it creates before doing its +- xopenat() should pin the parent dir of the inode it creates before doing its thing, so that it can create, open, label somewhat atomically. -* use CHASE_MUST_BE_DIRECTORY and CHASE_MUST_BE_REGULAR at more places (the +- use CHASE_MUST_BE_DIRECTORY and CHASE_MUST_BE_REGULAR at more places (the majority of places that currently employ chase() probably should use this) -Deprecations and removals: +## Deprecations and Removals -* Remove any support for booting without /usr pre-mounted in the initrd entirely. +- Remove any support for booting without /usr pre-mounted in the initrd entirely. Update INITRD_INTERFACE.md accordingly. -* remove cgroups v1 support EOY 2023. As per +- remove cgroups v1 support (overdue since EOY 2023). As per https://lists.freedesktop.org/archives/systemd-devel/2022-July/048120.html and then rework cgroupsv2 support around fds, i.e. keep one fd per active unit around, and always operate on that, instead of cgroup fs paths. -* drop support for LOOP_CONFIGURE-less loopback block devices, once kernel +- drop support for LOOP_CONFIGURE-less loopback block devices, once kernel baseline is 5.8. -* Remove /dev/mem ACPI FPDT parsing when /sys/firmware/acpi/fpdt is ubiquitous. +- Remove /dev/mem ACPI FPDT parsing when /sys/firmware/acpi/fpdt is ubiquitous. That requires distros to enable CONFIG_ACPI_FPDT, and have kernels v5.12 for x86 and v6.2 for arm. -* In v260: remove support for deprecated FactoryReset EFI variable in - systemd-repart, replaced by FactoryResetRequest. +- Remove support for deprecated FactoryReset EFI variable in + systemd-repart, replaced by FactoryResetRequest (was planned for v260). -* Consider removing root=gpt-auto, and push people to use root=dissect instead. +- Consider removing root=gpt-auto, and push people to use root=dissect instead. -* remove any trace of "cpuacct" cgroup controller, it's a cgroupv1 thing. +- remove any trace of "cpuacct" cgroup controller, it's a cgroupv1 thing. similar "devices" -Features: +## Features -* sd-lldp: pick up 802.3 maximum frame size/mtu, to be able to detect jumbo - frame capable networks +- a small tool that can do basic btrfs raid policy mgmt. i.e. gets started as + part of the initial transaction for some btrfs raid fs, waits for some time, + then puts message on screen (plymouth, console) that some devices apparently + are not showing up, then counts down, eventually set a flag somewhere, and + retriggers the fs is was invoked for, which causes the udev rules to rerun + that assemble the btrfs raid, but this time force degraded assembly. -* networkd: maintain a file in /run/ that can be symlinked into /run/issue.d/ - that always shows the current primary IP address +- a way for container managers to turn off getty starting via $container_headless= or so... -* oci: add support for blake hashes for layers +- add "conditions" for bls type 1 and type 2 profiles that allow suppressing + them under various conditions: 1. if tpm2 is available or not available; + 2. if sb is on or off; 3. if we are netbooted or not; … -* oci: add support for "importctl import-oci" which implements the "OCI layout" - spec (i.e. acquiring via local fs access), as opposed to the current - "importctl pull-oci" which focusses on the "OCI image spec", i.e. downloads - from the web (i.e. acquiring via URLs). +- add "homectl export" and "homectl import" that gets you an "atomic" snapshot + of your homedir, i.e. either a tarball or a snapshot of the underlying disk + (use FREEZE/THAW to make it consistent, btrfs snapshots) -* oci: support "data" in any OCI descriptor, not just manifest config. +- Add "purpose" flag to partition flags in discoverable partition spec that + indicate if partition is intended for sysext, for portable service, for + booting and so on. Then, when dissecting DDI allow specifying a purpose to + use as additional search condition. Use case: images that combined a sysext + partition with a portable service partition in one. -* report: - - plug "facts" into systemd-report too, i.e. stuff that is more static, such as hostnames, ssh keys and so on. - - pass filtering hints to services, so that they can also be applied server-side, not just client side - - metrics from pid1: suppress metrics form units that are inactive and have nothing to report - - add "hint-suppress-zero" flag (which suppresses all metrics which are zero) - - add "hint-object" parameter (which only queries info about certain object) - - make systemd-report a varlink service +- add "systemctl wait" or so, which does what "systemd-run --wait" does, but + for all units. It should be both a way to pin units into memory as well as a + wait to retrieve their exit data. -* implement a varlink registry service, similar to the one of the reference - implementation, backed by /run/varlink/registry/. Then, also implement - connect-via-registry-resolution in sd-varlink and varlinkctl. Care needs to - be taken to do the resolution asynchronousy. Also, note that the Varlink - reference implementation uses a different address syntax, which needs to be - taken into account. +- add "systemd-analyze debug" + AttachDebugger= in unit files: The former + specifies a command to execute; the latter specifies that an already running + "systemd-analyze debug" instance shall be contacted and execution paused + until it gives an OK. That way, tools like gdb or strace can be safely be + invoked on processes forked off PID 1. -* downgrade the uid/gid disposition enforcement in udev +- add "systemd-sysext identify" verb, that you can point on any file in /usr/ + and that determines from which overlayfs layer it originates, which image, and with + what it was signed. -* have a signal that reloads every unit that supports reloading +- add --vacuum-xyz options to coredumpctl, matching those journalctl already has. -* systemd: add storage API via varlink, where everyone can drop a socket in a - dir, similar, do the same thing for networking +- Add a "systemctl list-units --by-slice" mode or so, which rearranges the + output of "systemctl list-units" slightly by showing the tree structure of + the slices, and the units attached to them. -* do a console daemon that takes stdio fds for services and allows to reconnect - to them later +- Add a concept of ListenStream=anonymous to socket units: listen on a socket + that is deleted in the fs. Use case would be with ConnectSocket= above. -* report: have something that requests cloud workload identity bearer tokens - and includes it in the report +- add a ConnectSocket= setting to service unit files, that may reference a + socket unit, and which will connect to the socket defined therein, and pass + the resulting fd to the service program via socket activation proto. -* sysupdate: download multiple arbitrary patterns from same source +- add a dbus call to generate target from current state -* sysupdate: SHA256SUMS format with bearer tokens for each resource to download +- add a dependency on standard-conf.xml and other included files to man pages -* sysupdate: decrypt SHA256SUMS with key from tpm +- add a job mode that will fail if a transaction would mean stopping + running units. Use this in timedated to manage the NTP service + state. + https://lists.freedesktop.org/archives/systemd-devel/2015-April/030229.html -* sysupdate: clean up stuff on disk that disappears from SHA256SUMS +- add a kernel cmdline switch (and cred?) for marking a system to be + "headless", in which case we never open /dev/console for reading, only for + writing. This would then mean: systemd-firstboot would process creds but not + ask interactively, getty would not be started and so on. -* sysupdate: turn http backend stuff int plugin via varlink +- add a Load= setting which takes literal data in text or base64 format, and + puts it into a memfd, and passes that. This enables some fun stuff, such as + embedding bash scripts in unit files, by combining Load= with + ExecStart=/bin/bash /proc/self/fd/3 -* add new tool that can be used in debug mode runs in very early boot, - generates a random password, passes it as credential to sysusers for the root - user, then displays it on screen. people can use this to remotely log in. +- add a mechanism we can drop capabilities from pid1 *before* transitioning + from initrd to host. i.e. before we transition into the slightly lower trust + domain that is the host systems we might want to get rid of some caps. + Example: CAP_SYS_BPF in the signed bpf loading logic above. (We already have + CapabilityBoundingSet= in system.conf, but that is enforced when pid 1 + initializes, rather then when it transitions to the next.) -* Maybe introducean InodeRef structure inspired by PidRef, which references a - specific inode, and combines: a path, an O_PATH fd, and possibly a FID into - one. Why? We often pass around path and fd separately in chaseat() and similar - calls. Because passing around both separately is cumbersome we sometimes only - one pass one, once the other and sometimes both. It would make the code a lot - simpler if we could path both around at the same time in a simple way, via an - InodeRef which *both* pins the inode via an fd, *and* gives us a friendly - name for it. +- add a new "debug" job mode, that is propagated to unit_start() and for + services results in two things: we raise SIGSTOP right before invoking + execve() and turn off watchdog support. Then, use that to implement + "systemd-gdb" for attaching to the start-up of any system service in its + natural habitat. -* systemd-sysupdate: for each transfer support looking at multiple sources, - pick source with newest entry. If multiple sources have the same entry, use - first configured source. Usecase: "sideload" components from local dirs, - without disabling remote sources. +- add a new flag to chase() that stops chasing once the first missing + component is found and then allows the caller to create the rest. -* systemd-sysupdate: support "revoked" items, which cause the client to - downgrade/upgrade +- add a new PE binary section ".mokkeys" or so which sd-stub will insert into + Mok keyring, by overriding/extending whatever shim sets in the EFI + var. Benefit: we can extend the kernel module keyring at ukify time, + i.e. without recompiling the kernel, taking an upstream OS' kernel and adding + a local key to it. -* portable services: attach not only unit files to host, but also simple - binaries to a tmpfs path in $PATH. +- add a new specifier to unit files that figures out the DDI the unit file is + from, tracing through overlayfs, DM, loopback block device. -* systemd-sysext: add "exec" command or so that is a bit like "refresh" but - runs it in a new namespace and then just executes the selected binary within - it. Could be useful to run one-off binaries inside a sysext as a CLI tool. +- add a new switch --auto-definitions=yes/no or so to systemd-repart. If + specified, synthesize a definition automatically if we can: enlarge last + partition on disk, but only if it is marked for growing and not read-only. -* systemd-repart: implement Integrity=data/meta and Integrity=inline for non-LUKS - case. Currently, only Integrity=inline combined with Encrypt= is implemented - and uses libcryptsetup features. Add support for plain dm-integrity setups when - integrity tags are stored by the device (inline), interleaved with data (data), - and on a separate device (meta). +- add a new syscall group "@esoteric" for more esoteric stuff such as bpf() and + usefaultd() and make systemd-analyze check for it. -* homed/pam_systemd: allow authentication by ssh-agent, so that run0/polkit can - be allowed if caller comes with the right ssh-agent keys. +- Add a new verb "systemctl top" -* machined: gc for OCI layers that are not referenced anymore by any .mstack/ links. +- add a pam module that on password changes updates any LUKS slot where the password matches -* pull-oci: progress notification +- add a percentage syntax for TimeoutStopSec=, e.g. TimeoutStopSec=150%, and + then use that for the setting used in user@.service. It should be understood + relative to the configured default value. -* networkd/machined: implement reverse name lookups in the resolved hook +- add a plugin for factory reset logic that erases certain parts of the ESP, + but leaves others in place. -* networkd's resolved hook: optionally map all lease IP addresses handed out to - the same hostname which is configured on the .network file. Optionally, even - derive this single name from the network interface name (i.e. probably - altname or so). This way, when spawning a VM the host could pick the hostname - for it and the client gets no say. +- add a proper concept of a "developer" mode, i.e. where cryptographic + protections of the root OS are weakened after interactive confirmation, to + allow hackers to allow their own stuff. idea: allow entering developer mode + only via explicit choice in boot menu: i.e. add explicit boot menu item for + it. When developer mode is entered, generate a key pair in the TPM2, and add + the public part of it automatically to keychain of valid code signature keys + on subsequent boots. Then provide a tool to sign code with the key in the + TPM2. Ensure that boot menu item is the only way to enter developer mode, by + binding it to locality/PCRs so that keys cannot be generated otherwise. -* systemd-repart: add --ghost, that creates file systems, updates the kernel's - partition table but does *not* update partition table on disk. This way, we - have disk backed file systems that go effectively disappear on reboot. This - is useful when booting from a "live" usb stick that is writable, as it means - we do not have to place everything in memory. Moreover, we could then migrate - the file systems to disk later (using btrfs device replacement), if needed as - part of an installer logic. +- add a system-wide seccomp filter list for syscalls, kill "acct()" "@obsolete" + and a few other legacy syscalls that way. -* journald: log pidfid as another field, i.e. _PIDFDID= +- add a test if all entries in the catalog are properly formatted. + (Adding dashes in a catalog entry currently results in the catalog entry + being silently skipped. journalctl --update-catalog must warn about this, + and we should also have a unit test to check that all our message are OK.) -* measure all log-in attempts into a new nvpcr +- add a utility that can be used with the kernel's + CONFIG_STATIC_USERMODEHELPER_PATH and then handles them within pid1 so that + security, resource management and cgroup settings can be enforced properly + for all umh processes. -* measure all DDI activations into a new nvpcr +- add a way to lock down cgroup migration: a boolean, which when set for a unit + makes sure the processes in it can never migrate out of it -* maybe rework systemd-modules-load to be a generator that just instantiates - modprobe@.service a bunch of times +- add ability to path_is_valid() to classify paths that refer to a dir from + those which may refer to anything, and use that in various places to filter + early. i.e. stuff ending in "/", "/." and "/.." definitely refers to a + directory, and paths ending that way can be refused early in many contexts. -* Split vconsole-setup in two, of which the second is started via udev (instead - of the "restart" job it currently fires). That way, boot becomes purely - positive again, and we can nicely order the two against each other. +- Add ACL-based access management to .socket units. i.e. add AllowPeerUser= + + AllowPeerGroup= that installs additional user/group ACL entries on AF_UNIX + sockets. -* Add ELF section to make systemd main binary recognizable cleanly, the same - way as we make sd-boot recognizable via PE section. +- Add AddUser= setting to unit files, similar to DynamicUser=1 which however + creates a static, persistent user rather than a dynamic, transient user. We + can leverage code from sysusers.d for this. -* Add knob to cryptsetup, to trigger automatic reboot on failure to unlock - disk. Enable this by default for rootfs, also in gpt-auto-generator +- add an explicit parser for LimitRTPRIO= that verifies + the specified range and generates sane error messages for incorrect + specifications. -* Add RebootUptimeMinSec= knob to PID 1, that makes systemd-shutdown sleep - until the specified uptime has passed, to lengthen tight boot loops. +- Add and pickup tpm2 metadata for creds structure. -* replace bootctl's PE version check to actually use APIs from pe-binary.[ch] - to find binary version. +- add another PE section ".fname" or so that encodes the intended filename for + PE file, and validate that when loading add-ons and similar before using + it. This is particularly relevant when we load multiple add-ons and want to + sort them to apply them in a define order. The order should not be under + control of the attacker. -* replace symlink_label(), mknodat_label(), btrfs_subvol_make_label(), - mkdir_label() and related calls by flags-based calls that use - label_ops_pre()/label_ops_post(). +- add bus API for creating unit files in /etc, reusing the code for transient units -* maybe reconsider whether virtualization consoles (hvc1) are considered local - or remote. i.e. are they more like an ssh login, or more like a /dev/tty1 - login? Lennart used to believe the former, but maybe the latter is more - appropriate? This has effect on polkit interactivity, since it would mean - questions via hvc0 would suddenly use the local polkit property. But this - also raises the question whether such sessions shall be considered active or - not +- add bus api to query unit file's X fields. -* automatically reset specific EFI vars on factory reset (make this generic - enough so that infrac can be used to erase shim's mok vars?) +- add bus API to remove unit files from /etc -* similar: add a plugin for factory reset logic that erases certain parts of - the ESP, but leaves others in place. +- add bus API to retrieve current unit file contents (i.e. implement "systemctl cat" on the bus only) -* flush_fd() should probably try to be smart and stop reading once we know that - all further queued data was enqueued after flush_fd() was originally - called. For that, try SIOCINQ if fd refers to stream socket, and look at - timestamps for datagram sockets. +- Add ConditionDirectoryNotEmpty= handle non-absoute paths as a search path or add + ConditionConfigSearchPathNotEmpty= or different syntax? See the discussion starting at + https://github.com/systemd/systemd/pull/15109#issuecomment-607740136. -* Similar flush_accept() should look at sockdiag queued sockets count and exit - once we flushed out the specified number of connections. +- add CopyFile= or so as unit file setting that may be used to copy files or + directory trees from the host to the services RootImage= and RootDirectory= + environment. Which we can use for /etc/machine-id and in particular + /etc/resolv.conf. Should be smart and do something useful on read-only + images, for example fall back to read-only bind mounting the file instead. -* maybe introduce a new per-unit drop-in directory .confext.d/ that may contain - symlinks to confext images to enable for the unit. +- Add ELF section to make systemd main binary recognizable cleanly, the same + way as we make sd-boot recognizable via PE section. -* nspawn: map foreign UID range through 1:1 +- Add ExecMonitor= setting. May be used multiple times. Forks off a process in + the service cgroup, which is supposed to monitor the service, and when it + exits the service is considered failed by its monitor. -* a small tool that can do basic btrfs raid policy mgmt. i.e. gets started as - part of the initial transaction for some btrfs raid fs, waits for some time, - then puts message on screen (plymouth, console) that some devices apparently - are not showing up, then counts down, eventually set a flag somewhere, and - retriggers the fs is was invoked for, which causes the udev rules to rerun - that assemble the btrfs raid, but this time force degraded assembly. +- add field to bls type 1 and type 2 profiles that ensures an item is never + considered for automatic selection -* systemd-repart: make useful to duplicate current OS onto a second disk, so - that we can sanely copy ESP contents, /usr/ images, and then set up btrfs - raid for the root fs to extend/mirror the existing install. This would be - very similar to the concept of live-install-through-btrfs-migration. +- add generator that pulls in systemd-network from containers when + CAP_NET_ADMIN is set, more than the loopback device is defined, even + when it is otherwise off -* introduce /etc/boottab or so which lists block devices that bootctl + - kernel-install shall update the ESPs on (and register in EFI BootXYZ - variables), in addition to whatever is currently the booted /usr/. - systemd-sysupdate should also take it into consideration and update the - /usr/ images on all listed devices. +- add growvol and makevol options for /etc/crypttab, similar to + x-systemd.growfs and x-systemd-makefs. -* replace all uses of fopen_temporary() by fopen_tmpfile_linkable() + - flink_tmpfile() and then get rid of fopen_temporary(). Benefit: use O_TMPFILE - pervasively, and avoid rename() wherever we can. +- Add knob to cryptsetup, to trigger automatic reboot on failure to unlock + disk. Enable this by default for rootfs, also in gpt-auto-generator -* loginctl: show argv[] of "leader" process in tabular list-sessions output +- add linker script that implicitly adds symbol for build ID and new coredump + json package metadata, and use that when logging -* loginctl: show "service identifier" in tabular list-sessions output, to make - run0 sessions easily visible. +- add new gpt type for btrfs volumes -* run0: maybe enable utmp for run0 sessions, so that they are easily visible. +- add new tool that can be used in debug mode runs in very early boot, + generates a random password, passes it as credential to sysusers for the root + user, then displays it on screen. people can use this to remotely log in. -* maybe beef up sd-event: optionally, allow sd-event to query the timestamp of - next pending datagram inside a SOCK_DGRAM IO fd, and order event source - dispatching by that. Enable this on the native + syslog sockets in journald, - so that we add correct ordering between the two. Use MSG_PEEK + SCM_TIMESTAMP - for this. +- add option to sockets to avoid activation. Instead just drop packets/connections, see http://cyberelk.net/tim/2012/02/15/portreserve-systemd-solution/ -* bsod: add target "bsod.target" or so, which invokes systemd-bsod.target and - waits and then reboots. Then use OnFailure=bsod.target from various jobs that - should result in system reboots, such as TPM tamper detection cases. +- add PR_SET_DUMPABLE service setting -* honour validatefs xattrs in dissect-image.c too +- add proper .osrel matching for PE addons. i.e. refuse applying an addon + intended for a different OS. Take inspiration from how confext/sysext are + matched against OS. -* pcrextend: maybe add option to disable measurements entirely via kernel cmdline +- add proper dbus APIs for the various sd_notify() commands, such as MAINPID=1 + and so on, which would mean we could report errors and such. -* tpm2-setup: reboot if we detect SRK changed +- add service file setting to force the fwmark (a la SO_MARK) to some value, so + that we can allowlist certain services for imds this way. -* validatefs: validate more things: check if image id + os id of initrd match - target mount, so that we refuse early any attempts to boot into different - images with the wrong kernels. check min/max kernel version too. all encoded - via xattrs in the target fs. +- Add service unit setting ConnectStream= which takes IP addresses and connects to them. -* pcrextend: when we fail to measure, reboot the system (at least optionally). - important because certain measurements are supposed to "destroy" tpm object - access. +- add some optional flag to ReadWritePaths= and friends, that has the effect + that we create the dir in question when the service is started. Example: -* pcrextend: after measuring get an immediate quote from the TPM, and validate - it. if it doesn't check out, i.e. the measurement we made doesn't appear in - the PCR then also reboot. + ReadWritePaths=:/var/lib/foobar -* cryptsetup: add boolean for disabling use of any password/recovery key slots. - (i.e. that we can operate in a tpm-only mode, and thus protect us from rogue - root disks) +- add some service that makes an atomic snapshot of PCR state and event log up + to that point available, possibly even with quote by the TPM. -* complete varlink introspection comments: - - io.systemd.BootControl - - io.systemd.Hostname - - io.systemd.ManagedOOM - - io.systemd.Network - - io.systemd.PCRLock - - io.systemd.Resolve.Monitor - - io.systemd.Resolve - - io.systemd.oom - - io.systemd.sysext +- add some special mode to LogsDirectory=/StateDirectory=… that allows + declaring these directories without necessarily pulling in deps for them, or + creating them when starting up. That way, we could declare that + systemd-journald writes to /var/log/journal, which could be useful when we + doing disk usage calculations and so on. -* maybe define a /etc/machine-info field for the ANSI color to associate with a - hostname. Then use it for the shell prompt to highlight the hostname. If no - color is explicitly set, hash a color automatically from the hostname as a - fallback, in a reasonable way. Take inspiration from the ANSI_COLOR= field - that already exists in /etc/os-release, i.e. use the same field name and - syntax. When hashing the color, use the hsv_to_rgb() helper we already have, - fixate S and V to something reasonable and constant, and derive the H from - the hostname. Ultimate goal with this: give people a visual hint about the - system they are on if the have many to deal with, by giving each a color - identity. This code should be placed in hostnamed, so that clients can query - the color via varlink or dbus. +- add support for "portablectl attach http://foobar.com/waaa.raw (i.e. importd integration) -* unify how blockdev_get_root() and sysupdate find the default root block device +- add support for activating nvme-oF devices at boot automatically via kernel + cmdline, and maybe even support a syntax such as + root=nvme:\:\:\:\:\ to boot directly from + nvme-oF -* Maybe rename pkcs7 and public verbs of systemd-keyutil to be more verb like. +- add support for asymmetric LUKS2 TPM based encryption. i.e. allow preparing + an encrypted image on some host given a public key belonging to a specific + other host, so that only hosts possessing the private key in the TPM2 chip + can decrypt the volume key and activate the volume. Use case: systemd-confext + for a central orchestrator to generate confext images securely that can only + be activated on one specific host (which can be used for installing a bunch + of creds in /etc/credstore/ for example). Extending on this: allow binding + LUKS2 TPM based encryption also to the TPM2 internal clock. Net result: + prepare a confext image that can only be activated on a specific host that + runs a specific software in a specific time window. confext would be + automatically invalidated outside of it. -* maybe extend the capsule concept to the per-user instance too: invokes a - systemd --user instance with a subdir of $HOME as $HOME, and a subdir of - $XDG_RUNTIME_DIR as $XDG_RUNTIME_DIR. +- Add support for extra verity configuration options to systemd-repart (FEC, + hash type, etc) -* add "homectl export" and "homectl import" that gets you an "atomic" snapshot - of your homedir, i.e. either a tarball or a snapshot of the underlying disk - (use FREEZE/THAW to make it consistent, btrfs snapshots) +- Add SUPPORT_END_URL= field to os-release with more *actionable* information + what to do if support ended -* maybe introduce a new partition that we can store debug logs and similar at - the very last moment of shutdown. idea would be to store reference to block - device (major + minor + partition id + diskeq?) in /run somewhere, than use - that from systemd-shutdown, just write a raw JSON blob into the partition. - Include timestamp, boot id and such, plus kmsg. on next boot immediately - import into journal. maybe use timestamp for making clock more monotonic. - also use this to detect unclean shutdowns, boot into special target if - detected +- Add systemd-analyze security checks for RestrictFileSystems= and + RestrictNetworkInterfaces= -* fix homed/homectl confusion around terminology, i.e. "home directory" - vs. "home" vs. "home area". Stick to one term for the concept, and it - probably shouldn't contain "area". +- Add systemd-mount@.service which is instantiated for a block device and + invokes systemd-mount and exits. This is then useful to use in + ENV{SYSTEMD_WANTS} in udev rules, and a bit prettier than using RUN+= -* sd-boot: do something useful if we find exactly zero entries (ignoring items - such as reboot/poweroff/factory reset). Show a help text or so. +- Add systemd-sysupdate-initrd.service or so that runs systemd-sysupdate in the + initrd to bootstrap the initrd to populate the initial partitions. Some things + to figure out: + - Should it run on firstboot or on every boot? + - If run on every boot, should it use the sysupdate config from the host on + subsequent boots? -* sd-boot: optionally ask for confirmation before executing certain operations - (e.g. factory resets, storagetm with world access, and so on) +- add systemd.abort_on_kill or some other such flag to send SIGABRT instead of SIGKILL + (throughout the codebase, not only PID1) -* add field to bls type 1 and type 2 profiles that ensures an item is never - considered for automatic selection +- Add UKI profile conditioning so that profiles are only available if secure + boot is turned off, or only on. similar, add conditions on TPM availability, + network boot, and other conditions. -* add "conditions" for bls type 1 and type 2 profiles that allow suppressing - them under various conditions: 1. if tpm2 is available or not available; - 2. if sb is on or off; 3. if we are netbooted or not; … +- Allocate UIDs/GIDs automatically in userdbctl load-credentials if none are + included in the user/group record credentials -* logind: invoke a service manager for "area" logins too. i.e. instantiate - user@.service also for logins where XDG_AREA is set, in per-area fashion, and - ref count it properly. Benefit: graphical logins should start working with - the area logic. +- allow dynamic modifications of ConcurrencyHardMax= and ConcurrencySoftMax= + via DBus (and with that also by daemon-reload). Similar for portabled. -* repart: introduce concept of "ghost" partitions, that we setup in almost all - ways like other partitions, but do not actually register in the actual gpt - table, but only tell the kernel about via BLKPG ioctl. These partitions are - disk backed (hence can be large), but not persistent (as they are invisible - on next boot). Could be used by live media and similar, to boot up as usual - but automatically start at zero on each boot. There should also be a way to - make ghost partitions properly persistent on request. +- also include packaging metadata (á la + https://systemd.io/PACKAGE_METADATA_FOR_EXECUTABLE_FILES/) in our UEFI PE + binaries, using the same JSON format. -* repart: introduce MigrateFileSystem= or so which is a bit like - CopyFiles=/CopyBlocks= but operates via btrfs device logic: adds target as - new device then removes source from btrfs. Usecase: a live medium which uses - "ghost" partitions as suggested above, which can become persistent on request - on another device. - -* make nspawn containers, portable services and vmspawn VMs optionally survive - soft reboot wholesale. - -* Turn systemd-networkd-wait-online into a small varlink service that people - can talk to and specify exactly what to wait for via a method call, and get a - response back once that level of "online" is reached. - -* introduce a small "systemd-installer" tool or so, that glues - systemd-repart-as-installer and bootctl-install into one. Would just - interactively ask user for target disk (with completion and so on), and then do - two varlink calls to the the two tools with the right parameters. To support - "offline" operation, optionally invoke the two tools directly as child - processes with varlink communication over socketpair(). This all should be - useful as blueprint for graphical installers which should do the same. - -* Make run0 forward various signals to the forked process so that sending - signals to a child process works roughly the same regardless of whether the - child process is spawned via run0 or not. - -* write a document explaining how to write correct udev rules. Mention things - such as: - 1. do not do lists of vid/pid matches, use hwdb for that - 2. add|change action matches are typically wrong, should be != remove - 3. use GOTO, make rules short - 4. people shouldn't try to make rules file non-world-readable +- also parse out primary GPT disk label uuid from gpt partition device path at + boot and pass it as efi var to OS. -* make killing more debuggable: when we kill a service do so setting the - .si_code field with a little bit of info. Specifically, we can set a - recognizable value to first of all indicate that it's systemd that did the - killing. Secondly, we can give a reason for the killing, i.e. OOM or so, and - also the phase we are in, and which process we think we are killing (i.e. - main vs control process, useful in case of sd_notify() MAINPID= debugging). - Net result: people who try to debug why their process gets killed should have - some minimal, nice metadata directly on the signal event. +- as soon as we have sender timestamps, revisit coalescing multiple parallel daemon reloads: + https://lists.freedesktop.org/archives/systemd-devel/2014-December/025862.html -* sd-boot/sd-stub: install a uefi "handle" to a sidecar dir of bls type #1 - entries with an "uki" or "uki-url" stanza, and make sd-stub look for - that. That way we can parameterize type #1 entries nicely. +- augment CODE_FILE=, CODE_LINE= with something like CODE_BASE= or so which + contains some identifier for the project, which allows us to include + clickable links to source files generating these log messages. The identifier + could be some abbreviated URL prefix or so (taking inspiration from Go + imports). For example, for systemd we could use + CODE_BASE=github.com/systemd/systemd/blob/98b0b1123cc or so which is + sufficient to build a link by prefixing "http://" and suffixing the + CODE_FILE. -* add a system-wide seccomp filter list for syscalls, kill "acct()" "@obsolete" - and a few other legacy syscalls that way. +- Augment MESSAGE_ID with MESSAGE_BASE, in a similar fashion so that we can + make clickable links from log messages carrying a MESSAGE_ID, that lead to + some explanatory text online. -* maybe introduce "@icky" as a seccomp filter group, which contains acct() and - certain other syscalls that aren't quite obsolete, but certainly icky. +- automatic boot assessment: add one more default success check that just waits + for a bit after boot, and blesses the boot if the system stayed up that long. -* revisit how we pass fs images and initrd to the kernel. take uefi http boot - ramdisks as inspiration: for any confext/sysext/initrd erofs/DDI image simply - generate a fake pmem region in the UEFI memory tables, that Linux then turns - into /dev/pmemX. Then turn of cpio-based initrd logic in linux kernel, - instead let kernel boot directly into /dev/pmem0. In order to allow our usual - cpio-based parameterization, teach PID 1 to just uncompress cpio ourselves - early on, from another pmem device. (Related to this, maybe introduce a new - PE section .ramdisk that just synthesizes pmem devices from arbitrary - blobs. Could be particularly useful in add-ons) +- automatically ignore threaded cgroups in cg_xyz(). -* also parse out primary GPT disk label uuid from gpt partition device path at - boot and pass it as efi var to OS. +- automatically mount one virtiofs during early boot phase to /run/host/, + similar to how we do that for nspawn, based on some clear tag. -* storagetm: maybe also serve the specified disk via HTTP? we have glue for - microhttpd anyway already. Idea would also be serve currently booted UKI as - separate HTTP resource, so that EFI http boot on another system could - directly boot from our system, with full access to the hdd. +- automatically propagate LUKS password credential into cryptsetup from host + (i.e. SMBIOS type #11, …), so that one can unlock LUKS via VM hypervisor + supplied password. -* support specifying download hash sum in systemd-import-generator expression - to pin image/tarball. +- automatically reset specific EFI vars on factory reset (make this generic + enough so that infra can be used to erase shim's mok vars?) -* support boot into nvme-over-tcp: add generator that allows specifying nvme - devices on kernel cmdline + credentials. Also maybe add interactive mode - (where the user is prompted for nvme info), in order to boot from other - system's HDD. +- be able to specify a forced restart of service A where service B depends on, in case B + needs to be auto-respawned? -* ptyfwd: use osc context information in vmspawn/nspawn/… to optionally only - listen to ^]]] key when no further vmspawn/nspawn context is allocated +- be more careful what we export on the bus as (usec_t) 0 and (usec_t) -1 -* ptyfwd: usec osc context information to propagate status messages from - vmspawn/nspawn to service manager's "status" string, reporting what is - currently in the fg +- beef up log.c with support for stripping ANSI sequences from strings, so that + it is OK to include them in log strings. This would be particularly useful so + that our log messages could contain clickable links for example for unit + files and suchlike we operate on. -* nspawn/vmspawn: define hotkey that one can hit on the primary interface to - ask for a friendly, acpi style shutdown. +- beef up pam_systemd to take unit file settings such as cgroups properties as + parameters -* for better compat with major clouds: implement simple PTP device support in - timesyncd +- blog about fd store and restartable services -* for better compat with major clouds: recognize clouds via hwdb on DMI device, - and add udev properties to it that help with handling IMDS, i.e. entrypoint - URL, which fields to find ip hostname, ssh key, … +- **bootctl:** + - recognize the case when not booted on EFI + - add tool for registering BootXXX entry that boots from some http + server of your choice (i.e. like kernel-bootcfg --add-uri=) + - add reboot-to-disk which takes a block device name, and + automatically sets things up so that system reboots into that device next. + - show whether UEFI audit mode is available + - teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation + - teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host -* for better compat with major clouds: introduce imds mini client service that - sets up primary netif in a private netns (ipvlan?) to query imds without - affecting rest of the host. pick up literal credentials from there plus the - fields the hwdb reports for the other fields and turn them into credentials. - then write generator that used detected virtualization info and plugs this - service into the early boot, waiting for the DMI and network device to show - up. +- BootLoaderSpec: define a way how an installer can figure out whether a BLS + compliant boot loader is installed. -* Add UKI profile conditioning so that profles are only available if secure - boot is turned off, or only on. similar, add conditions on TPM availability, - network boot, and other conditions. +- BootLoaderSpec: document @saved pseudo-entry, update mention in BLI -* fix bug around run0 background color on ls in fresh terminal +- bootspec: permit graceful "update" from type #2 to type #1. If both a type #1 + and a type #2 entry exist under otherwise the exact same name, then use the + type #1 entry, and ignore the type #2 entry. This way, people can "upgrade" + from the UKI with all parameters baked in to a Type #1 .conf file with manual + parametrization, if needed. This matches our usual rule that admin config + should win over vendor defaults. -* Reset TPM2 DA bit on each successful boot +- bpf: see if we can address opportunistic inode sharing of immutable fs images + with BPF. i.e. if bpf gives us power to hook into openat() and return a + different inode than is requested for which we however it has same contents + then we can use that to implement opportunistic inode sharing among DDIs: + make all DDIs ship xattr on all reg files with a SHA256 hash. Then, also + dictate that DDIs should come with a top-level subdir where all reg files are + linked into by their SHA256 sum. Then, whenever an inode is opened with the + xattr set, check bpf table to find dirs with hashes for other prior DDIs and + try to use inode from there. -* systemd-repart: add --installer or so, that will intractively ask for a - target disk, maybe ask for confirmation, and install something on disk. Then, - hook that into installer.target or so, so that it can be used to - install/replicate installs +- bpf: see if we can use BPF to solve the syslog message cgroup source problem: + one idea would be to patch source sockaddr of all AF_UNIX/SOCK_DGRAM to + implicitly contain the source cgroup id. Another idea would be to patch + sendto()/connect()/sendmsg() sockaddr on-the-fly to use a different target + sockaddr. -* systemd-cryptenroll: add --firstboot or so, that will interactively ask user - whether recovery key shall be enrolled and do so +- bsod: add target "bsod.target" or so, which invokes systemd-bsod.target and + waits and then reboots. Then use OnFailure=bsod.target from various jobs that + should result in system reboots, such as TPM tamper detection cases. -* bootctl: add tool for registering BootXXX entry that boots from some http - server of your choice (i.e. like kernel-bootcfg --add-uri=) +- bsod: maybe use graphical mode. Use DRM APIs directly, see + https://github.com/dvdhrm/docs/blob/master/drm-howto/modeset.c for an example + for doing that. -* maybe introduce container-shell@.service or so, to match - container-getty.service but skips authentication, so you get a shell prompt - directly. Usecase: wsl-like stuff (they have something pretty much like - that). Question: how to pick user for this. Instance parameter? somehow from - credential (would probably require some binary that converts credential to - User= parameter? +- build short web pages out of each catalog entry, build them along with man + pages, and include hyperlinks to them in the journal output -* systemd-firstboot: optionally install an ssh key for root for offline use. +- busctl: maybe expose a verb "ping" for pinging a dbus service to see if it + exists and responds. -* Allocate UIDs/GIDs automatically in userdbctl load-credentials if none are - included in the user/group record credentials +- bypass SIGTERM state in unit files if KillSignal is SIGKILL -* introduce new ANSI sequence for communicating log level and structured error - metadata to terminals. +- cache sd_event_now() result from before the first iteration... -* in pid1: include ExecStart= cmdlines (and other Exec*= cmdlines) in polkit - request, so that policies can match against command lines. +- calenderspec: add support for week numbers and day numbers within a + year. This would allow us to define "bi-weekly" triggers safely. -* allow dynamic modifications of ConcurrencyHardMax= and ConcurrencySoftMax= - via DBus (and with that also by daemon-reload) +- cgroups: use inotify to get notified when somebody else modifies cgroups + owned by us, then log a friendly warning. -* sysupdated: introduce per-user version that can update per-user installed dDIs +- **cgroups:** + - implement per-slice CPUFairScheduling=1 switch + - introduce high-level settings for RT budget, swappiness + - how to reset dynamically changed unit cgroup attributes sanely? + - when reloading configuration, apply new cgroup configuration + - when recursively showing the cgroup hierarchy, optionally also show + the hierarchies of child processes + - add settings for cgroup.max.descendants and cgroup.max.depth, + maybe use them for user@.service -* portabled: similar +- chase(): take inspiration from path_extract_filename() and return + O_DIRECTORY if input path contains trailing slash. -* resolved: make resolved process DNR DHCP info +- Check that users of inotify's IN_DELETE_SELF flag are using it properly, as + usually IN_ATTRIB is the right way to watch deleted files, as the former only + fires when a file is actually removed from disk, i.e. the link count drops to + zero and is not open anymore, while the latter happens when a file is + unlinked from any dir. -* maybe introduce an OSC sequence that signals when we ask for a password, so - that terminal emulators can maybe connect a password manager or so, and - highlight things specially. +- Clean up "reboot argument" handling, i.e. set it through some IPC service + instead of directly via /run/, so that it can be sensible set remotely. -* start using STATX_SUBVOL in btrfs_is_subvol(). Also, make use of it - generically, so that image discovery recognizes bcachefs subvols too. +- clean up date formatting and parsing so that all absolute/relative timestamps we format can also be parsed -* foreign uid: - - add support to export-fs, import-fs - - systemd-dissect should learn mappings, too, when doing mtree and such +- **complete varlink introspection comments:** + - io.systemd.Hostname + - io.systemd.ManagedOOM + - io.systemd.Network + - io.systemd.PCRLock + - io.systemd.Resolve.Monitor + - io.systemd.Resolve + - io.systemd.oom + - io.systemd.sysext -* resolved: report ttl in resolution replies if we know it. This data is useful - for tools such as wireguard which want to periodically re-resolve DNS names, - and might want to use the TTL has hint for that. +- confext/sysext: instead of mounting the overlayfs directly on /etc/ + /usr/, + insert an intermediary bind mount on itself there. This has the benefit that + services where mount propagation from the root fs is off, an still have + confext/sysext propagated in. -* journald: beef up ClientContext logic to store pidfd_id of peer, to validate - we really use the right cache entry +- consider adding a new partition type, just for /opt/ for usage in system + extensions -* journald: log client's pidfd id as a new automatic field _PIDFDID= or so. +- coredump: maybe when coredumping read a new xattr from /proc/$PID/exe that + may be used to mark a whole binary as non-coredumpable. Would fix: + https://bugs.freedesktop.org/show_bug.cgi?id=69447 -* journald: split up ClientContext cache in two: one cache keyed by pid/pidfdid - with process information, and another one keyed by cgroup path/cgroupid with - cgroup information. This way if a service consisting of many logging - processes can take benefit of the cgroup caching. +- **coredump:** + - save coredump in Windows/Mozilla minidump format + - when truncating coredumps, also log the full size that the process had, and make a metadata field so we can report truncated coredumps + - add examples for other distros in PACKAGE_METADATA_FOR_EXECUTABLE_FILES -* system lsmbpf policy that prohibits creating files owned by "nobody" - system-wide +- **credentials system:** + - acquire from EFI variable? + - acquire via ask-password? + - acquire creds via keyring? + - pass creds via keyring? + - pass creds via memfd? + - acquire + decrypt creds from pkcs11? + - make macsec code in networkd read key via creds logic (copy logic from + wireguard) + - make gatewayd/remote read key via creds logic + - add sd_notify() command for flushing out creds not needed anymore + - if we ever acquire a secure way to derive cgroup id of socket + peers (i.e. SO_PEERCGROUPID), then extend the "scoped" credential logic to + allow cgroup-scoped (i.e. app or service scoped) credentials. Then, as next + step use this to implement per-app/per-service encrypted directories, where + we set up fscrypt on the StateDirectory= with a randomized key which is + stored as xattr on the directory, encrypted as a credential. + - optionally include a per-user secret in scoped user-credential + encryption keys. should come from homed in some way, derived from the luks + volume key or fscrypt directory key. + - add a flag to the scoped credentials that if set require PK + reauthentication when unlocking a secret. + - rework docs. The list in + https://systemd.io/CREDENTIALS/#well-known-credentials is very stale. + Document credentials in individual man pages, generate list as in + systemd.directives. + +- creds: add a new cred format that reused the JSON structures we use in the + LUKS header, so that we get the various newer policies for free. -* system lsmpbf policy that prohibits creating or opening device nodes outside - of devtmpfs/tmpfs, except if they are the pseudo-devices /dev/null, - /dev/zero, /dev/urandom and so on. +- cryptenroll/cryptsetup/homed: add unlock mechanism that combines tpm2 and + fido2, as well as tpm2 + ssh-agent, inspired by ChromeOS' logic: encrypt the + volume key with the TPM, with a policy that insists that a nonce is signed by + the fido2 device's key or ssh-agent key. Thus, add unlock/login time the TPM + generates a nonce, which is sent as a challenge to the fido2/ssh-agent, which + returns a signature which is handed to the tpm, which then reveals the volume + key to the PC. -* system lsmbpf policy that enforces that block device backed mounts may only - be established on top of dm-crypt or dm-verity devices, or an allowlist of - file systems (which should probably include vfat, for compat with the ESP) +- cryptenroll/cryptsetup/homed: similar to this, implement TOTP backed by TPM. -* $SYSTEMD_EXECPID that the service manager sets should - be augmented with $SYSTEMD_EXECPIDFD (and similar for - other env vars we might send). +- cryptsetup/homed: implement TOTP authentication backed by TPM2 and its + internal clock. -* port copy.c over to use LabelOps for all labelling. +- **cryptsetup:** + - cryptsetup-generator: allow specification of passwords in crypttab itself + - support rd.luks.allow-discards= kernel cmdline params in cryptsetup generator + - add boolean for disabling use of any password/recovery key slots. + (i.e. that we can operate in a tpm-only mode, and thus protect us from rogue + root disks) + - new crypttab option to auto-grow a luks device to its backing + partition size. new crypttab option to reencrypt a luks device with a new + volume key. + - a mechanism that allows signing a volume key with some key that + has to be present in the kernel keyring, or similar, to ensure that confext + DDIs can be encrypted against the local SRK but signed with the admin's key + and thus can authenticated locally before they are decrypted. + - add option for automatically removing empty password slot on boot + - optionally, when run during boot-up and password is never + entered, and we are on battery power (or so), power off machine again + - when waiting for FIDO2/PKCS#11 token, tell plymouth that, and + allow plymouth to abort the waiting and enter pw instead + - allow encoding key directly in /etc/crypttab, maybe with a + "base64:" prefix. Useful in particular for pkcs11 mode. + - reimplement the mkswap/mke2fs in cryptsetup-generator to use + systemd-makefs.service instead. + +- sysext: make systemd-{sys,conf}ext-sysroot.service work in the split `/var` + configuration. + +- introduce a concept of /etc/machine-info "TAGS=" field that allows tagging + machines with zero, one or more roles, states or other forms of + categorization. Then, add a way of using this in sysupdate to automatically + enable certain transfers, one for each role. + +- sd-varlink: add fully async modes of the protocol upgrade stuff + +- repart: maybe remove iso9660/eltorito superblock from disk when booting via + gpt, if there is one. + +- crypttab/gpt-auto-generator: allow explicit control over which unlock mechs + to permit, and maybe have a global headless kernel cmdline option + +- currently x-systemd.timeout is lost in the initrd, since crypttab is copied into dracut, but fstab is not + +- dbus: when a unit failed to load (i.e. is in UNIT_ERROR state), we + should be able to safely try another attempt when the bus call LoadUnit() is invoked. -* get rid of compat with libbpf.so.0 (retainly only for libbpf.so.1) +- ddi must be listed as block device fstype -* define a generic "report" varlink interface, which services can implement to +- define a generic "report" varlink interface, which services can implement to provide health/statistics data about themselves. then define a dir somewhere in /run/ where components can bind such sockets. Then make journald, logind, and pid1 itself implement this and expose various stats on things there. Then @@ -624,446 +684,425 @@ Features: quote. tpm quote should protect the json doc via the nonce field studd. Allow shipping this off elsewhere for analyze. -* The bind(AF_UNSPEC) construct (for resetting sockets to their initial state) - should be blocked in many cases because it punches holes in many sandboxes. +- define a JSON format for units, separating out unit definitions from unit + runtime state. Then, expose it: -* introduce new structure Tpm2CombinedPolicy, that combines the various TPm2 - policy bits into one structure, i.e. public key info, pcr masks, pcrlock - stuff, pin and so on. Then pass that around in tpm2_seal() and tpm2_unseal(). + 1. Add Describe() method to Unit D-Bus object that returns a JSON object + about the unit. + 2. Expose this natively via Varlink, in similar style + 3. Use it when invoking binaries (i.e. make PID 1 fork off systemd-executor + binary which reads the JSON definition and runs it), to address the cow + trap issue and the fact that NSS is actually forbidden in + forked-but-not-exec'ed children + 4. Add varlink API to run transient units based on provided JSON definitions -* look at nsresourced, mountfsd, homed, importd, and try to come up with a way - how the forked off worker processes can be moved into transient services with - sandboxing, without breaking notify socket stuff and so on. +- define gpt header bits to select volatility mode -* replace all \x1b, \x1B, \033 C string escape sequences in our codebase with a - more readable \e. It's a GNU extension, but a ton more readable than the - others, and most importantly it doesn't result in confusing errors if you - suffix the escape sequence with one more decimal digit, because compilers - think you might actually specify a value outside the 8bit range with that. +- delay activation of logind until somebody logs in, or when /dev/tty0 pulls it + in or lingering is on (so that containers don't bother with it until PAM is used). also exit-on-idle -* confext/sysext: instead of mounting the overlayfs directly on /etc/ + /usr/, - insert an intermediary bind mount on itself there. This has the benefit that - services where mount propagation from the root fs is off, an still have - confext/sysext propagated in. +- deprecate RootDirectoryStartOnly= in favour of a new ExecStart= prefix char + +- **dhcp6:** + - add functions to set previously stored IPv6 addresses on startup and get + them at shutdown; store them in client->ia_na + - write more test cases + - implement reconfigure support, see 5.3., 15.11. and 22.20. + - implement support for temporary addresses (IA_TA) + - implement dhcpv6 authentication + - investigate the usefulness of Confirm messages; i.e. are there any + situations where the link changes without any loss in carrier detection + or interface down + - some servers don't do rapid commit without a filled in IA_NA, verify + this behavior + - RouteTable= ? + +- **dhcp:** + - figure out how much we can increase Maximum Message Size + +- dissection policy should enforce that unlocking can only take place by + certain means, i.e. only via pw, only via tpm2, or only via fido, or a + combination thereof. -* generic interface for varlink for setting log level and stuff that all our daemons can implement +- do a console daemon that takes stdio fds for services and allows to reconnect + to them later -* maybe teach repart.d/ dropins a new setting MakeMountNodes= or so, which is - just like MakeDirectories=, but uses an access mode of 0000 and sets the +i - chattr bit. This is useful as protection against early uses of /var/ or /tmp/ - before their contents is mounted. +- doc: prep a document explaining PID 1's internal logic, i.e. transactions, + jobs, units -* go through all uses of table_new() in our codebase, and make sure we support - all three of: - 1. --no-legend properly - 2. --json= properly - 3. --no-pager properly +- doc: prep a document explaining resolved's internal objects, i.e. Query + vs. Question vs. Transaction vs. Stream and so on. -* go through all --help texts in our codebases, and make sure: - 1. the one sentence description of the tool is highlighted via ANSI how we - usually do it - 2. If more than one or two commands are supported (as opposed to switches), - separate commands + switches from each other, using underlined --help sections. - 3. If there are many switches, consider adding additional --help sections. +- docs: bring https://systemd.io/MY_SERVICE_CANT_GET_REALTIME up to date -* go through our codebase, and convert "vertical tables" (i.e. things such as - "systemctl status") to use table_new_vertical() for output +- document Environment=SYSTEMD_LOG_LEVEL=debug drop-in in debugging document -* pcrlock: add support for multi-profile UKIs +- document org.freedesktop.MemoryAllocation1 -* initrd: when transitioning from initrd to host, validate that - /lib/modules/`uname -r` exists, refuse otherwise +- **document:** + - document that deps in [Unit] sections ignore Alias= fields in + [Install] units of other units, unless those units are disabled + - document that service reload may be implemented as service reexec + - add a man page containing packaging guidelines and recommending usage of things like Documentation=, PrivateTmp=, PrivateNetwork= and ReadOnlyDirectories=/etc /usr. + - document systemd-journal-flush.service properly + - documentation: recommend to connect the timer units of a service to the service via Also= in [Install] + - man: document the very specific env the shutdown drop-in tools live in + - man: add more examples to man pages, + - in particular an example how to do the equivalent of switching runlevels + - man: maybe sort directives in man pages, and take sections from --help and apply them to man too + - document root=gpt-auto properly -* signed bpf loading: to address need for signature verification for bpf - programs when they are loaded, and given the bpf folks don't think this is - realistic in kernel space, maybe add small daemon that facilitates this - loading on request of clients, validates signatures and then loads the - programs. This daemon should be the only daemon with privs to do load BPF on - the system. It might be a good idea to run this daemon already in the initrd, - and leave it around during the initrd transition, to continue serve requests. - Should then live in its own fs namespace that inherits from the initrd's - fs tree, not from the host, to isolate it properly. Should set - PR_SET_DUMPABLE so that it cannot be ptraced from the host. Should have - CAP_SYS_BPF as only service around. +- dot output for --test showing the 'initial transaction' -* add a mechanism we can drop capabilities from pid1 *before* transitioning - from initrd to host. i.e. before we transition into the slightly lower trust - domain that is the host systems we might want to get rid of some caps. - Example: CAP_SYS_BPF in the signed bpf loading logic above. (We already have - CapabilityBoundingSet= in system.conf, but that is enforced when pid 1 - initializes, rather then when it transitions to the next.) +- drop nss-myhostname in favour of nss-resolve? -* maybe add a new standard slice where process that are started in the initrd - and stick around for the whole system runtime (i.e. root fs storage daemons, - the bpf loader daemon discussed above, and such) are placed. maybe - protected.slice or so? Then write docs that suggest that services like this - set Slice=protected.sice, RefuseManualStart=yes, RefuseManualStop=yes and a - couple of other things. +- drop NV_ORDERLY flag from the product uuid nvpcr. Effect of the flag is that + it pushes the thing into TPM RAM, but a TPM usually has very little of that, + less than NVRAM. hence setting the flag amplifies space issues. Unsetting the + flag increases wear issues on the NVRAM, however, but this should be limited + for the product uuid nvpcr, since its only changed once per boot. this needs + to be configurable by nvpcr however, as other nvpcrs are different, + i.e. verity one receives many writes during system uptime quite + possibly. (also, NV_ORDERLY makes stuff faster, and dropping it costs + possibly up to 100ms supposedly) -* rough proposed implementation design for remote attestation infra: add a tool - that generates a quote of local PCRs and NvPCRs, along with synchronous log - snapshot. use "audit session" logic for that, so that we get read-outs and - signature in one step. Then turn this into a JSON object. Use the "TCG TSS 2.0 - JSON Data Types and Policy Language" format to encode the signature. And CEL - for the measurement log. +- **EFI:** + - honor language efi variables for default language selection (if there are any?) + - honor timezone efi variables for default timezone selection (if there are any?) -* creds: add a new cred format that reused the JSON structures we use in the - LUKS header, so that we get the various newer policies for free. +- enable LockMLOCK to take a percentage value relative to physical memory -* systemd-analyze: port "pcrs" verb to talk directly to TPM device, instead of - using sysfs interface (well, or maybe not, as that would require privileges?) +- Enable RestrictFileSystems= for all our long-running services (similar: + RestrictNetworkInterfaces=) -* pcrextend/tpm2-util: add a concept of "rotation" to event log. i.e. allow - trailing parts of the logs if time or disk space limit is hit. Protect the - boot-time measurements however (i.e. up to some point where things are - settled), since we need those for pcrlock measurements and similar. When - deleting entries for rotation, place an event that declares how many items - have been dropped, and what the hash before and after that. +- encode type1 entries in some UKI section to add additional entries to the + menu. -* use name_to_handle_at() with AT_HANDLE_FID instead of .st_ino (inode - number) for identifying inodes, for example in copy.c when finding hard - links, or loop-util.c for tracking backing files, and other places. +- enumerate virtiofs devices during boot-up in a generator, and synthesize + mounts for rootfs, /usr/, /home/, /srv/ and some others from it, depending on + the "tag". (waits for: https://gitlab.com/virtio-fs/virtiofsd/-/issues/128) -* cryptenroll/cryptsetup/homed: add unlock mechanism that combines tpm2 and - fido2, as well as tpm2 + ssh-agent, inspired by ChromeOS' logic: encrypt the - volume key with the TPM, with a policy that insists that a nonce is signed by - the fido2 device's key or ssh-agent key. Thus, add unlock/login time the TPM - generates a nonce, which is sent as a challenge to the fido2/ssh-agent, which - returns a signature which is handed to the tpm, which then reveals the volume - key to the PC. +- /etc/veritytab: allow that the roothash column can be specified as fs path + including a path to an AF_UNIX path, similar to how we do things with the + keys of /etc/crypttab. That way people can store/provide the roothash + externally and provide to us on demand only. -* cryptenroll/cryptsetup/homed: similar to this, implement TOTP backed by TPM. +- exponential backoff in timesyncd when we cannot reach a server -* expose the handoff timestamp fully via the D-Bus properties that contain - ExecStatus information +- expose MS_NOSYMFOLLOW in various places -* properly serialize the ExecStatus data from all ExecCommand objects - associated with services, sockets, mounts and swaps. Currently, the data is - flushed out on reload, which is quite a limitation. +- expose the handoff timestamp fully via the D-Bus properties that contain + ExecStatus information -* Clean up "reboot argument" handling, i.e. set it through some IPC service - instead of directly via /run/, so that it can be sensible set remotely. +- extend the smbios11 logic for passing credentials so that instead of passing + the credential data literally it can also just reference an AF_VSOCK CID/port + to read them from. This way the data doesn't remain in the SMBIOS blob during + runtime, but only in the credentials fs. -* systemd-tpm2-support: add a some logic that detects if system is in DA - lockout mode, and queries the user for TPM recovery PIN then. +- extend the verity signature partition to permit multiple signatures for the + same root hash, so that people can sign a single image with multiple keys. -* systemd-repart should probably enable btrfs' "temp_fsid" feature for all file - systems it creates, as we have no interest in RAID for repart, and it should - make sure that we can mount them trivially everywhere. +- figure out a nice way how we can let the admin know what child/sibling unit causes cgroup membership for a specific unit -* systemd-nspawn should get the same SSH key support that vmspawn now has. +- Figure out how to do unittests of networkd's state serialization -* move documentation about our common env vars (SYSTEMD_LOG_LEVEL, - SYSTEMD_PAGER, …) into a man page of its own, and just link it from our - various man pages that so far embed the whole list again and again, in an - attempt to reduce clutter and noise a bid. +- Figure out naming of verbs in systemd-analyze: we have (singular) capability, + exit-status, but (plural) filesystems, architectures. -* vmspawn switch default swtpm PCR bank to SHA384-only (away from SHA256), at - least on 64bit archs, simply because SHA384 is typically double the hashing - speed than SHA256 on 64bit archs (since based on 64bit words unlike SHA256 - which uses 32bit words). +- figure out what to do about credentials sealed to PCRs in kexec + soft-reboot + scenarios. Maybe insist sealing is done additionally against some keypair in + the TPM to which access is updated on each boot, for the next, or so? -* In vmspawn/nspawn/machined wait for X_SYSTEMD_UNIT_ACTIVE=ssh-active.target - and X_SYSTEMD_SIGNALS_LEVEL=2 as indication whether/when SSH and the POSIX - signals are available. Similar for D-Bus (but just use sockets.target for - that). Report as property for the machine. +- figure out when we can use the coarse timers -* teach nspawn/machined a new bus call/verb that gets you a - shell in containers that have no sensible pid1, via joining the container, - and invoking a shell directly. Then provide another new bus call/vern that is - somewhat automatic: if we detect that pid1 is running and fully booted up we - provide a proper login shell, otherwise just a joined shell. Then expose that - as primary way into the container. +- Find a solution for SMACK capabilities stuff: + https://lists.freedesktop.org/archives/systemd-devel/2014-December/026188.html -* make vmspawn/nspawn/importd/machined a bit more usable in a WSL-like - fashion. i.e. teach unpriv systemd-vmspawn/systemd-nspawn a reasonable - --bind-user= behaviour that mounts the calling user through into the - machine. Then, ship importd with a small database of well known distro images - along with their pinned signature keys. Then add some minimal glue that binds - this together: downloads a suitable image if not done so yet, starts it in - the bg via vmspawn/nspawn if not done so yet and then requests a shell inside - it for the invoking user. +- fix bug around run0 background color on ls in fresh terminal -* add a new specifier to unit files that figures out the DDI the unit file is - from, tracing through overlayfs, DM, loopback block device. +- Fix DECIMAL_STR_MAX or DECIMAL_STR_WIDTH. One includes a trailing NUL, the + other doesn't. What a disaster. Probably to exclude it. -* importd/importctl - - complete varlink interface - - download images into .v/ dirs +- fix homed/homectl confusion around terminology, i.e. "home directory" + vs. "home" vs. "home area". Stick to one term for the concept, and it + probably shouldn't contain "area". -* in os-release define a field that can be initialized at build time from - SOURCE_DATE_EPOCH (maybe even under that name?). Would then be used to - initialize the timestamp logic of ConditionNeedsUpdate=. +- fix our various hwdb lookup keys to end with ":" again. The original idea was + that hwdb patterns can match arbitrary fields with expressions like + "*:foobar:*", to wildcard match both the start and the end of the string. + This only works safely for later extensions of the string if the strings + always end in a colon. This requires updating our udev rules, as well as + checking if the various hwdb files are fine with that. -* nspawn/vmspawn/pid1: add ability to easily insert fully booted VMs/FOSC into - shell pipelines, i.e. add easy to use switch that turns off console status - output, and generates the right credentials for systemd-run-generator so that - a program is invoked, and its output captured, with correct EOF handling and - exit code propagation +- flush_accept() should look at sockdiag queued sockets count and exit once we + flushed out the specified number of connections. -* Introduce a CGroupRef structure, inspired by PidRef. Should contain cgroup - path, cgroup id, and cgroup fd. Use it to continuously pin all v2 cgroups via - a cgroup_ref field in the CGroupRuntime structure. Eventually switch things - over to do all cgroupfs access only via that structure's fd. +- flush_fd() should probably try to be smart and stop reading once we know that + all further queued data was enqueued after flush_fd() was originally + called. For that, try SIOCINQ if fd refers to stream socket, and look at + timestamps for datagram sockets. -* Get rid of the symlinks in /run/systemd/units/* and exclusively use cgroupfs - xattrs to convey info about invocation ids, logging settings and so on. - support for cgroupfs xattrs in the "trusted." namespace was added in linux - 3.7, i.e. which we don't pretend to support anymore. +- for better compat with major clouds: implement simple PTP device support in + timesyncd -* rewrite bpf-devices in libbpf/C code, rather than home-grown BPF assembly, to - match bpf-restrict-fs, bpf-restrict-ifaces, bpf-socket-bind +- for better compat with major clouds: introduce imds mini client service that + sets up primary netif in a private netns (ipvlan?) to query imds without + affecting rest of the host. pick up literal credentials from there plus the + fields the hwdb reports for the other fields and turn them into credentials. + then write generator that used detected virtualization info and plugs this + service into the early boot, waiting for the DMI and network device to show + up. -* ditto: rewrite bpf-firewall in libbpf/C code +- for better compat with major clouds: recognize clouds via hwdb on DMI device, + and add udev properties to it that help with handling IMDS, i.e. entrypoint + URL, which fields to find ip hostname, ssh key, … -* credentials: if we ever acquire a secure way to derive cgroup id of socket - peers (i.e. SO_PEERCGROUPID), then extend the "scoped" credential logic to - allow cgroup-scoped (i.e. app or service scoped) credentials. Then, as next - step use this to implement per-app/per-service encrypted directories, where - we set up fscrypt on the StateDirectory= with a randomized key which is - stored as xattr on the directory, encrypted as a credential. +- For timer units: add some mechanisms so that timer units that trigger immediately on boot do not have the services + they run added to the initial transaction and thus confuse Type=idle. -* credentials: optionally include a per-user secret in scoped user-credential - encryption keys. should come from homed in some way, derived from the luks - volume key or fscrypt directory key. +- **for vendor-built signed initrds:** + - kernel-install should be able to install encrypted creds automatically for + machine id, root pw, rootfs uuid, resume partition uuid, and place next to + EFI kernel, for sd-stub to pick them up. These creds should be locked to + the TPM, and bind to the right PCR the kernel is measured to. + - kernel-install should be able to pick up initrd sysexts automatically and + place them next to EFI kernel, for sd-stub to pick them up. + - systemd-fstab-generator should look for rootfs device to mount in creds + - systemd-resume-generator should look for resume partition uuid in creds -* credentials: add a flag to the scoped credentials that if set require PK - reauthentication when unlocking a secret. +- **foreign uid:** + - add support to export-fs, import-fs + - systemd-dissect should learn mappings, too, when doing mtree and such -* credentials: rework docs. The list in - https://systemd.io/CREDENTIALS/#well-known-credentials is very stale. - Document credentials in individual man pages, generate list as in - systemd.directives. +- fstab-generator: default to tmpfs-as-root if only usr= is specified on the kernel cmdline -* extend the smbios11 logic for passing credentials so that instead of passing - the credential data literally it can also just reference an AF_VSOCK CID/port - to read them from. This way the data doesn't remain in the SMBIOS blob during - runtime, but only in the credentials fs. +- generator that automatically discovers btrfs subvolumes, identifies their purpose based on some xattr on them. -* machined: optionally track nspawn unix-export/ runtime for each machined, and - then update systemd-ssh-proxy so that it can connect to that. +- generic interface for varlink for setting log level and stuff that all our daemons can implement -* introduce mntid_t, and make it 64bit, as apparently the kernel switched to - 64bit mount ids +- get rid of compat with libbpf.so.0 (retainly only for libbpf.so.1) -* mountfsd/nsresourced - - userdb: maybe allow callers to map one uid to their own uid - - bpflsm: allow writes if resulting UID on disk would be userns' owner UID - - make encrypted DDIs work (password…) - - add API for creating a new file system from scratch (together with some - dm-integrity/HMAC key). Should probably work using systemd-repart (access - via varlink). - - add api to make an existing file "trusted" via dm-integry/HMAC key - - port: portabled - - port: tmpfiles, sysusers and similar - - lets see if we can make runtime bind mounts into unpriv nspawn work +- Get rid of the symlinks in /run/systemd/units/* and exclusively use cgroupfs + xattrs to convey info about invocation ids, logging settings and so on. + support for cgroupfs xattrs in the "trusted." namespace was added in linux + 3.7, i.e. which we don't pretend to support anymore. -* add a kernel cmdline switch (and cred?) for marking a system to be - "headless", in which case we never open /dev/console for reading, only for - writing. This would then mean: systemd-firstboot would process creds but not - ask interactively, getty would not be started and so on. +- go through all --help texts in our codebases, and make sure: + 1. the one sentence description of the tool is highlighted via ANSI how we + usually do it + 2. If more than one or two commands are supported (as opposed to switches), + separate commands + switches from each other, using underlined --help sections. + 3. If there are many switches, consider adding additional --help sections. -* cryptsetup: new crypttab option to auto-grow a luks device to its backing - partition size. new crypttab option to reencrypt a luks device with a new - volume key. +- go through all uses of table_new() in our codebase, and make sure we support + all three of: + 1. --no-legend properly + 2. --json= properly + 3. --no-pager properly -* we probably should have some infrastructure to acquire sysexts with - drivers/firmware for local hardware automatically. Idea: reuse the modalias - logic of the kernel for this: make the main OS image install a hwdb file - that matches against local modalias strings, and adds properties to relevant - devices listing names of sysexts needed to support the hw. Then provide some - tool that goes through all devices and tries to acquire/download the - specified images. +- go through our codebase, and convert "vertical tables" (i.e. things such as + "systemctl status") to use table_new_vertical() for output -* repart + cryptsetup: support file systems that are encrypted and use verity - on top. Usecase: confexts that shall be signed by the admin but also be - confidential. Then, add a new --make-ddi=confext-encrypted for this. +- **gpt-auto-generator:** + - Make /home automount rather than mount? -* tmpfiles: add new line type for moving files from some source dir to some - target dir. then use that to move sysexts/confexts and stuff from initrd - tmpfs to /run/, so that host can pick things up. +- have a signal that reloads every unit that supports reloading -* tiny varlink service that takes a fd passed in and serves it via http. Then - make use of that in networkd, and expose some EFI binary of choice for - DHCP/HTTP base EFI boot. +- hibernate/s2h: if swap is on weird storage and refuse if so -* bootctl: add reboot-to-disk which takes a block device name, and - automatically sets things up so that system reboots into that device next. +- homed/pam_systemd: allow authentication by ssh-agent, so that run0/polkit can + be allowed if caller comes with the right ssh-agent keys. -* maybe: in PID1, when we detect we run in an initrd, make superblock read-only - early on, but provide opt-out via kernel cmdline. +- homed/userdb: maybe define a "companion" dir for home directories where apps + can safely put privileged stuff in. Would not be writable by the user, but + still conceptually belong to the user. Would be included in user's quota if + possible, even if files are not owned by UID of user. Use case: container + images that owned by arbitrary UIDs, and are owned/managed by the users, but + are not directly belonging to the user's UID. Goal: we shouldn't place more + privileged dirs inside of unprivileged dirs, and thus containers really + should not be placed inside of traditional UNIX home dirs (which are owned by + users themselves) but somewhere else, that is separate, but still close + by. Inform user code about path to this companion dir via env var, so that + container managers find it. the ~/.identity file is also a candidate for a + file to move there, since it is managed by privileged code (i.e. homed) and + not unprivileged code. -* systemd-pcrextend: - - once we have that start measuring every sysext we apply, every confext, - every RootImage= we apply, every nspawn and so on. All in separate fake - PCRs. +- **homed:** + - when user tries to log into record signed by unrecognized key, automatically add key to our chain after polkit auth + - rollback when resize fails mid-operation + - GNOME's side for forget key on suspend (requires rework so that lock screen runs outside of uid) + - update LUKS password on login if we find there's a password that unlocks the JSON record but not the LUKS device. + - create on activate? + - properties: icon url?, administrator bool (which translates to 'wheel' membership)?, address?, telephone?, vcard?, samba stuff?, parental controls? + - communicate clearly when usb stick is safe to remove. probably involves + beefing up logind to make pam session close hook synchronous and wait until + systemd --user is shut down. + - logind: maybe keep a "busy fd" as long as there's a non-released session around or the user@.service + - maybe make automatic, read-only, time-based reflink-copies of LUKS disk + images (and btrfs snapshots of subvolumes) (think: time machine) + - distinguish destroy / remove (i.e. currently we can unregister a user, unregister+remove their home directory, but not just remove their home directory) + - fingerprint authentication, pattern authentication, … + - make sure "classic" user records can also be managed by homed + - make size of $XDG_RUNTIME_DIR configurable in user record + - move acct mgmt stuff from pam_systemd_home to pam_systemd? + - when "homectl --pkcs11-token-uri=" is used, synthesize ssh-authorized-keys records for all keys we have private keys on the stick for + - make slice for users configurable (requires logind rework) + - logind: populate auto-login list bus property from PKCS#11 token + - when determining state of a LUKS home directory, check DM suspended sysfs file + - when homed is in use, maybe start the user session manager in a mount namespace with MS_SLAVE, + so that mounts propagate down but not up - eg, user A setting up a backup volume + doesn't mean user B sees it + - use credentials logic/TPM2 logic to store homed signing key + - permit multiple user record signing keys to be used locally, and pick + the right one for signing records automatically depending on a pre-existing + signature + - add a way to "take possession" of a home directory, i.e. strip foreign signatures + and insert a local signature instead. + - as an extension to the directory+subvolume backend: if located on + especially marked fs, then sync down password into LUKS header of that fs, + and always verify passwords against it too. Bootstrapping is a problem + though: if no one is logged in (or no other user even exists yet), how do you + unlock the volume in order to create the first user and add the first pw. + - support new FS_IOC_ADD_ENCRYPTION_KEY ioctl for setting up fscrypt + - maybe pre-create ~/.cache as subvol so that it can have separate quota + easily? + - store PKCS#11 + FIDO2 token info in LUKS2 header, compatible with + systemd-cryptsetup, so that it can unlock homed volumes + - maybe make all *.home files owned by `systemd-home` user or so, so that we + can easily set overall quota for all users + - on login, if we can't fallocate initially, but rebalance is on, then allow + login in discard mode, then immediately rebalance, then turn off discard + - add "homectl unbind" command to remove local user record of an inactive + home dir + - use systemd-storagetm to expose home dirs via nvme-tcp. Then, + teach homed/pam_systemd_homed with a user name such as + lennart%nvme_tcp_192.168.100.77_8787 to log in from any linux host with the + same home dir. Similar maybe for nbd, iscsi? this should then first ask for + the local root pw, to authenticate that logging in like this is ok, and would + then be followed by another password prompt asking for the user's own + password. Also, do something similar for CIFS: if you log in via + lennart%cifs-someserver_someshare, then set up the homed dir for it + automatically. The PAM module should update the user name used for login to + the short version once it set up the user. Some care should be taken, so that + the long version can be still be resolved via NSS afterwards, to deal with + PAM clients that do not support PAM sessions where PAM_USER changes half-way. + - add a basic form of secrets management to homed, that stores + secrets in $HOME somewhere, is protected by the accounts own authentication + mechanisms. Should implement something PKCS#11-like that can be used to + implement emulated FIDO2 in unpriv userspace on top (which should happen + outside of homed), emulated PKCS11, and libsecrets support. Operate with a + 2nd key derived from volume key of the user, with which to wrap all + keys. maintain keys in kernel keyring if possible. + - when resizing an fs don't sync identity beforehand there might simply + not be enough disk space for that. try to be defensive and sync only after + resize. + - if for some reason the partition ended up being much smaller than + whole disk, recover from that, and grow it again. + - if the homed shell fallback thing has access to an SSH agent, try to + use it to unlock home dir (if ssh-agent forwarding is enabled). We + could implement SSH unlocking of a homedir with that: when enrolling a new + ssh pubkey in a user record we'd ask the ssh-agent to sign some random value + with the privkey, then use that as luks key to unlock the home dir. Will not + work for ECDSA keys since their signatures contain a random component, but + will work for RSA and Ed25519 keys. + +- honour validatefs xattrs in dissect-image.c too + +- hook up journald with TPMs? measure new journal records to the TPM in regular + intervals, validate the journal against current TPM state with that. (taking + inspiration from IMA log) -* vmspawn: - - --ephemeral support - - --read-only support - - automatically suspend/resume the VM if the host suspends. Use logind - suspend inhibitor to implement this. request clean suspend by generating - suspend key presses. - - support for "real" networking via "-n" and --network-bridge= - - translate SIGTERM to clean ACPI shutdown event - - implement hotkeys ^]^]r and ^]^]p like nspawn +- Hook up journald's FSS logic with TPM2: seal the verification disk by + time-based policy, so that the verification key can remain on host and ve + validated via TPM. -* storagetm: - - add USB mass storage device logic, so that all local disks are also exposed - as mass storage devices on systems that have a USB controller that can - operate in device mode - - add NVMe authentication +- Hook up systemd-journal-upload with RESTART_RESET=1 logic (should probably + be conditioned on the num of successfully uploaded entries?) -* add support for activating nvme-oF devices at boot automatically via kernel - cmdline, and maybe even support a syntax such as - root=nvme::::: to boot directly from - nvme-oF +- hostnamectl: show root image uuid -* pcrlock: - - add kernel-install plugin that automatically creates UKI .pcrlock file when - UKI is installed, and removes it when it is removed again - - automatically install PE measurement of sd-boot on "bootctl install" - - pre-calc sysext + kernel cmdline measurements - - pre-calc cryptsetup root key measurement - - maybe make systemd-repart generate .pcrlock for old and new GPT header in - /run? - - Add support for more than 8 branches per PCR OR - - add "systemd-pcrlock lock-kernel-current" or so which synthesizes .pcrlock - policy from currently booted kernel/event log, to close gap for first boot - for pre-built images +- if /usr/bin/swapoff fails due to OOM, log a friendly explanatory message about it -* in sd-boot and sd-stub measure the SMBIOS vendor strings to some PCR (at - least some subset of them that look like systemd stuff), because apparently - some firmware does not, but systemd honours it. avoid duplicate measurement - by sd-boot and sd-stub by adding LoaderFeatures/StubFeatures flag for this, - so that sd-stub can avoid it if sd-boot already did it. +- if we fork of a service with StandardOutput=journal, and it forks off a + subprocess that quickly dies, we might not be able to identify the cgroup it + comes from, but we can still derive that from the stdin socket its output + came from. We apparently don't do that right now. -* cryptsetup: a mechanism that allows signing a volume key with some key that - has to be present in the kernel keyring, or similar, to ensure that confext - DDIs can be encrypted against the local SRK but signed with the admin's key - and thus can authenticated locally before they are decrypted. +- If we try to find a unit via a dangling symlink, generate a clean + error. Currently, we just ignore it and read the unit from the search + path anyway. -* image policy should be extended to allow dictating *how* a disk is unlocked, +- image policy should be extended to allow dictating *how* a disk is unlocked, i.e. root=encrypted-tpm2+encrypted-fido2 would mean "root fs must be encrypted and unlocked via fido2 or tpm2, but not otherwise" -* systemd-repart: add support for formatting dm-crypt + dm-integrity file - systems. - -* homed: use systemd-storagetm to expose home dirs via nvme-tcp. Then, - teach homed/pam_systemd_homed with a user name such as - lennart%nvme_tcp_192.168.100.77_8787 to log in from any linux host with the - same home dir. Similar maybe for nbd, iscsi? this should then first ask for - the local root pw, to authenticate that logging in like this is ok, and would - then be followed by another password prompt asking for the user's own - password. Also, do something similar for CIFS: if you log in via - lennart%cifs-someserver_someshare, then set up the homed dir for it - automatically. The PAM module should update the user name used for login to - the short version once it set up the user. Some care should be taken, so that - the long version can be still be resolved via NSS afterwards, to deal with - PAM clients that do not support PAM sessions where PAM_USER changes half-way. - -* redefine /var/lib/extensions/ as the dir one can place all three of sysext, - confext as well is multi-modal DDIs that qualify as both. Then introduce - /var/lib/sysexts/ which can be used to place only DDIs that shall be used as - sysext - -* Varlinkification of the following command line tools, to open them up to - other programs via IPC: - - coredumpcl - - systemd-bless-boot - - systemd-measure - - systemd-cryptenroll (to allow UIs to enroll FIDO2 keys and such) - - systemd-dissect - - systemd-sysupdate - - systemd-analyze - - kernel-install - - systemd-mount (with PK so that desktop environments could use it to mount disks) - -* enumerate virtiofs devices during boot-up in a generator, and synthesize - mounts for rootfs, /usr/, /home/, /srv/ and some others from it, depending on - the "tag". (waits for: https://gitlab.com/virtio-fs/virtiofsd/-/issues/128) - -* automatically mount one virtiofs during early boot phase to /run/host/, - similar to how we do that for nspawn, based on some clear tag. - -* add some service that makes an atomic snapshot of PCR state and event log up - to that point available, possibly even with quote by the TPM. - -* encode type1 entries in some UKI section to add additional entries to the - menu. +- imds: maybe do smarter api version handling -* Add ACL-based access management to .socket units. i.e. add AllowPeerUser= + - AllowPeerGroup= that installs additional user/group ACL entries on AF_UNIX - sockets. +- implement a varlink registry service, similar to the one of the reference + implementation, backed by /run/varlink/registry/. Then, also implement + connect-via-registry-resolution in sd-varlink and varlinkctl. Care needs to + be taken to do the resolution asynchronousy. Also, note that the Varlink + reference implementation uses a different address syntax, which needs to be + taken into account. -* systemd-tpm2-setup should support a mode where we refuse booting if the SRK - changed. (Must be opt-in, to not break systems which are supposed to be - migratable between PCs) +- implement Distribute= in socket units to allow running multiple + service instances processing the listening socket, and open this up + for ReusePort= -* when systemd-sysext learns mutable /usr/ (and systemd-confext mutable /etc/) - then allow them to store the result in a .v/ versioned subdir, for some basic - snapshot logic +- **importd/importctl:** + - complete varlink interface + - download images into .v/ dirs -* add a new PE binary section ".mokkeys" or so which sd-stub will insert into - Mok keyring, by overriding/extending whatever shim sets in the EFI - var. Benefit: we can extend the kernel module keyring at ukify time, - i.e. without recompiling the kernel, taking an upstream OS' kernel and adding - a local key to it. +- importd: support image signature verification with PKCS#7 + OpenBSD signify + logic, as alternative to crummy gpg -* PidRef conversion work: - - cg_pid_get_xyz() - - pid_from_same_root_fs() - - get_ctty_devnr() - - actually wait for POLLIN on pidref's pidfd in service logic - - openpt_allocate_in_namespace() - - unit_attach_pid_to_cgroup_via_bus() - - cg_attach() – requires new kernel feature - - journald's process cache +- In .socket units, add ConnectStream=, ConnectDatagram=, + ConnectSequentialPacket= that create a socket, and then *connect to* rather than + listen on some socket. Then, add a new setting WriteData= that takes some + base64 data that systemd will write into the socket early on. This can then + be used to create connections to arbitrary services and issue requests into + them, as long as the data is static. This can then be combined with the + aforementioned journald subscription varlink service, to enable + activation-by-message id and similar. -* ddi must be listed as block device fstype +- In DynamicUser= mode: before selecting a UID, use disk quota APIs on relevant + disks to see if the UID is already in use. -* measure some string via pcrphase whenever we end up booting into emergency - mode. +- in journald, write out a recognizable log record whenever the system clock is + changed ("stepped"), and in timesyncd whenever we acquire an NTP fix + ("slewing"). Then, in journalctl for each boot time we come across, find + these records, and use the structured info they include to display + "corrected" wallclock time, as calculated from the monotonic timestamp in the + log record, adjusted by the delta declared in the structured log record. -* similar, measure some string via pcrphase whenever we resume from hibernate +- in journald: whenever we start a new journal file because the boot ID + changed, let's generate a recognizable log record containing info about old + and new ID. Then, when displaying log stream in journalctl look for these + records, to be able to order them. -* homed: add a basic form of secrets management to homed, that stores - secrets in $HOME somewhere, is protected by the accounts own authentication - mechanisms. Should implement something PKCS#11-like that can be used to - implement emulated FIDO2 in unpriv userspace on top (which should happen - outside of homed), emulated PKCS11, and libsecrets support. Operate with a - 2nd key derived from volume key of the user, with which to wrap all - keys. maintain keys in kernel keyring if possible. +- in networkd, when matching device types, fix up DEVTYPE rubbish the kernel passes to us -* use sd-event ratelimit feature optionally for journal stream clients that log - too much +- in nss-systemd, if we run inside of RootDirectory= with PrivateUsers= set, + find a way to map the User=/Group= of the service to the right name. This way + a user/group for a service only has to exist on the host for the right + mapping to work. -* systemd-mount should only consider modern file systems when mounting, similar - to systemd-dissect +- in os-release define a field that can be initialized at build time from + SOURCE_DATE_EPOCH (maybe even under that name?). Would then be used to + initialize the timestamp logic of ConditionNeedsUpdate=. -* add another PE section ".fname" or so that encodes the intended filename for - PE file, and validate that when loading add-ons and similar before using - it. This is particularly relevant when we load multiple add-ons and want to - sort them to apply them in a define order. The order should not be under - control of the attacker. +- in pid1: include ExecStart= cmdlines (and other Exec*= cmdlines) in polkit + request, so that policies can match against command lines. -* also include packaging metadata (á la - https://systemd.io/PACKAGE_METADATA_FOR_EXECUTABLE_FILES/) in our UEFI PE - binaries, using the same JSON format. +- in sd-boot and sd-stub measure the SMBIOS vendor strings to some PCR (at + least some subset of them that look like systemd stuff), because apparently + some firmware does not, but systemd honours it. avoid duplicate measurement + by sd-boot and sd-stub by adding LoaderFeatures/StubFeatures flag for this, + so that sd-stub can avoid it if sd-boot already did it. -* make "bootctl install" + "bootctl update" useful for installing shim too. For - that introduce new dir /usr/lib/systemd/efi/extra/ which we copy mostly 1:1 - into the ESP at install time. Then make the logic smart enough so that we - don't overwrite bootx64.efi with our own if the extra tree already contains - one. Also, follow symlinks when copying, so that shim rpm can symlink their - stuff into our dir (which is safe since the target ESP is generally VFAT and - thus does not have symlinks anyway). Later, teach the update logic to look at - the ELF package metadata (which we also should include in all PE files, see - above) for version info in all *.EFI files, and use it to only update if - newer. +- in sd-id128: also parse UUIDs in RFC4122 URN syntax (i.e. chop off urn:uuid: prefix) -* in sd-stub: optionally add support for a new PE section .keyring or so that +- in sd-stub: optionally add support for a new PE section .keyring or so that contains additional certificates to include in the Mok keyring, extending what shim might have placed there. why? let's say I use "ukify" to build + sign my own fedora-based UKIs, and only enroll my personal lennart key via @@ -1072,295 +1111,364 @@ Features: also mean that the key would be in effect whenever I boot an archlinux UKI built the same way, signed with the same lennart key. -* resolved: take possession of some IPv6 ULA address (let's say - fd00:5353:5353:5353:5353:5353:5353:5353), and listen on port 53 on it for the - local stubs, so that we can make the stub available via ipv6 too. +- in the initrd, once the rootfs encryption key has been measured to PCR 15, + derive default machine ID to use from it, and pass it to host PID 1. -* Maybe add SwitchRootEx() as new bus call that takes env vars to set for new - PID 1 as argument. When adding SwitchRootEx() we should maybe also add a - flags param that allows disabling and enabling whether serialization is - requested during switch root. +- in the initrd: derive the default machine ID to pass to the host PID 1 via + $machine_id from the same seed credential. -* introduce a .acpitable section for early ACPI table override +- in the long run: permit a system with /etc/machine-id linked to /dev/null, to + make it lose its identity, i.e. be anonymous. For this we'd have to patch + through the whole tree to make all code deal with the case where no machine + ID is available. -* add proper .osrel matching for PE addons. i.e. refuse applying an addon - intended for a different OS. Take inspiration from how confext/sysext are - matched against OS. +- In vmspawn/nspawn/machined wait for X_SYSTEMD_UNIT_ACTIVE=ssh-active.target + and X_SYSTEMD_SIGNALS_LEVEL=2 as indication whether/when SSH and the POSIX + signals are available. Similar for D-Bus (but just use sockets.target for + that). Report as property for the machine. -* figure out what to do about credentials sealed to PCRs in kexec + soft-reboot - scenarios. Maybe insist sealing is done additionally against some keypair in - the TPM to which access is updated on each boot, for the next, or so? +- initialize the hostname from the fs label of /, if /etc/hostname does not exist? -* logind: when logging in, always take an fd to the home dir, to keep the dir - busy, so that autofs release can never happen. (this is generally a good - idea, and specifically works around the fact the autofs ignores busy by mount - namespaces) +- initrd: when transitioning from initrd to host, validate that + /lib/modules/`uname -r` exists, refuse otherwise -* mount most file systems with a restrictive uidmap. e.g. mount /usr/ with a - uidmap that blocks out anything outside 0…1000 (i.e. system users) and similar. +- instead of going directly for DefineSpace when initializing nvpcrs, check if + they exist first. apparently DefineSpace is broken on some tpms, and also + creates log spam if the nvindex already exists. -* mount the root fs with MS_NOSUID by default, and then mount /usr/ without - both so that suid executables can only be placed there. Do this already in - the initrd. If /usr/ is not split out create a bind mount automatically. +- introduce /etc/boottab or so which lists block devices that bootctl + + kernel-install shall update the ESPs on (and register in EFI BootXYZ + variables), in addition to whatever is currently the booted /usr/. + systemd-sysupdate should also take it into consideration and update the + /usr/ images on all listed devices. -* fix our various hwdb lookup keys to end with ":" again. The original idea was - that hwdb patterns can match arbitrary fields with expressions like - "*:foobar:*", to wildcard match both the start and the end of the string. - This only works safely for later extensions of the string if the strings - always end in a colon. This requires updating our udev rules, as well as - checking if the various hwdb files are fine with that. +- introduce a .acpitable section for early ACPI table override -* mount /tmp/ and /var/tmp with a uidmap applied that blocks out "nobody" user - among other things such as dynamic uid ranges for containers and so on. That - way no one can create files there with these uids and we enforce they are only - used transiently, never persistently. +- Introduce a CGroupRef structure, inspired by PidRef. Should contain cgroup + path, cgroup id, and cgroup fd. Use it to continuously pin all v2 cgroups via + a cgroup_ref field in the CGroupRuntime structure. Eventually switch things + over to do all cgroupfs access only via that structure's fd. -* rework loopback support in fstab: when "loop" option is used, then - instantiate a new systemd-loop@.service for the source path, set the - lo_file_name field for it to something recognizable derived from the fstab - line, and then generate a mount unit for it using a udev generated symlink - based on lo_file_name. +- introduce a new group to own TPM devices -* teach systemd-nspawn the boot assessment logic: hook up vpick's try counters - with success notifications from nspawn payloads. When this is enabled, - automatically support reverting back to older OS version images if newer ones - fail to boot. +- introduce a small "systemd-installer" tool or so, that glues + systemd-repart-as-installer and bootctl-install into one. Would just + interactively ask user for target disk (with completion and so on), and then do + two varlink calls to the the two tools with the right parameters. To support + "offline" operation, optionally invoke the two tools directly as child + processes with varlink communication over socketpair(). This all should be + useful as blueprint for graphical installers which should do the same. -* remove tomoyo support, it's obsolete and unmaintained apparently +- introduce an option (or replacement) for "systemctl show" that outputs all + properties as JSON, similar to busctl's new JSON output. In contrast to that + it should skip the variant type string though. -* In .socket units, add ConnectStream=, ConnectDatagram=, - ConnectSequentialPacket= that create a socket, and then *connect to* rather than - listen on some socket. Then, add a new setting WriteData= that takes some - base64 data that systemd will write into the socket early on. This can then - be used to create connections to arbitrary services and issue requests into - them, as long as the data is static. This can then be combined with the - aforementioned journald subscription varlink service, to enable - activation-by-message id and similar. +- introduce DefaultSlice= or so in system.conf that allows changing where we + place our units by default, i.e. change system.slice to something + else. Similar, ManagerSlice= should exist so that PID1's own scope unit could + be moved somewhere else too. Finally machined and logind should get similar + options so that it is possible to move user session scopes and machines to a + different slice too by default. Use case: people who want to put resources on + the entire system, with the exception of one specific service. See: + https://lists.freedesktop.org/archives/systemd-devel/2018-February/040369.html -* .service with invalid Sockets= starts successfully. +- introduce mntid_t, and make it 64bit, as apparently the kernel switched to + 64bit mount ids -* landlock: lock down RuntimeDirectory= via landlock, so that services lose - ability to write anywhere else below /run/. Similar for - StateDirectory=. Benefit would be clear delegation via unit files: services - get the directories they get, and nothing else even if they wanted to. +- introduce new ANSI sequence for communicating log level and structured error + metadata to terminals. + +- introduce new structure Tpm2CombinedPolicy, that combines the various TPm2 + policy bits into one structure, i.e. public key info, pcr masks, pcrlock + stuff, pin and so on. Then pass that around in tpm2_seal() and tpm2_unseal(). + +- introduce per-unit (i.e. per-slice, per-service) journal log size limits. + +- investigate whether the gnome pty helper should be moved into systemd, to provide cgroup support. + +- **journal:** + - consider introducing implicit _TTY= + _PPID= + _EUID= + _EGID= + _FSUID= + _FSGID= fields + - journald: also get thread ID from client, plus thread name + - journal: when waiting for journal additions in the client always sleep at least 1s or so, in order to minimize wakeups + - add API to close/reopen/get fd for journal client fd in libsystemd-journal. + - fall back to /dev/log based logging in libsystemd-journal, if we cannot log natively? + - declare the local journal protocol stable in the wiki interface chart + - sd-journal: speed up sd_journal_get_data() with transparent hash table in bg + - journald: when dropping msgs due to ratelimit make sure to write + "dropped %u messages" not only when we are about to print the next + message that works, but already after a short timeout + - check if we can make journalctl by default use --follow mode inside of less if called without args? + - maybe add API to send pairs of iovecs via sd_journal_send + - journal: add a setgid "systemd-journal" utility to invoke from libsystemd-journal, which passes fds via STDOUT and does PK access + - journalctl: support negative filtering, i.e. FOOBAR!="waldo", + and !FOOBAR for events without FOOBAR. + - journal: store timestamp of journal_file_set_offline() in the header, + so it is possible to display when the file was last synced. + - journal-send.c, log.c: when the log socket is clogged, and we drop, count this and write a message about this when it gets unclogged again. + - journal: find a way to allow dropping history early, based on priority, other rules + - journal: When used on NFS, check payload hashes + - journald: add kernel cmdline option to disable ratelimiting for debug purposes + - refuse taking lower-case variable names in sd_journal_send() and friends. + - journald: we currently rotate only after MaxUse+MaxFilesize has been reached. + - journal: deal nicely with byte-by-byte copied files, especially regards header + - journal: sanely deal with entries which are larger than the individual file size, but where the components would fit + - Replace utmp, wtmp, btmp, and lastlog completely with journal + - journalctl: instead --after-cursor= maybe have a --cursor=XYZ+1 syntax? + - when a kernel driver logs in a tight loop, we should ratelimit that too. + - journald: optionally, log debug messages to /run but everything else to /var + - journald: when we drop syslog messages because the syslog socket is + full, make sure to write how many messages are lost as first thing + to syslog when it works again. + - journald: allow per-priority and per-service retention times when rotating/vacuuming + - journald: make use of uid-range.h to manage uid ranges to split + journals in. + - journalctl: add the ability to look for the most recent process of a binary. + journalctl /usr/bin/X11 --invocation=-1 + - systemctl: change 'status' to show logs for the last invocation, not a fixed + number of lines + - improve journalctl performance by loading journal files + lazily. Encode just enough information in the file name, so that we + do not have to open it to know that it is not interesting for us, for + the most common operations. + - man: document that corrupted journal files is nothing to act on + - rework journald sigbus stuff to use mutex + - Set RLIMIT_NPROC for systemd-journal-xyz, and all other of our + services that run under their own user ids, and use User= (but only + in a world where userns is ubiquitous since otherwise we cannot + invoke those daemons on the host AND in a container anymore). Also, + if LimitNPROC= is used without User= we should warn and refuse + operation. + - journalctl --verify: don't show files that are currently being + written to as FAIL, but instead show that they are being written to. + - add journalctl -H that talks via ssh to a remote peer and passes through + binary logs data + - add a version of --merge which also merges /var/log/journal/remote + - journalctl: -m should access container journals directly by enumerating + them via machined, and also watch containers coming and going. + Benefit: nspawn --ephemeral would start working nicely with the journal. + - assign MESSAGE_ID to log messages about failed services + - check if loop in decompress_blob_xz() is necessary + - log pidfid as another field, i.e. _PIDFDID= + - beef up ClientContext logic to store pidfd_id of peer, to validate + we really use the right cache entry + - log client's pidfd id as a new automatic field _PIDFDID= or so. + - split up ClientContext cache in two: one cache keyed by pid/pidfdid + with process information, and another one keyed by cgroup path/cgroupid with + cgroup information. This way if a service consisting of many logging + processes can take benefit of the cgroup caching. + - support RFC3164 fully for the incoming syslog transport, see + https://github.com/systemd/systemd/issues/19251#issuecomment-816601955 + - add varlink service that allows subscribing to certain log events, + for example matching by message ID, or log level returns a list of journal + cursors as they happen. + - also collect CLOCK_BOOTTIME timestamps per log entry. Then, derive + "corrected" CLOCK_REALTIME information on display from that and the timestamp + info of the newest entry of the specific boot (as identified by the boot + ID). This way, if a system comes up without a valid clock but acquires a + better clock later, we can "fix" older entry timestamps on display, by + calculating backwards. We cannot use CLOCK_MONOTONIC for this, since it does + not account for suspend phases. This would then also enable us to correct the + kmsg timestamping we consume (where we erroneously assume the clock was in + CLOCK_MONOTONIC, but it actually is CLOCK_BOOTTIME as per kernel). + - generate recognizable log events whenever we shutdown journald + cleanly, and when we migrate run → var. This way tools can verify that a + previous boot terminated cleanly, because either of these two messages must + be safely written to disk, then. + - do journal file writing out-of-process, with one writer process per + client UID, so that synthetic hash table collisions can slow down a specific + user's journal stream down but not the others. + - make sure -f ends when the container indicated by -M terminates + - sigbus API via a signal-handler safe function that people may call + from the SIGBUS handler + +- journalctl/timesyncd: whenever timesyncd acquires a synchronization from NTP, + create a structured log entry that contains boot ID, monotonic clock and + realtime clock (I mean, this requires no special work, as these three fields + are implicit). Then in journalctl when attempting to display the realtime + timestamp of a log entry, first search for the closest later log entry + of this kinda that has a matching boot id, and convert the monotonic clock + timestamp of the entry to the realtime clock using this info. This way we can + retroactively correct the wallclock timestamps, in particular for systems + without RTC, i.e. where initially wallclock timestamps carry rubbish, until + an NTP sync is acquired. -* landlock: for unprivileged systemd (i.e. systemd --user), use landlock to +- landlock: for unprivileged systemd (i.e. systemd --user), use landlock to implement ProtectSystem=, ProtectHome= and so on. Landlock does not require privs, and we can implement pretty similar behaviour. Also, maybe add a mode where ProtectSystem= combined with an explicit PrivateMounts=no could request similar behaviour for system services, too. -* Add systemd-mount@.service which is instantiated for a block device and - invokes systemd-mount and exits. This is then useful to use in - ENV{SYSTEMD_WANTS} in udev rules, and a bit prettier than using RUN+= +- landlock: lock down RuntimeDirectory= via landlock, so that services lose + ability to write anywhere else below /run/. Similar for + StateDirectory=. Benefit would be clear delegation via unit files: services + get the directories they get, and nothing else even if they wanted to. -* udevd: extend memory pressure logic: also kill any idle worker processes +- Lennart: big blog story about "why systemd-boot" -* udevadm: to make symlink querying with udevadm nicer: - - do not enable the pager for queries like 'udevadm info -q symlink -r' - - add mode with newlines instead of spaces (for grep)? +- Lennart: big blog story about building initrds -* SIGRTMIN+18 and memory pressure handling should still be added to: hostnamed, - localed, oomd, timedated. +- Lennart: big blog story about DDIs -* repart/gpt-auto/DDIs: maybe introduce a concept of "extension" partitions, - that have a new type uuid and can "extend" earlier partitions, to work around - the fact that systemd-repart can only grow the last partition defined. During - activation we'd simply set up a dm-linear mapping to merge them again. A - partition that is to be extended would just set a bit in the partition flags - field to indicate that there's another extension partition to look for. The - identifying UUID of the extension partition would be hashed in counter mode - from the uuid of the original partition it extends. Inspiration for this is - the "dynamic partitions" concept of new Android. This would be a minimalistic - concept of a volume manager, with the extents it manages being exposes as GPT - partitions. I a partition is extended multiple times they should probably - grow exponentially in size to ensure O(log(n)) time for finding them on - access. +- let's not GC a unit while its ratelimits are still pending -* Make nspawn to a frontend for systemd-executor, so that we have to ways into - the executor: via unit files/dbus/varlink through PID1 and via cmdline/OCI - through nspawn. +- libsystemd-journal, libsystemd-login, libudev: add calls to easily attach these objects to sd-event event loops -* sd-stub: detect if we are running with uefi console output on serial, and if so - automatically add console= to kernel cmdline matching the same port. +- lock down acceptable encrypted credentials at boot, via simple allowlist, + maybe on kernel command line: + systemd.import_encrypted_creds=foobar.waldo,tmpfiles.extra to protect locked + down kernels from credentials generated on the host with a weak kernel -* add a utility that can be used with the kernel's - CONFIG_STATIC_USERMODEHELPER_PATH and then handles them within pid1 so that - security, resource management and cgroup settings can be enforced properly - for all umh processes. +- lock down swtpm a bit to make it harder to extract keys from it as it is + running. i.e. make ptracing + termination hard from the outside. also run + swtpm as unpriv user (not trivial, probably requires patch swtpm, as it needs + to allocate vtpm device), to lock it down from the inside. -* homed: when resizing an fs don't sync identity beforehand there might simply - not be enough disk space for that. try to be defensive and sync only after - resize. +- loginctl: show "service identifier" in tabular list-sessions output, to make + run0 sessions easily visible. -* homed: if for some reason the partition ended up being much smaller than - whole disk, recover from that, and grow it again. +- loginctl: show argv[] of "leader" process in tabular list-sessions output -* timesyncd: when saving/restoring clock try to take boot time into account. - Specifically, along with the saved clock, store the current boot ID. When - starting, check if the boot id matches. If so, don't do anything (we are on - the same boot and clock just kept running anyway). If not, then read - CLOCK_BOOTTIME (which started at boot), and add it to the saved clock - timestamp, to compensate for the time we spent booting. If EFI timestamps are - available, also include that in the calculation. With this we'll then only - miss the time spent during shutdown after timesync stopped and before the - system actually reset. +- **logind:** + - logind: optionally, ignore idle-hint logic for autosuspend, block suspend as long as a session is around + - logind: wakelock/opportunistic suspend support + - Add pretty name for seats in logind + - logind: allow showing logout dialog from system? + - add Suspend() bus calls which take timestamps to fix double suspend issues when somebody hits suspend and closes laptop quickly. + - if pam_systemd is invoked by su from a process that is outside of a + any session we should probably just become a NOP, since that's + usually not a real user session but just some system code that just + needs setuid(). + - logind: make the Suspend()/Hibernate() bus calls wait for the for + the job to be completed. before returning, so that clients can wait + for "systemctl suspend" to finish to know when the suspending is + complete. + - logind: when the power button is pressed short, just popup a + logout dialog. If it is pressed for 1s, do the usual + shutdown. Inspiration are Macs here. + - expose "Locked" property on logind session objects + - maybe allow configuration of the StopTimeout for session scopes + - rename session scope so that it includes the UID. THat way + the session scope can be arranged freely in slices and we don't have + make assumptions about their slice anymore. + - follow PropertiesChanged state more closely, to deal with quick logouts and + relogins + - (optionally?) spawn seat-manager@$SEAT.service whenever a seat shows up that as CanGraphical set + - invoke a service manager for "area" logins too. i.e. instantiate + user@.service also for logins where XDG_AREA is set, in per-area fashion, and + ref count it properly. Benefit: graphical logins should start working with + the area logic. + - when logging in, always take an fd to the home dir, to keep the dir + busy, so that autofs release can never happen. (this is generally a good + idea, and specifically works around the fact the autofs ignores busy by mount + namespaces) + +- look at nsresourced, mountfsd, homed, importd, portabled, and try to come up + with a way how the forked off worker processes can be moved into transient + services with sandboxing, without breaking notify socket stuff and so on. + +- **machined:** + - add an API so that libvirt-lxc can inform us about network interfaces being + removed or added to an existing machine + - "machinectl migrate" or similar to copy a container from or to a + difference host, via ssh + - introduce systemd-nspawn-ephemeral@.service, and hook it into + "machinectl start" with a new --ephemeral switch + - "machinectl status" should also show internal logs of the container in + question + - "machinectl history" + - "machinectl diff" + - "machinectl commit" that takes a writable snapshot of a tree, invokes a + shell in it, and marks it read-only after use + - gc for OCI layers that are not referenced anymore by any .mstack/ links. + - optionally track nspawn unix-export/ runtime for each machined, and + then update systemd-ssh-proxy so that it can connect to that. -* systemd-stub: maybe store a "boot counter" in the ESP, and pass it down to - userspace to allow ordering boots (for example in journalctl). The counter - would be monotonically increased on every boot. +- make "bootctl install" + "bootctl update" useful for installing shim too. For + that introduce new dir /usr/lib/systemd/efi/extra/ which we copy mostly 1:1 + into the ESP at install time. Then make the logic smart enough so that we + don't overwrite bootx64.efi with our own if the extra tree already contains + one. Also, follow symlinks when copying, so that shim rpm can symlink their + stuff into our dir (which is safe since the target ESP is generally VFAT and + thus does not have symlinks anyway). Later, teach the update logic to look at + the ELF package metadata (which we also should include in all PE files, see + above) for version info in all *.EFI files, and use it to only update if + newer. -* pam_systemd_home: add module parameter to control whether to only accept - only password or only pcks11/fido2 auth, and then use this to hook nicely - into two of the three PAM stacks gdm provides. - See discussion at https://github.com/authselect/authselect/pull/311 +- make cryptsetup lower --iter-time -* maybe prohibit setuid() to the nobody user, to lock things down, via seccomp. - the nobody is not a user any code should run under, ever, as that user would - possibly get a lot of access to resources it really shouldn't be getting - access to due to the userns + nfs semantics of the user. Alternatively: use - the seccomp log action, and allow it. +- Make it possible to set the keymap independently from the font on + the kernel cmdline. Right now setting one resets also the other. -* systemd-gpt-auto-generator: add kernel cmdline option to override block - device to dissect. also support dissecting a regular file. useccase: include - encrypted/verity root fs in UKI. +- make killing more debuggable: when we kill a service do so setting the + .si_code field with a little bit of info. Specifically, we can set a + recognizable value to first of all indicate that it's systemd that did the + killing. Secondly, we can give a reason for the killing, i.e. OOM or so, and + also the phase we are in, and which process we think we are killing (i.e. + main vs control process, useful in case of sd_notify() MAINPID= debugging). + Net result: people who try to debug why their process gets killed should have + some minimal, nice metadata directly on the signal event. -* sd-stub: add ".bootcfg" section for kernel bootconfig data (as per - https://docs.kernel.org/admin-guide/bootconfig.html) +- make MAINPID= message reception checks even stricter: if service uses User=, + then check sending UID and ignore message if it doesn't match the user or + root. -* tpm2: add (optional) support for generating a local signing key from PCR 15 - state. use private key part to sign PCR 7+14 policies. stash signatures for - expected PCR7+14 policies in EFI var. use public key part in disk encryption. - generate new sigs whenever db/dbx/mok/mokx gets updated. that way we can - securely bind against SecureBoot/shim state, without having to renroll - everything on each update (but we still have to generate one sig on each - update, but that should be robust/idempotent). needs rollback protection, as - usual. +- make nspawn containers, portable services and vmspawn VMs optionally survive + soft reboot wholesale. -* Lennart: big blog story about DDIs +- Make nspawn to a frontend for systemd-executor, so that we have to ways into + the executor: via unit files/dbus/varlink through PID1 and via cmdline/OCI + through nspawn. -* Lennart: big blog story about building initrds +- make persistent restarts easier by adding a new setting OpenPersistentFile= + or so, which allows opening one or more files that is "persistent" across + service restarts, hot reboot, cold reboots (depending on configuration): the + files are created empty on first invocation, and on subsequent invocations + the files are reboot. The files would be backed by tmpfs, pmem or /var + depending on desired level of persistency. -* Lennart: big blog story about "why systemd-boot" +- make repeated alt-ctrl-del presses printing a dump -* bpf: see if we can use BPF to solve the syslog message cgroup source problem: - one idea would be to patch source sockaddr of all AF_UNIX/SOCK_DGRAM to - implicitly contain the source cgroup id. Another idea would be to patch - sendto()/connect()/sendmsg() sockaddr on-the-fly to use a different target - sockaddr. +- make rfkill uaccess controllable by default, i.e. steal rule from + gnome-bluetooth and friends -* bpf: see if we can address opportunistic inode sharing of immutable fs images - with BPF. i.e. if bpf gives us power to hook into openat() and return a - different inode than is requested for which we however it has same contents - then we can use that to implement opportunistic inode sharing among DDIs: - make all DDIs ship xattr on all reg files with a SHA256 hash. Then, also - dictate that DDIs should come with a top-level subdir where all reg files are - linked into by their SHA256 sum. Then, whenever an inode is opened with the - xattr set, check bpf table to find dirs with hashes for other prior DDIs and - try to use inode from there. +- Make run0 forward various signals to the forked process so that sending + signals to a child process works roughly the same regardless of whether the + child process is spawned via run0 or not. -* extend the verity signature partition to permit multiple signatures for the - same root hash, so that people can sign a single image with multiple keys. +- make sure systemd-ask-password-wall does not shutdown systemd-ask-password-console too early -* consider adding a new partition type, just for /opt/ for usage in system - extensions +- make sure the ratelimit object can deal with USEC_INFINITY as way to turn off things -* dissection policy should enforce that unlocking can only take place by - certain means, i.e. only via pw, only via tpm2, or only via fido, or a - combination thereof. +- make systemd work nicely without /bin/sh, logins and associated shell tools around + - make sure debug shell service (sushell) has a nice failure mode, prints a message and reboots + - varlink interface for "systemctl start" and friends + - https://github.com/util-linux/util-linux/issues/4117 -* make the systemd-repart "seed" value provisionable via credentials, so that +- make the systemd-repart "seed" value provisionable via credentials, so that confidential computing environments can set it and deterministically enforce the uuids for partitions created, so that they can calculate PCR 15 ahead of time. -* systemd-repart: also derive the volume key from the seed value, for the - aforementioned purpose. - -* in the initrd: derive the default machine ID to pass to the host PID 1 via - $machine_id from the same seed credential. - -* Add systemd-sysupdate-initrd.service or so that runs systemd-sysupdate in the - initrd to bootstrap the initrd to populate the initial partitions. Some things - to figure out: - - Should it run on firstboot or on every boot? - - If run on every boot, should it use the sysupdate config from the host on - subsequent boots? - -* To mimic the new tpm2-measure-pcr= crypttab option and tpm2-measure-nvpcr= - veritytab option, add the same to integritytab (measuring the HMAC key if one - is used) - -* We should start measuring all services, containers, and system extensions we - activate. probably into PCR 13. i.e. add --tpm2-measure-pcr= or so to - systemd-nspawn, and MeasurePCR= to unit files. Should contain a measurement - of the activated configuration and the image that is being activated (in case - verity is used, hash of the root hash). - -* bootspec: permit graceful "update" from type #2 to type #1. If both a type #1 - and a type #2 entry exist under otherwise the exact same name, then use the - type #1 entry, and ignore the type #2 entry. This way, people can "upgrade" - from the UKI with all parameters baked in to a Type #1 .conf file with manual - parametrization, if needed. This matches our usual rule that admin config - should win over vendor defaults. - -* automatic boot assessment: add one more default success check that just waits - for a bit after boot, and blesses the boot if the system stayed up that long. - -* systemd-repart: add support for generating ISO9660 images - -* systemd-repart: in addition to the existing "factory reset" mode (which - simply empties existing partitions marked for that). add a mode where - partitions marked for it are entirely removed. Use case: remove secondary OS - copy, and redundant partitions entirely, and recreate them anew. - -* systemd-boot: maybe add support for collapsing menu entries of the same OS - into one item that can be opened (like in a "tree view" UI element) or - collapsed. If only a single OS is installed, disable this mode, but if - multiple OSes are installed might make sense to default to it, so that user - is not immediately bombarded with a multitude of Linux kernel versions but - only one for each OS. - -* systemd-repart: if the GPT *disk* UUID (i.e. the one global for the entire - disk) is set to all FFFFF then use this as trigger for factory reset, in - addition to the existing mechanisms via EFI variables and kernel command - line. Benefit: works also on non-EFI systems, and can be requested on one - boot, for the next. - -* systemd-sysupdate: make transport pluggable, so people can plug casync or - similar behind it, instead of http. +- make use of ethtool veth peer info in machined, for automatically finding out + host-side interface pointing to the container. -* systemd-tmpfiles: add concept for conditionalizing lines on factory reset - boot, or on first boot. +- make use of new glibc 2.32 APIs sigabbrev_np(). -* we probably needs .pcrpkeyrd or so as additional PE section in UKIs, - which contains a separate public key for PCR values that only apply in the - initrd, i.e. in the boot phase "enter-initrd". Then, consumers in userspace - can easily bind resources to just the initrd. Similar, maybe one more for - "enter-initrd:leave-initrd" for resources that shall be accessible only - before unprivileged user code is allowed. (we only need this for .pcrpkey, - not for .pcrsig, since the latter is a list of signatures anyway). With that, - when you enroll a LUKS volume or similar, pick either the .pcrkey (for - coverage through all phases of the boot, but excluding shutdown), the - .pcrpkeyrd (for coverage in the initrd only) and .pcrpkeybt (for coverage - until users are allowed to log in). +- make vmspawn/nspawn/importd/machined a bit more usable in a WSL-like + fashion. i.e. teach unpriv systemd-vmspawn/systemd-nspawn a reasonable + --bind-user= behaviour that mounts the calling user through into the + machine. Then, ship importd with a small database of well known distro images + along with their pinned signature keys. Then add some minimal glue that binds + this together: downloads a suitable image if not done so yet, starts it in + the bg via vmspawn/nspawn if not done so yet and then requests a shell inside + it for the invoking user. -* Once the root fs LUKS volume key is measured into PCR 15, default to binding - credentials to PCR 15 in "systemd-creds" +- man: rework os-release(5), and clearly separate our extension-release.d/ and + initrd-release parts, i.e. list explicitly which fields are about what. -* add support for asymmetric LUKS2 TPM based encryption. i.e. allow preparing - an encrypted image on some host given a public key belonging to a specific - other host, so that only hosts possessing the private key in the TPM2 chip - can decrypt the volume key and activate the volume. Use case: systemd-confext - for a central orchestrator to generate confext images securely that can only - be activated on one specific host (which can be used for installing a bunch - of creds in /etc/credstore/ for example). Extending on this: allow binding - LUKS2 TPM based encryption also to the TPM2 internal clock. Net result: - prepare a confext image that can only be activated on a specific host that - runs a specific software in a specific time window. confext would be - automatically invalidated outside of it. +- man: the documentation of Restart= currently is very misleading and suggests the tools from ExecStartPre= might get restarted. -* maybe add a "systemd-report" tool, that generates a TPM2-backed "report" of +- maybe add a "systemd-report" tool, that generates a TPM2-backed "report" of current system state, i.e. a combination of PCR information, local system time and TPM clock, running services, recent high-priority log messages/coredumps, system load/PSI, signed by the local TPM chip, to form an @@ -1382,909 +1490,1223 @@ Features: and via the time window TPM logic invalidated if node doesn't keep itself updated, or becomes corrupted in some way. -* in the initrd, once the rootfs encryption key has been measured to PCR 15, - derive default machine ID to use from it, and pass it to host PID 1. - -* sd-boot: for each installed OS, grey out older entries (i.e. all but the - newest), to indicate they are obsolete +- maybe add a new standard slice where process that are started in the initrd + and stick around for the whole system runtime (i.e. root fs storage daemons, + the bpf loader daemon discussed above, and such) are placed. maybe + protected.slice or so? Then write docs that suggest that services like this + set Slice=protected.sice, RefuseManualStart=yes, RefuseManualStop=yes and a + couple of other things. -* automatically propagate LUKS password credential into cryptsetup from host - (i.e. SMBIOS type #11, …), so that one can unlock LUKS via VM hypervisor - supplied password. +- maybe add call sd_journal_set_block_timeout() or so to set SO_SNDTIMEO for + the sd-journal logging socket, and, if the timeout is set to 0, sets + O_NONBLOCK on it. That way people can control if and when to block for + logging. -* add ability to path_is_valid() to classify paths that refer to a dir from - those which may refer to anything, and use that in various places to filter - early. i.e. stuff ending in "/", "/." and "/.." definitely refers to a - directory, and paths ending that way can be refused early in many contexts. +- maybe add kernel cmdline params: to force random seed crediting -* systemd-measure: add --pcrpkey-auto as an alternative to --pcrpkey=, where it - would just use the same public key specified with --public-key= (or the one - automatically derived from --private-key=). +- maybe add new flags to gpt partition tables for rootfs and usrfs indicating + purpose, i.e. whether something is supposed to be bootable in a VM, on + baremetal, on an nspawn-style container, if it is a portable service image, + or a sysext for initrd, for host os, or for portable container. Then hook + portabled/… up to udev to watch block devices coming up with the flags set, and + use it. -* Add "purpose" flag to partition flags in discoverable partition spec that - indicate if partition is intended for sysext, for portable service, for - booting and so on. Then, when dissecting DDI allow specifying a purpose to - use as additional search condition. Use case: images that combined a sysext - partition with a portable service partition in one. +- maybe add support for binding and connecting AF_UNIX sockets in the file + system outside of the 108ch limit. When connecting, open O_PATH fd to socket + inode first, then connect to /proc/self/fd/XYZ. When binding, create symlink + to target dir in /tmp, and bind through it. -* On boot, auto-generate an asymmetric key pair from the TPM, - and use it for validating DDIs and credentials. Maybe upload it to the kernel - keyring, so that the kernel does this validation for us for verity and kernel - modules +- Maybe add SwitchRootEx() as new bus call that takes env vars to set for new + PID 1 as argument. When adding SwitchRootEx() we should maybe also add a + flags param that allows disabling and enabling whether serialization is + requested during switch root. -* lock down acceptable encrypted credentials at boot, via simple allowlist, - maybe on kernel command line: - systemd.import_encrypted_creds=foobar.waldo,tmpfiles.extra to protect locked - down kernels from credentials generated on the host with a weak kernel +- maybe allow timer units with an empty Units= setting, so that they + can be used for resuming the system but nothing else. -* Merge systemd-creds options --uid= (which accepts user names) and --user. +- maybe beef up sd-event: optionally, allow sd-event to query the timestamp of + next pending datagram inside a SOCK_DGRAM IO fd, and order event source + dispatching by that. Enable this on the native + syslog sockets in journald, + so that we add correct ordering between the two. Use MSG_PEEK + SCM_TIMESTAMP + for this. -* Add support for extra verity configuration options to systemd-repart (FEC, - hash type, etc) +- maybe define a /etc/machine-info field for the ANSI color to associate with a + hostname. Then use it for the shell prompt to highlight the hostname. If no + color is explicitly set, hash a color automatically from the hostname as a + fallback, in a reasonable way. Take inspiration from the ANSI_COLOR= field + that already exists in /etc/os-release, i.e. use the same field name and + syntax. When hashing the color, use the hsv_to_rgb() helper we already have, + fixate S and V to something reasonable and constant, and derive the H from + the hostname. Ultimate goal with this: give people a visual hint about the + system they are on if the have many to deal with, by giving each a color + identity. This code should be placed in hostnamed, so that clients can query + the color via varlink or dbus. -* chase(): take inspiration from path_extract_filename() and return - O_DIRECTORY if input path contains trailing slash. +- maybe do not install getty@tty1.service symlink in /etc but in /usr? -* measure credentials picked up from SMBIOS to some suitable PCR +- maybe extend .path units to expose fanotify() per-mount change events -* measure GPT and LUKS headers somewhere when we use them (i.e. in - systemd-gpt-auto-generator/systemd-repart and in systemd-cryptsetup?) +- maybe extend the capsule concept to the per-user instance too: invokes a + systemd --user instance with a subdir of $HOME as $HOME, and a subdir of + $XDG_RUNTIME_DIR as $XDG_RUNTIME_DIR. -* pick up creds from EFI vars +- Maybe extend the service protocol to support handling of some specific SIGRT + signal for setting service log level, that carries the level via the + sigqueue() data parameter. Enable this via unit file setting. -* Add and pickup tpm2 metadata for creds structure. +- maybe implicitly attach monotonic+realtime timestamps to outgoing messages in + log.c and sd-journal-send -* sd-boot: we probably should include all BootXY EFI variable defined boot - entries in our menu, and then suppress ourselves. Benefit: instant - compatibility with all other OSes which register things there, in particular - on other disks. Always boot into them via NextBoot EFI variable, to not - affect PCR values. +- maybe introduce "@icky" as a seccomp filter group, which contains acct() and + certain other syscalls that aren't quite obsolete, but certainly icky. -* systemd-measure tool: - - pre-calculate PCR 12 (command line) + PCR 13 (sysext) the same way we can precalculate PCR 11 +- Maybe introduce a helper safe_exec() or so, which is to execve() which + safe_fork() is to fork(). And then make revert the RLIMIT_NOFILE soft limit + to 1K implicitly, unless explicitly opted-out. -* sd-device: add an API for acquiring list of child devices, given a device - objects (i.e. all child dirents that dirs or symlinks to dirs) +- maybe introduce a new partition that we can store debug logs and similar at + the very last moment of shutdown. idea would be to store reference to block + device (major + minor + partition id + diskseq?) in /run somewhere, than use + that from systemd-shutdown, just write a raw JSON blob into the partition. + Include timestamp, boot id and such, plus kmsg. on next boot immediately + import into journal. maybe use timestamp for making clock more monotonic. + also use this to detect unclean shutdowns, boot into special target if + detected -* sd-device: maybe pin the sysfs dir with an fd, during the entire runtime of - an sd_device, then always work based on that. +- maybe introduce a new per-unit drop-in directory .confext.d/ that may contain + symlinks to confext images to enable for the unit. -* maybe add new flags to gpt partition tables for rootfs and usrfs indicating - purpose, i.e. whether something is supposed to be bootable in a VM, on - baremetal, on an nspawn-style container, if it is a portable service image, - or a sysext for initrd, for host os, or for portable container. Then hook - portabled/… up to udev to watch block devices coming up with the flags set, and - use it. +- Maybe introduce an InodeRef structure inspired by PidRef, which references a + specific inode, and combines: a path, an O_PATH fd, and possibly a FID into + one. Why? We often pass around path and fd separately in chaseat() and similar + calls. Because passing around both separately is cumbersome we sometimes only + one pass one, once the other and sometimes both. It would make the code a lot + simpler if we could path both around at the same time in a simple way, via an + InodeRef which *both* pins the inode via an fd, *and* gives us a friendly + name for it. -* sd-boot should look for information what to boot in SMBIOS, too, so that VM - managers can tell sd-boot what to boot into and suchlike +- maybe introduce an OSC sequence that signals when we ask for a password, so + that terminal emulators can maybe connect a password manager or so, and + highlight things specially. -* add "systemd-sysext identify" verb, that you can point on any file in /usr/ - and that determines from which overlayfs layer it originates, which image, and with - what it was signed. +- maybe introduce container-shell@.service or so, to match + container-getty.service but skips authentication, so you get a shell prompt + directly. Usecase: wsl-like stuff (they have something pretty much like + that). Question: how to pick user for this. Instance parameter? somehow from + credential (would probably require some binary that converts credential to + User= parameter? -* systemd-creds: extend encryption logic to support asymmetric - encryption/authentication. Idea: add new verb "systemd-creds public-key" - which generates a priv/pub key pair on the TPM2 and stores the priv key - locally in /var. It then outputs a certificate for the pub part to stdout. - This can then be copied/taken elsewhere, and can be used for encrypting creds - that only the host on its specific hw can decrypt. Then, support a drop-in - dir with certificates that can be used to authenticate credentials. Flow of - operations is then this: build image with owner certificate, then after - boot up issue "systemd-creds public-key" to acquire pubkey of the machine. - Then, when passing data to the machine, sign with privkey belonging to one of - the dropped in certs and encrypted with machine pubkey, and pass to machine. - Machine is then able to authenticate you, and confidentiality is guaranteed. +- maybe introduce xattrs that can be set on the root dir of the root fs + partition that declare the volatility mode to use the image in. Previously I + thought marking this via GPT partition flags but that's not ideal since + that's outside of the LUKS encryption/verity verification, and we probably + shouldn't operate in a volatile mode unless we got told so from a trusted + source. -* building on top of the above, the pub/priv key pair generated on the TPM2 - should probably also one you can use to get a remote attestation quote. +- maybe prohibit setuid() to the nobody user, to lock things down, via seccomp. + the nobody is not a user any code should run under, ever, as that user would + possibly get a lot of access to resources it really shouldn't be getting + access to due to the userns + nfs semantics of the user. Alternatively: use + the seccomp log action, and allow it. -* Process credentials in: - • crypttab-generator: allow defining additional crypttab-like volumes via - credentials (similar: verity-generator, integrity-generator). Use - fstab-generator logic as inspiration. - • run-generator: allow defining additional commands to run via a credential - • resolved: allow defining additional /etc/hosts entries via a credential (it - might make sense to then synthesize a new combined /etc/hosts file in /run - and bind mount it on /etc/hosts for other clients that want to read it. - • repart: allow defining additional partitions via credential - • timesyncd: pick NTP server info from credential - • portabled: read a credential "portable.extra" or so, that takes a list of - file system paths to enable on start. - • make systemd-fstab-generator look for a system credential encoding root= or - usr= - • in gpt-auto-generator: check partition uuids against such uuids supplied via - sd-stub credentials. That way, we can support parallel OS installations with - pre-built kernels. +- maybe reconsider whether virtualization consoles (hvc1) are considered local + or remote. i.e. are they more like an ssh login, or more like a /dev/tty1 + login? Lennart used to believe the former, but maybe the latter is more + appropriate? This has effect on polkit interactivity, since it would mean + questions via hvc0 would suddenly use the local polkit property. But this + also raises the question whether such sessions shall be considered active or + not -* define a JSON format for units, separating out unit definitions from unit - runtime state. Then, expose it: +- Maybe rename pkcs7 and public verbs of systemd-keyutil to be more verb like. - 1. Add Describe() method to Unit D-Bus object that returns a JSON object - about the unit. - 2. Expose this natively via Varlink, in similar style - 3. Use it when invoking binaries (i.e. make PID 1 fork off systemd-executor - binary which reads the JSON definition and runs it), to address the cow - trap issue and the fact that NSS is actually forbidden in - forked-but-not-exec'ed children - 4. Add varlink API to run transient units based on provided JSON definitions +- maybe rework systemd-modules-load to be a generator that just instantiates + modprobe@.service a bunch of times -* Add SUPPORT_END_URL= field to os-release with more *actionable* information - what to do if support ended +- maybe teach repart.d/ dropins a new setting MakeMountNodes= or so, which is + just like MakeDirectories=, but uses an access mode of 0000 and sets the +i + chattr bit. This is useful as protection against early uses of /var/ or /tmp/ + before their contents is mounted. -* pam_systemd: on interactive logins, maybe show SUPPORT_END information at - login time, à la motd +- maybe trigger a uevent "change" on a device if "systemctl reload xyz.device" + is issued. -* sd-boot: instead of unconditionally deriving the ESP to search boot loader - spec entries in from the paths of sd-boot binary, let's optionally allow it - to be configured on sd-boot cmdline + efi var. Use case: embed sd-boot in the - UEFI firmware (for example, ovmf supports that via qemu cmdline option), and - use it to load stuff from the ESP. +- maybe: in PID1, when we detect we run in an initrd, make superblock read-only + early on, but provide opt-out via kernel cmdline. -* mount /var/ from initrd, so that we can apply sysext and stuff before the - initrd transition. Specifically: - 1. There should be a var= kernel cmdline option, matching root= and usr= - 2. systemd-gpt-auto-generator should auto-mount /var if it finds it on disk - 3. mount.x-initrd mount option in fstab should be implied for /var +- measure all log-in attempts into a new nvpcr -* make persistent restarts easier by adding a new setting OpenPersistentFile= - or so, which allows opening one or more files that is "persistent" across - service restarts, hot reboot, cold reboots (depending on configuration): the - files are created empty on first invocation, and on subsequent invocations - the files are reboot. The files would be backed by tmpfs, pmem or /var - depending on desired level of persistency. +- measure credentials picked up from SMBIOS to some suitable PCR -* sd-event: add ability to "chain" event sources. Specifically, add a call - sd_event_source_chain(x, y), which will automatically enable event source y - in oneshot mode once x is triggered. Use case: in src/core/mount.c implement - the /proc/self/mountinfo rescan on SIGCHLD with this: whenever a SIGCHLD is - seen, trigger the rescan defer event source automatically, and allow it to be - dispatched *before* the SIGCHLD is handled (based on priorities). Benefit: - dispatch order is strictly controlled by priorities again. (next step: chain - event sources to the ratelimit being over) +- measure GPT and LUKS headers somewhere when we use them (i.e. in + systemd-gpt-auto-generator/systemd-repart and in systemd-cryptsetup?) -* if we fork of a service with StandardOutput=journal, and it forks off a - subprocess that quickly dies, we might not be able to identify the cgroup it - comes from, but we can still derive that from the stdin socket its output - came from. We apparently don't do that right now. +- measure some string via pcrphase whenever we end up booting into emergency + mode. -* add PR_SET_DUMPABLE service setting +- measure some string via pcrphase whenever we resume from hibernate -* homed/userdb: maybe define a "companion" dir for home directories where apps - can safely put privileged stuff in. Would not be writable by the user, but - still conceptually belong to the user. Would be included in user's quota if - possible, even if files are not owned by UID of user. Use case: container - images that owned by arbitrary UIDs, and are owned/managed by the users, but - are not directly belonging to the user's UID. Goal: we shouldn't place more - privileged dirs inside of unprivileged dirs, and thus containers really - should not be placed inside of traditional UNIX home dirs (which are owned by - users themselves) but somewhere else, that is separate, but still close - by. Inform user code about path to this companion dir via env var, so that - container managers find it. the ~/.identity file is also a candidate for a - file to move there, since it is managed by privileged code (i.e. homed) and - not unprivileged code. +- Merge systemd-creds options --uid= (which accepts user names) and --user. -* maybe add support for binding and connecting AF_UNIX sockets in the file - system outside of the 108ch limit. When connecting, open O_PATH fd to socket - inode first, then connect to /proc/self/fd/XYZ. When binding, create symlink - to target dir in /tmp, and bind through it. +- merge unit_kill_common() and unit_kill_context() -* add a proper concept of a "developer" mode, i.e. where cryptographic - protections of the root OS are weakened after interactive confirmation, to - allow hackers to allow their own stuff. idea: allow entering developer mode - only via explicit choice in boot menu: i.e. add explicit boot menu item for - it. When developer mode is entered, generate a key pair in the TPM2, and add - the public part of it automatically to keychain of valid code signature keys - on subsequent boots. Then provide a tool to sign code with the key in the - TPM2. Ensure that boot menu item is the only way to enter developer mode, by - binding it to locality/PCRs so that keys cannot be generated otherwise. +- MessageQueueMessageSize= (and suchlike) should use parse_iec_size(). -* services: add support for cryptographically unlocking per-service directories - via TPM2. Specifically, for StateDirectory= (and related dirs) use fscrypt to - set up the directory so that it can only be accessed if host and app are in - order. +- mount /tmp/ and /var/tmp with a uidmap applied that blocks out "nobody" user + among other things such as dynamic uid ranges for containers and so on. That + way no one can create files there with these uids and we enforce they are only + used transiently, never persistently. -* update HACKING.md to suggest developing systemd with the ideas from: - https://0pointer.net/blog/testing-my-system-code-in-usr-without-modifying-usr.html - https://0pointer.net/blog/running-an-container-off-the-host-usr.html +- mount /var/ from initrd, so that we can apply sysext and stuff before the + initrd transition. Specifically: + 1. There should be a var= kernel cmdline option, matching root= and usr= + 2. systemd-gpt-auto-generator should auto-mount /var if it finds it on disk + 3. mount.x-initrd mount option in fstab should be implied for /var -* sd-event: compat wd reuse in inotify code: keep a set of removed watch - descriptors, and clear this set piecemeal when we see the IN_IGNORED event - for it, or when read() returns EAGAIN or on IN_Q_OVERFLOW. Then, whenever we - see an inotify wd event check against this set, and if it is contained ignore - the event. (to be fully correct this would have to count the occurrences, in - case the same wd is reused multiple times before we start processing - IN_IGNORED again) +- mount most file systems with a restrictive uidmap. e.g. mount /usr/ with a + uidmap that blocks out anything outside 0…1000 (i.e. system users) and similar. -* for vendor-built signed initrds: - - kernel-install should be able to install encrypted creds automatically for - machine id, root pw, rootfs uuid, resume partition uuid, and place next to - EFI kernel, for sd-stub to pick them up. These creds should be locked to - the TPM, and bind to the right PCR the kernel is measured to. - - kernel-install should be able to pick up initrd sysexts automatically and - place them next to EFI kernel, for sd-stub to pick them up. - - systemd-fstab-generator should look for rootfs device to mount in creds - - systemd-resume-generator should look for resume partition uuid in creds +- mount the root fs with MS_NOSUID by default, and then mount /usr/ without + both so that suid executables can only be placed there. Do this already in + the initrd. If /usr/ is not split out create a bind mount automatically. -* Maybe extend the service protocol to support handling of some specific SIGRT - signal for setting service log level, that carries the level via the - sigqueue() data parameter. Enable this via unit file setting. +- mount: turn dependency information from /proc/self/mountinfo into dependency information between systemd units. -* sd_notify/vsock: maybe support binding to AF_VSOCK in Type=notify services, - then passing $NOTIFY_SOCKET and $NOTIFY_GUESTCID with PID1's cid (typically - fixed to "2", i.e. the official host cid) and the expected guest cid, for the - two sides of the channel. The latter env var could then be used in an - appropriate qemu cmdline. That way qemu payloads could talk sd_notify() - directly to host service manager. +- MountFlags=shared acts as MountFlags=slave right now. -* sd-device should return the devnum type (i.e. 'b' or 'c') via some API for an - sd_device object, so that data passed into sd_device_new_from_devnum() can - also be queried. +- **mountfsd/nsresourced:** + - userdb: maybe allow callers to map one uid to their own uid + - bpflsm: allow writes if resulting UID on disk would be userns' owner UID + - make encrypted DDIs work (password…) + - add API for creating a new file system from scratch (together with some + dm-integrity/HMAC key). Should probably work using systemd-repart (access + via varlink). + - add api to make an existing file "trusted" via dm-integry/HMAC key + - port: portabled + - port: tmpfiles, sysusers and similar + - lets see if we can make runtime bind mounts into unpriv nspawn work -* sd-event: optionally, if per-event source rate limit is hit, downgrade - priority, but leave enabled, and once ratelimit window is over, upgrade - priority again. That way we can combat event source starvation without - stopping processing events from one source entirely. +- move documentation about our common env vars (SYSTEMD_LOG_LEVEL, + SYSTEMD_PAGER, …) into a man page of its own, and just link it from our + various man pages that so far embed the whole list again and again, in an + attempt to reduce clutter and noise a bid. -* sd-event: similar to existing inotify support add fanotify support (given - that apparently new features in this area are only going to be added to the - latter). +- move multiseat vid/pid matches from logind udev rule to hwdb -* sd-event: add 1st class event source for clock changes +- Move RestrictAddressFamily= to the new cgroup create socket -* sd-event: add 1st class event source for timezone changes +- networkd's resolved hook: optionally map all lease IP addresses handed out to + the same hostname which is configured on the .network file. Optionally, even + derive this single name from the network interface name (i.e. probably + altname or so). This way, when spawning a VM the host could pick the hostname + for it and the client gets no say. -* sysext: measure all activated sysext into a TPM PCR +- networkd/machined: implement reverse name lookups in the resolved hook -* systemd-dissect: show available versions inside of a disk image, i.e. if - multiple versions are around of the same resource, show which ones. (in other - words: show partition labels). +- networkd: maintain a file in /run/ that can be symlinked into /run/issue.d/ + that always shows the current primary IP address -* systemd-dissect: add --cat switch for dumping files such as /etc/os-release +- **networkd:** + - add more keys to [Route] and [Address] sections + - add support for more DHCPv4 options (and, longer term, other kinds of dynamic config) + - add reduced [Link] support to .network files + - properly handle routerless dhcp leases + - work with non-Ethernet devices + - dhcp: do we allow configuring dhcp routes on interfaces that are not the one we got the dhcp info from? + - the DHCP lease data (such as NTP/DNS) is still made available when + a carrier is lost on a link. It should be removed instantly. + - expose in the API the following bits: + - option 15, domain name + - option 12, hostname and/or option 81, fqdn + - option 123, 144, geolocation + - option 252, configure http proxy (PAC/wpad) + - provide a way to define a per-network interface default metric value + for all routes to it. possibly a second default for DHCP routes. + - allow Name= to be specified repeatedly in the [Match] section. Maybe also + support Name=foo*|bar*|baz ? + - whenever uplink info changes, make DHCP server send out FORCERENEW + +- nspawn/vmspawn/pid1: add ability to easily insert fully booted VMs/FOSC into + shell pipelines, i.e. add easy to use switch that turns off console status + output, and generates the right credentials for systemd-run-generator so that + a program is invoked, and its output captured, with correct EOF handling and + exit code propagation -* per-service sandboxing option: ProtectIds=. If used, will overmount - /etc/machine-id and /proc/sys/kernel/random/boot_id with synthetic files, to - make it harder for the service to identify the host. Depending on the user - setting it should be fully randomized at invocation time, or a hash of the - real thing, keyed by the unit name or so. Of course, there are other ways to - get these IDs (e.g. journal) or similar ids (e.g. MAC addresses, DMI ids, CPU - ids), so this knob would only be useful in combination with other lockdown - options. Particularly useful for portable services, and anything else that - uses RootDirectory= or RootImage=. (Might also over-mount - /sys/class/dmi/id/*{uuid,serial} with /dev/null). +- nspawn/vmspawn: define hotkey that one can hit on the primary interface to + ask for a friendly, acpi style shutdown. -* doc: prep a document explaining resolved's internal objects, i.e. Query - vs. Question vs. Transaction vs. Stream and so on. +- **nspawn:** + - emulate /dev/kmsg using CUSE and turn off the syslog syscall + with seccomp. That should provide us with a useful log buffer that + systemd can log to during early boot, and disconnect container logs + from the kernel's logs. + - as soon as networkd has a bus interface, hook up --network-interface=, + --network-bridge= with networkd, to trigger netdev creation should an + interface be missing + - a nice way to boot up without machine id set, so that it is set at boot + automatically for supporting --ephemeral. Maybe hash the host machine id + together with the machine name to generate the machine id for the container + - fix logic always print a final newline on output. + https://github.com/systemd/systemd/pull/272#issuecomment-113153176 + - should optionally support receiving WATCHDOG=1 messages from its payload + PID 1... + - optionally automatically add FORWARD rules to iptables whenever nspawn is + running, remove them when shut down. + - add support for sysext extensions, too. i.e. a new --extension= switch that + takes one or more arguments, and applies the extensions already during + startup. + - when main nspawn supervisor process gets suspended due to SIGSTOP/SIGTTOU + or so, freeze the payload too. + - support time namespaces + - on cgroupsv1 issue cgroup empty handler process based on host events, so + that we make cgroup agent logic safe + - add API to invoke binary in container, then use that as fallback in + "machinectl shell" + - make nspawn suitable for shell pipelines: instead of triggering a hangup + when input is finished, send ^D, which synthesizes an EOF. Then wait for + hangup or ^D before passing on the EOF. + - greater control over selinux label? + - support that /proc, /sys/, /dev are pre-mounted + - maybe allow TPM passthrough, backed by swtpm, and measure --image= hash + into its PCR 11, so that nspawn instances can be TPM enabled, and partake + in measurements/remote attestation and such. swtpm would run outside of + control of container, and ideally would itself bind its encryption keys to + host TPM. + - make boot assessment do something sensible in a container. i.e send an + sd_notify() from payload to container manager once boot-up is completed + successfully, and use that in nspawn for dealing with boot counting, + implemented in the partition table labels and directory names. + - optionally set up nftables/iptables routes that forward UDP/TCP traffic on + port 53 to resolved stub 127.0.0.54 + - maybe optionally insert .nspawn file as GPT partition into images, so that + such container images are entirely stand-alone and can be updated as one. + - The subreaper logic we currently have seems overly complex. We should + investigate whether creating the inner child with CLONE_PARENT isn't better. + - Reduce the number of sockets that are currently in use and just rely on one + or two sockets. + - map foreign UID range through 1:1 + - systemd-nspawn should get the same SSH key support that vmspawn now has. -* doc: prep a document explaining PID 1's internal logic, i.e. transactions, - jobs, units +- oci: add support for "importctl import-oci" which implements the "OCI layout" + spec (i.e. acquiring via local fs access), as opposed to the current + "importctl pull-oci" which focusses on the "OCI image spec", i.e. downloads + from the web (i.e. acquiring via URLs). -* automatically ignore threaded cgroups in cg_xyz(). +- oci: add support for blake hashes for layers -* add linker script that implicitly adds symbol for build ID and new coredump - json package metadata, and use that when logging +- oci: support "data" in any OCI descriptor, not just manifest config. -* Enable RestrictFileSystems= for all our long-running services (similar: - RestrictNetworkInterfaces=) +- On boot, auto-generate an asymmetric key pair from the TPM, + and use it for validating DDIs and credentials. Maybe upload it to the kernel + keyring, so that the kernel does this validation for us for verity and kernel + modules -* Add systemd-analyze security checks for RestrictFileSystems= and - RestrictNetworkInterfaces= +- on first login of a user, measure its identity to some nvpcr -* cryptsetup/homed: implement TOTP authentication backed by TPM2 and its - internal clock. +- on shutdown: move utmp, wall, audit logic all into PID 1 (or logind?) -* man: rework os-release(5), and clearly separate our extension-release.d/ and - initrd-release parts, i.e. list explicitly which fields are about what. +- once swtpm's sd_notify() support has landed in the distributions, remove the + invocation in tpm2-swtpm.c and let swtpm handle it. -* sysext: before applying a sysext, do a superficial validation run so that - things are not rearranged to wildy. I.e. protect against accidental fuckups, - such as masking out /usr/lib/ or so. We should probably refuse if existing - inodes are replaced by other types of inodes or so. +- Once the root fs LUKS volume key is measured into PCR 15, default to binding + credentials to PCR 15 in "systemd-creds" -* userdb: when synthesizing NSS records, pick "best" password from defined - passwords, not just the first. i.e. if there are multiple defined, prefer - unlocked over locked and prefer non-empty over empty. +- optionally, also require WATCHDOG=1 notifications during service start-up and shutdown -* homed: if the homed shell fallback thing has access to an SSH agent, try to - use it to unlock home dir (if ssh-agent forwarding is enabled). We - could implement SSH unlocking of a homedir with that: when enrolling a new - ssh pubkey in a user record we'd ask the ssh-agent to sign some random value - with the privkey, then use that as luks key to unlock the home dir. Will not - work for ECDSA keys since their signatures contain a random component, but - will work for RSA and Ed25519 keys. +- optionally, collect cgroup resource data, and store it in per-unit RRD files, + suitable for processing with rrdtool. Add bus API to access this data, and + possibly implement a CPULoad property based on it. -* userdbd: implement an additional varlink service socket that provides the - host user db in restricted form, then allow this to be bind mounted into - sandboxed environments that want the host database in minimal form. All - records would be stripped of all meta info, except the basic UID/name - info. Then use this in portabled environments that do not use PrivateUsers=1. +- optionally: turn on cgroup delegation for per-session scope units -* portabled: when extracting unit files and copying to system.attached, if a - .p7s is available in the image, use it to protect the system.attached copy - with fs-verity, so that it cannot be tampered with +- pam_systemd: on interactive logins, maybe show SUPPORT_END information at + login time, à la motd -* /etc/veritytab: allow that the roothash column can be specified as fs path - including a path to an AF_UNIX path, similar to how we do things with the - keys of /etc/crypttab. That way people can store/provide the roothash - externally and provide to us on demand only. +- pam_systemd_home: add module parameter to control whether to only accept + only password or only pcks11/fido2 auth, and then use this to hook nicely + into two of the three PAM stacks gdm provides. + See discussion at https://github.com/authselect/authselect/pull/311 -* rework recursive read-only remount to use new mount API +- paranoia: whenever we process passwords, call mlock() on the memory + first. i.e. look for all places we use free_and_erasep() and + augment them with mlock(). Also use MADV_DONTDUMP. + Alternatively (preferably?) use memfd_secret(). -* when mounting disk images: if IMAGE_ID/IMAGE_VERSION is set in os-release - data in the image, make sure the image filename actually matches this, so - that images cannot be misused. +- pcrextend/tpm2-util: add a concept of "rotation" to event log. i.e. allow + trailing parts of the logs if time or disk space limit is hit. Protect the + boot-time measurements however (i.e. up to some point where things are + settled), since we need those for pcrlock measurements and similar. When + deleting entries for rotation, place an event that declares how many items + have been dropped, and what the hash before and after that. -* sysupdate: - - add fuzzing to the pattern parser - - support casync as download mechanism - - "systemd-sysupdate update --all" support, that iterates through all components - defined on the host, plus all images installed into /var/lib/machines/, - /var/lib/portable/ and so on. - - Allow invocation with a single transfer definition, i.e. with - --definitions= pointing to a file rather than a dir. - - add ability to disable implicit decompression of downloaded artifacts, - i.e. a Compress=no option in the transfer definitions +- pcrextend: after measuring get an immediate quote from the TPM, and validate + it. if it doesn't check out, i.e. the measurement we made doesn't appear in + the PCR then also reboot. -* in sd-id128: also parse UUIDs in RFC4122 URN syntax (i.e. chop off urn:uuid: prefix) +- pcrextend: maybe add option to disable measurements entirely via kernel cmdline -* systemd-sysext: optionally, run it in initrd already, before transitioning - into host, to open up possibility for services shipped like that. +- pcrextend: when we fail to measure, reboot the system (at least optionally). + important because certain measurements are supposed to "destroy" tpm object + access. -* whenever we receive fds via SCM_RIGHTS make sure none got dropped due to the - reception limit the kernel silently enforces. +- pcrlock: add support for multi-profile UKIs -* Add service unit setting ConnectStream= which takes IP addresses and connects to them. +- **pcrlock:** + - add kernel-install plugin that automatically creates UKI .pcrlock file when + UKI is installed, and removes it when it is removed again + - automatically install PE measurement of sd-boot on "bootctl install" + - pre-calc sysext + kernel cmdline measurements + - pre-calc cryptsetup root key measurement + - maybe make systemd-repart generate .pcrlock for old and new GPT header in + /run? + - Add support for more than 8 branches per PCR OR + - add "systemd-pcrlock lock-kernel-current" or so which synthesizes .pcrlock + policy from currently booted kernel/event log, to close gap for first boot + for pre-built images -* Similar, Load= which takes literal data in text or base64 format, and puts it - into a memfd, and passes that. This enables some fun stuff, such as embedding - bash scripts in unit files, by combining Load= with ExecStart=/bin/bash - /proc/self/fd/3 +- per-service sandboxing option: ProtectIds=. If used, will overmount + /etc/machine-id and /proc/sys/kernel/random/boot_id with synthetic files, to + make it harder for the service to identify the host. Depending on the user + setting it should be fully randomized at invocation time, or a hash of the + real thing, keyed by the unit name or so. Of course, there are other ways to + get these IDs (e.g. journal) or similar ids (e.g. MAC addresses, DMI ids, CPU + ids), so this knob would only be useful in combination with other lockdown + options. Particularly useful for portable services, and anything else that + uses RootDirectory= or RootImage=. (Might also over-mount + /sys/class/dmi/id/*{uuid,serial} with /dev/null). -* add a ConnectSocket= setting to service unit files, that may reference a - socket unit, and which will connect to the socket defined therein, and pass - the resulting fd to the service program via socket activation proto. +- Permit masking specific netlink APIs with RestrictAddressFamily= -* Add a concept of ListenStream=anonymous to socket units: listen on a socket - that is deleted in the fs. Use case would be with ConnectSocket= above. +- pick up creds from EFI vars -* importd: support image signature verification with PKCS#7 + OpenBSD signify - logic, as alternative to crummy gpg +- PID 1 should send out sd_notify("WATCHDOG=1") messages (for usage in the --user mode, and when run via nspawn) -* add "systemd-analyze debug" + AttachDebugger= in unit files: The former - specifies a command to execute; the latter specifies that an already running - "systemd-analyze debug" instance shall be contacted and execution paused - until it gives an OK. That way, tools like gdb or strace can be safely be - invoked on processes forked off PID 1. +- **pid1:** + - When logging about multiple units (stopping BoundTo units, conflicts, etc.), + log both units as UNIT=, so that journalctl -u triggers on both. + - generate better errors when people try to set transient properties + that are not supported... + https://lists.freedesktop.org/archives/systemd-devel/2015-February/028076.html + - recreate systemd's D-Bus private socket file on SIGUSR2 + - when we automatically restart a service, ensure we restart its rdeps, too. + - hide PAM options in fragment parser when compile time disabled + - Support --test based on current system state + - If we show an error about a unit (such as not showing up) and it has no Description string, then show a description string generated form the reverse of unit_name_mangle(). + - after deserializing sockets in socket.c we should reapply sockopts and things + - drop PID 1 reloading, only do reexecing (difficult: Reload() + currently is properly synchronous, Reexec() is weird, because we + cannot delay the response properly until we are back, so instead of + being properly synchronous we just keep open the fd and close it + when done. That means clients do not get a successful method reply, + but much rather a disconnect on success. + - when breaking cycles drop services from /run first, then from /etc, then from /usr + - when a bus name of a service disappears from the bus make sure to queue further activation requests + - maybe introduce CoreScheduling=yes/no to optionally set a PR_SCHED_CORE cookie, so that all + processes in a service's cgroup share the same cookie and are guaranteed not to share SMT cores + with other units https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/hw-vuln/core-scheduling.rst + - ExtensionImages= deduplication for services is currently only applied to disk images without GPT envelope. + This should be extended to work with proper DDIs too, as well as directory confext/sysext. Moreover, + system-wide confext/sysext should support this too. + - Pin the mount namespace via FD by sending it back from sd-exec to the manager, and use it + for live mounting, instead of doing it via PID + - also remove PID files of a service when the service starts, not just + when it exits + - activation by journal search expression + - lock image configured with RootDirectory=/RootImage= using the usual nspawn semantics while the unit is up + - find a way how we can reload unit file configuration for + specific units only, without reloading the whole of systemd + +- **PidRef conversion work:** + - cg_pid_get_xyz() + - pid_from_same_root_fs() + - get_ctty_devnr() + - actually wait for POLLIN on pidref's pidfd in service logic + - openpt_allocate_in_namespace() + - unit_attach_pid_to_cgroup_via_bus() + - cg_attach() – requires new kernel feature + - journald's process cache -* expose MS_NOSYMFOLLOW in various places +- port copy.c over to use LabelOps for all labelling. -* credentials system: - - acquire from EFI variable? - - acquire via ask-password? - - acquire creds via keyring? - - pass creds via keyring? - - pass creds via memfd? - - acquire + decrypt creds from pkcs11? - - make macsec code in networkd read key via creds logic (copy logic from - wireguard) - - make gatewayd/remote read key via creds logic - - add sd_notify() command for flushing out creds not needed anymore +- portable services: attach not only unit files to host, but also simple + binaries to a tmpfs path in $PATH. -* TPM2: auto-reenroll in cryptsetup, as fallback for hosed firmware upgrades - and such +- portabled: when extracting unit files and copying to system.attached, if a + .p7s is available in the image, use it to protect the system.attached copy + with fs-verity, so that it cannot be tampered with -* introduce a new group to own TPM devices +- print a nicer explanation if people use variable/specifier expansion in ExecStart= for the first word -* cryptsetup: add option for automatically removing empty password slot on boot +- **Process credentials in:** + - crypttab-generator: allow defining additional crypttab-like volumes via + credentials (similar: verity-generator, integrity-generator). Use + fstab-generator logic as inspiration. + - run-generator: allow defining additional commands to run via a credential + - resolved: allow defining additional /etc/hosts entries via a credential (it + might make sense to then synthesize a new combined /etc/hosts file in /run + and bind mount it on /etc/hosts for other clients that want to read it. + - repart: allow defining additional partitions via credential + - timesyncd: pick NTP server info from credential + - portabled: read a credential "portable.extra" or so, that takes a list of + file system paths to enable on start. + - make systemd-fstab-generator look for a system credential encoding root= or + usr= + - in gpt-auto-generator: check partition uuids against such uuids supplied via + sd-stub credentials. That way, we can support parallel OS installations with + pre-built kernels. -* cryptsetup: optionally, when run during boot-up and password is never - entered, and we are on battery power (or so), power off machine again +- properly handle loop back mounts via fstab, especially regards to fsck/passno -* cryptsetup: when waiting for FIDO2/PKCS#11 token, tell plymouth that, and - allow plymouth to abort the waiting and enter pw instead +- properly serialize the ExecStatus data from all ExecCommand objects + associated with services, sockets, mounts and swaps. Currently, the data is + flushed out on reload, which is quite a limitation. -* make cryptsetup lower --iter-time +- ProtectClock= (drops CAP_SYS_TIMES, adds seccomp filters for settimeofday, adjtimex), sets DeviceAllow o /dev/rtc -* cryptsetup: allow encoding key directly in /etc/crypttab, maybe with a - "base64:" prefix. Useful in particular for pkcs11 mode. +- ProtectKeyRing= to take keyring calls away -* cryptsetup: reimplement the mkswap/mke2fs in cryptsetup-generator to use - systemd-makefs.service instead. +- ProtectMount= (drop mount/umount/pivot_root from seccomp, disallow fuse via DeviceAllow, imply Mountflags=slave) -* cryptsetup: - - cryptsetup-generator: allow specification of passwords in crypttab itself - - support rd.luks.allow-discards= kernel cmdline params in cryptsetup generator +- ProtectReboot= that masks reboot() and kexec_load() syscalls, prohibits kill + on PID 1 with the relevant signals, and makes relevant files in /sys and + /proc (such as the sysrq stuff) unavailable -* systemd-analyze netif that explains predictable interface (or networkctl) +- ProtectTracing= (drops CAP_SYS_PTRACE, blocks ptrace syscall, makes /sys/kernel/tracing go away) -* systemd-analyze inspect-elf should show other notes too, at least build-id. +- ptyfwd: use osc context information in vmspawn/nspawn/… to optionally only + listen to ^]]] key when no further vmspawn/nspawn context is allocated -* Figure out naming of verbs in systemd-analyze: we have (singular) capability, - exit-status, but (plural) filesystems, architectures. +- ptyfwd: usec osc context information to propagate status messages from + vmspawn/nspawn to service manager's "status" string, reporting what is + currently in the fg -* special case some calls of chase() to use openat2() internally, so - that the kernel does what we otherwise do. +- pull-oci: progress notification -* add a new flag to chase() that stops chasing once the first missing - component is found and then allows the caller to create the rest. +- redefine /var/lib/extensions/ as the dir one can place all three of sysext, + confext as well is multi-modal DDIs that qualify as both. Then introduce + /var/lib/sysexts/ which can be used to place only DDIs that shall be used as + sysext -* make use of new glibc 2.32 APIs sigabbrev_np(). +- refcounting in sd-resolve is borked -* if /usr/bin/swapoff fails due to OOM, log a friendly explanatory message about it +- refuse boot if /usr/lib/os-release is missing or /etc/machine-id cannot be set up -* pid1: also remove PID files of a service when the service starts, not just - when it exits +- remove any syslog support from log.c — we probably cannot do this before split-off udev is gone for good -* seccomp: maybe use seccomp_merge() to merge our filters per-arch if we can. - Apparently kernel performance is much better with fewer larger seccomp - filters than with more smaller seccomp filters. +- remove tomoyo support, it's obsolete and unmaintained apparently -* systemd-path: Add "private" runtime/state/cache dir enum, mapping to - $RUNTIME_DIRECTORY, $STATE_DIRECTORY and such +- RemoveKeyRing= to remove all keyring entries of the specified user -* seccomp: by default mask x32 ABI system wide on x86-64. it's on its way out +- repart + cryptsetup: support file systems that are encrypted and use verity + on top. Usecase: confexts that shall be signed by the admin but also be + confidential. Then, add a new --make-ddi=confext-encrypted for this. -* seccomp: don't install filters for ABIs that are masked anyway for the - specific service +- repart/gpt-auto/DDIs: maybe introduce a concept of "extension" partitions, + that have a new type uuid and can "extend" earlier partitions, to work around + the fact that systemd-repart can only grow the last partition defined. During + activation we'd simply set up a dm-linear mapping to merge them again. A + partition that is to be extended would just set a bit in the partition flags + field to indicate that there's another extension partition to look for. The + identifying UUID of the extension partition would be hashed in counter mode + from the uuid of the original partition it extends. Inspiration for this is + the "dynamic partitions" concept of new Android. This would be a minimalistic + concept of a volume manager, with the extents it manages being exposes as GPT + partitions. I a partition is extended multiple times they should probably + grow exponentially in size to ensure O(log(n)) time for finding them on + access. -* busctl: maybe expose a verb "ping" for pinging a dbus service to see if it - exists and responds. +- repart: introduce concept of "ghost" partitions, that we setup in almost all + ways like other partitions, but do not actually register in the actual gpt + table, but only tell the kernel about via BLKPG ioctl. These partitions are + disk backed (hence can be large), but not persistent (as they are invisible + on next boot). Could be used by live media and similar, to boot up as usual + but automatically start at zero on each boot. There should also be a way to + make ghost partitions properly persistent on request. -* socket units: allow creating a udev monitor socket with ListenDevices= or so, - with matches, then activate app through that passing socket over +- repart: introduce MigrateFileSystem= or so which is a bit like + CopyFiles=/CopyBlocks= but operates via btrfs device logic: adds target as + new device then removes source from btrfs. Usecase: a live medium which uses + "ghost" partitions as suggested above, which can become persistent on request + on another device. -* unify on openssl: - - figure out what to do about libmicrohttpd: - - 1.x is stable and has a hard dependency on gnutls - - 2.x is in development and has openssl support - - Worth testing against 2.x in our CI? - - port fsprg over to openssl +- replace all \x1b, \x1B, \033 C string escape sequences in our codebase with a + more readable \e. It's a GNU extension, but a ton more readable than the + others, and most importantly it doesn't result in confusing errors if you + suffix the escape sequence with one more decimal digit, because compilers + think you might actually specify a value outside the 8bit range with that. -* add growvol and makevol options for /etc/crypttab, similar to - x-systemd.growfs and x-systemd-makefs. +- replace all uses of fopen_temporary() by fopen_tmpfile_linkable() + + flink_tmpfile() and then get rid of fopen_temporary(). Benefit: use O_TMPFILE + pervasively, and avoid rename() wherever we can. -* userdb: allow existence checks +- replace bootctl's PE version check to actually use APIs from pe-binary.[ch] + to find binary version. -* pid1: activation by journal search expression +- replace symlink_label(), mknodat_label(), btrfs_subvol_make_label(), + mkdir_label() and related calls by flags-based calls that use + label_ops_pre()/label_ops_post(). -* when switching root from initrd to host, set the machine_id env var so that - if the host has no machine ID set yet we continue to use the random one the - initrd had set. +- report: have something that requests cloud workload identity bearer tokens + and includes it in the report -* sd-event: add native support for P_ALL waitid() watching, then move PID 1 to - it for reaping assigned but unknown children. This needs to some special care - to operate somewhat sensibly in light of priorities: P_ALL will return - arbitrary processes, regardless of the priority we want to watch them with, - hence on each event loop iteration check all processes which we shall watch - with higher prio explicitly, and then watch the entire rest with P_ALL. +- **report:** + - plug "facts" into systemd-report too, i.e. stuff that is more static, such as hostnames, ssh keys and so on. + - pass filtering hints to services, so that they can also be applied server-side, not just client side + - metrics from pid1: suppress metrics form units that are inactive and have nothing to report + - add "hint-suppress-zero" flag (which suppresses all metrics which are zero) + - add "hint-object" parameter (which only queries info about certain object) + - make systemd-report a varlink service -* tweak sd-event's child watching: keep a prioq of children to watch and use - waitid() only on the children with the highest priority until one is waitable - and ignore all lower-prio ones from that point on +- Reset TPM2 DA bit on each successful boot -* maybe introduce xattrs that can be set on the root dir of the root fs - partition that declare the volatility mode to use the image in. Previously I - thought marking this via GPT partition flags but that's not ideal since - that's outside of the LUKS encryption/verity verification, and we probably - shouldn't operate in a volatile mode unless we got told so from a trusted - source. +- **resolved:** + - mDNS/DNS-SD + - service registration + - service/domain/types browsing + - avahi compat + - DNS-SD service registration from socket units + - resolved should optionally register additional per-interface LLMNR + names, so that for the container case we can establish the same name + (maybe "host") for referencing the server, everywhere. + - allow clients to request DNSSEC for a single lookup even if DNSSEC is off (?) + - make resolved process DNR DHCP info + - report ttl in resolution replies if we know it. This data is useful + for tools such as wireguard which want to periodically re-resolve DNS names, + and might want to use the TTL has hint for that. + - take possession of some IPv6 ULA address (let's say + fd00:5353:5353:5353:5353:5353:5353:5353), and listen on port 53 on it for the + local stubs, so that we can make the stub available via ipv6 too. + +- revisit how we pass fs images and initrd to the kernel. take uefi http boot + ramdisks as inspiration: for any confext/sysext/initrd erofs/DDI image simply + generate a fake pmem region in the UEFI memory tables, that Linux then turns + into /dev/pmemX. Then turn of cpio-based initrd logic in linux kernel, + instead let kernel boot directly into /dev/pmem0. In order to allow our usual + cpio-based parameterization, teach PID 1 to just uncompress cpio ourselves + early on, from another pmem device. (Related to this, maybe introduce a new + PE section .ramdisk that just synthesizes pmem devices from arbitrary + blobs. Could be particularly useful in add-ons) -* coredump: maybe when coredumping read a new xattr from /proc/$PID/exe that - may be used to mark a whole binary as non-coredumpable. Would fix: - https://bugs.freedesktop.org/show_bug.cgi?id=69447 +- rework ExecOutput and ExecInput enums so that EXEC_OUTPUT_NULL loses its + magic meaning and is no longer upgraded to something else if set explicitly. -* teach parse_timestamp() timezones like the calendar spec already knows it +- rework fopen_temporary() to make use of open_tmpfile_linkable() (problem: the + kernel doesn't support linkat() that replaces existing files, currently) -* We should probably replace /etc/rc.d/README with a symlink to doc - content. After all it is constant vendor data. +- rework journalctl -M to be based on a machined method that generates a mount + fd of the relevant journal dirs in the container with uidmapping applied to + allow the host to read it, while making everything read-only. -* maybe add kernel cmdline params: to force random seed crediting +- rework loopback support in fstab: when "loop" option is used, then + instantiate a new systemd-loop@.service for the source path, set the + lo_file_name field for it to something recognizable derived from the fstab + line, and then generate a mount unit for it using a udev generated symlink + based on lo_file_name. -* let's not GC a unit while its ratelimits are still pending +- rework recursive read-only remount to use new mount API -* when killing due to service watchdog timeout maybe detect whether target - process is under ptracing and then log loudly and continue instead. +- rework seccomp/nnp logic that even if User= is used in combination with + a seccomp option we don't have to set NNP. For that, change uid first while + keeping CAP_SYS_ADMIN, then apply seccomp, the drop cap. -* make rfkill uaccess controllable by default, i.e. steal rule from - gnome-bluetooth and friends +- rewrite bpf-devices in libbpf/C code, rather than home-grown BPF assembly, to + match bpf-restrict-fs, bpf-restrict-ifaces, bpf-socket-bind -* make MAINPID= message reception checks even stricter: if service uses User=, - then check sending UID and ignore message if it doesn't match the user or - root. +- rewrite bpf-firewall in libbpf/C code -* maybe trigger a uevent "change" on a device if "systemctl reload xyz.device" - is issued. +- rfkill,backlight: we probably should run the load tools inside of the udev rules so that the state is properly initialized by the time other software sees it -* when importing an fs tree with machined, optionally apply userns-rec-chown +- rough proposed implementation design for remote attestation infra: add a tool + that generates a quote of local PCRs and NvPCRs, along with synchronous log + snapshot. use "audit session" logic for that, so that we get read-outs and + signature in one step. Then turn this into a JSON object. Use the "TCG TSS 2.0 + JSON Data Types and Policy Language" format to encode the signature. And CEL + for the measurement log. -* when importing an fs tree with machined, complain if image is not an OS +- run0: maybe enable utmp for run0 sessions, so that they are easily visible. -* Maybe introduce a helper safe_exec() or so, which is to execve() which - safe_fork() is to fork(). And then make revert the RLIMIT_NOFILE soft limit - to 1K implicitly, unless explicitly opted-out. +- sd-boot/sd-stub: install a uefi "handle" to a sidecar dir of bls type #1 + entries with an "uki" or "uki-url" stanza, and make sd-stub look for + that. That way we can parameterize type #1 entries nicely. -* rework seccomp/nnp logic that even if User= is used in combination with - a seccomp option we don't have to set NNP. For that, change uid first while - keeping CAP_SYS_ADMIN, then apply seccomp, the drop cap. +- **sd-boot:** + - do something useful if we find exactly zero entries (ignoring items + such as reboot/poweroff/factory reset). Show a help text or so. + - optionally ask for confirmation before executing certain operations + (e.g. factory resets, storagetm with world access, and so on) + - for each installed OS, grey out older entries (i.e. all but the + newest), to indicate they are obsolete + - we probably should include all BootXY EFI variable defined boot + entries in our menu, and then suppress ourselves. Benefit: instant + compatibility with all other OSes which register things there, in particular + on other disks. Always boot into them via NextBoot EFI variable, to not + affect PCR values. + - should look for information what to boot in SMBIOS, too, so that VM + managers can tell sd-boot what to boot into and suchlike + - instead of unconditionally deriving the ESP to search boot loader + spec entries in from the paths of sd-boot binary, let's optionally allow it + to be configured on sd-boot cmdline + efi var. Use case: embed sd-boot in the + UEFI firmware (for example, ovmf supports that via qemu cmdline option), and + use it to load stuff from the ESP. + - optionally, show boot menu when previous default boot item has + non-zero "tries done" count + +- **sd-bus:** + - EBADSLT handling + - GetAllProperties() on a non-existing object does not result in a failure currently + - port to sd-resolve for connecting to TCP dbus servers + - see if we can introduce a new sd_bus_get_owner_machine_id() call to retrieve the machine ID of the machine of the bus itself + - see if we can drop more message validation on the sending side + - add API to clone sd_bus_message objects + - longer term: priority inheritance + - dbus spec updates: + - NameLost/NameAcquired obsolete + - path escaping + - update systemd.special(7) to mention that dbus.socket is only about the compatibility socket now + - add vtable flag, that may be used to request client creds implicitly + and asynchronously before dispatching the operation + - parse addresses given in sd_bus_set_addresses immediately and not + only when used. Add unit tests. + +- **sd-device:** + - add an API for acquiring list of child devices, given a device + objects (i.e. all child dirents that dirs or symlinks to dirs) + - maybe pin the sysfs dir with an fd, during the entire runtime of + an sd_device, then always work based on that. + - should return the devnum type (i.e. 'b' or 'c') via some API for an + sd_device object, so that data passed into sd_device_new_from_devnum() can + also be queried. + +- **sd-event:** + - allow multiple signal handlers per signal? + - document chaining of signal handler for SIGCHLD and child handlers + - define more intervals where we will shift wakeup intervals around in, 1h, 6h, 24h, ... + - maybe support iouring as backend, so that we allow hooking read and write + operations instead of IO ready events into event loops. See considerations + here: + http://blog.vmsplice.net/2020/07/rethinking-event-loop-integration-for.html + - add ability to "chain" event sources. Specifically, add a call + sd_event_source_chain(x, y), which will automatically enable event source y + in oneshot mode once x is triggered. Use case: in src/core/mount.c implement + the /proc/self/mountinfo rescan on SIGCHLD with this: whenever a SIGCHLD is + seen, trigger the rescan defer event source automatically, and allow it to be + dispatched *before* the SIGCHLD is handled (based on priorities). Benefit: + dispatch order is strictly controlled by priorities again. (next step: chain + event sources to the ratelimit being over) + - compat wd reuse in inotify code: keep a set of removed watch + descriptors, and clear this set piecemeal when we see the IN_IGNORED event + for it, or when read() returns EAGAIN or on IN_Q_OVERFLOW. Then, whenever we + see an inotify wd event check against this set, and if it is contained ignore + the event. (to be fully correct this would have to count the occurrences, in + case the same wd is reused multiple times before we start processing + IN_IGNORED again) + - optionally, if per-event source rate limit is hit, downgrade + priority, but leave enabled, and once ratelimit window is over, upgrade + priority again. That way we can combat event source starvation without + stopping processing events from one source entirely. + - similar to existing inotify support add fanotify support (given + that apparently new features in this area are only going to be added to the + latter). + - add 1st class event source for clock changes + - add 1st class event source for timezone changes + - add native support for P_ALL waitid() watching, then move PID 1 to + it for reaping assigned but unknown children. This needs to some special care + to operate somewhat sensibly in light of priorities: P_ALL will return + arbitrary processes, regardless of the priority we want to watch them with, + hence on each event loop iteration check all processes which we shall watch + with higher prio explicitly, and then watch the entire rest with P_ALL. + +- sd-journal puts a limit on parallel journal files to view at once. journald + should probably honour that same limit (JOURNAL_FILES_MAX) when vacuuming to + ensure we never generate more files than we can actually view. -* when no locale is configured, default to UEFI's PlatformLang variable +- sd-lldp: pick up 802.3 maximum frame size/mtu, to be able to detect jumbo + frame capable networks -* add a new syscall group "@esoteric" for more esoteric stuff such as bpf() and - usefaultd() and make systemd-analyze check for it. +- **sd-rtnl:** + - add support for more attribute types + - inbuilt piping support (essentially degenerate async)? see loopback-setup.c and other places -* paranoia: whenever we process passwords, call mlock() on the memory - first. i.e. look for all places we use free_and_erasep() and - augment them with mlock(). Also use MADV_DONTDUMP. - Alternatively (preferably?) use memfd_secret(). +- **sd-stub:** + - detect if we are running with uefi console output on serial, and if so + automatically add console= to kernel cmdline matching the same port. + - add ".bootcfg" section for kernel bootconfig data (as per + https://docs.kernel.org/admin-guide/bootconfig.html) -* Move RestrictAddressFamily= to the new cgroup create socket +- sd_notify/vsock: maybe support binding to AF_VSOCK in Type=notify services, + then passing $NOTIFY_SOCKET and $NOTIFY_GUESTCID with PID1's cid (typically + fixed to "2", i.e. the official host cid) and the expected guest cid, for the + two sides of the channel. The latter env var could then be used in an + appropriate qemu cmdline. That way qemu payloads could talk sd_notify() + directly to host service manager. -* optionally: turn on cgroup delegation for per-session scope units +- **seccomp:** + - maybe use seccomp_merge() to merge our filters per-arch if we can. + Apparently kernel performance is much better with fewer larger seccomp + filters than with more smaller seccomp filters. + - by default mask x32 ABI system wide on x86-64. it's on its way out + - don't install filters for ABIs that are masked anyway for the + specific service -* sd-boot: optionally, show boot menu when previous default boot item has - non-zero "tries done" count +- seems that when we follow symlinks to units we prefer the symlink + destination path over /etc and /usr. We should not do that. Instead + /etc should always override /run+/usr and also any symlink + destination. -* augment CODE_FILE=, CODE_LINE= with something like CODE_BASE= or so which - contains some identifier for the project, which allows us to include - clickable links to source files generating these log messages. The identifier - could be some abberviated URL prefix or so (taking inspiration from Go - imports). For example, for systemd we could use - CODE_BASE=github.com/systemd/systemd/blob/98b0b1123cc or so which is - sufficient to build a link by prefixing "http://" and suffixing the - CODE_FILE. - -* Augment MESSAGE_ID with MESSAGE_BASE, in a similar fashion so that we can - make clickable links from log messages carrying a MESSAGE_ID, that lead to - some explanatory text online. - -* maybe extend .path units to expose fanotify() per-mount change events - -* hibernate/s2h: if swap is on weird storage and refuse if so - -* cgroups: use inotify to get notified when somebody else modifies cgroups - owned by us, then log a friendly warning. - -* beef up log.c with support for stripping ANSI sequences from strings, so that - it is OK to include them in log strings. This would be particularly useful so - that our log messages could contain clickable links for example for unit - files and suchlike we operate on. - -* add support for "portablectl attach http://foobar.com/waaa.raw (i.e. importd integration) - -* sync dynamic uids/gids between host+portable service (i.e. if DynamicUser=1 is set for a service, make sure that the - selected user is resolvable in the service even if it ships its own /etc/passwd) - -* Fix DECIMAL_STR_MAX or DECIMAL_STR_WIDTH. One includes a trailing NUL, the - other doesn't. What a disaster. Probably to exclude it. - -* Check that users of inotify's IN_DELETE_SELF flag are using it properly, as - usually IN_ATTRIB is the right way to watch deleted files, as the former only - fires when a file is actually removed from disk, i.e. the link count drops to - zero and is not open anymore, while the latter happens when a file is - unlinked from any dir. - -* systemctl, machinectl, loginctl: port "status" commands over to - format-table.c's vertical output logic. +- .service with invalid Sockets= starts successfully. -* pid1: lock image configured with RootDirectory=/RootImage= using the usual nspawn semantics while the unit is up +- services: add support for cryptographically unlocking per-service directories + via TPM2. Specifically, for StateDirectory= (and related dirs) use fscrypt to + set up the directory so that it can only be accessed if host and app are in + order. -* add --vacuum-xyz options to coredumpctl, matching those journalctl already has. +- shared/wall: Once more programs are taught to prefer sd-login over utmp, + switch the default wall implementation to wall_logind + (https://github.com/systemd/systemd/pull/29051#issuecomment-1704917074) -* add CopyFile= or so as unit file setting that may be used to copy files or - directory trees from the host to the services RootImage= and RootDirectory= - environment. Which we can use for /etc/machine-id and in particular - /etc/resolv.conf. Should be smart and do something useful on read-only - images, for example fall back to read-only bind mounting the file instead. +- show whether a service has out-of-date configuration in "systemctl status" by + using mtime data of ConfigurationDirectory=. -* bypass SIGTERM state in unit files if KillSignal is SIGKILL +- shutdown logging: store to EFI var, and store to USB stick? -* add proper dbus APIs for the various sd_notify() commands, such as MAINPID=1 - and so on, which would mean we could report errors and such. +- signed bpf loading: to address need for signature verification for bpf + programs when they are loaded, and given the bpf folks don't think this is + realistic in kernel space, maybe add small daemon that facilitates this + loading on request of clients, validates signatures and then loads the + programs. This daemon should be the only daemon with privs to do load BPF on + the system. It might be a good idea to run this daemon already in the initrd, + and leave it around during the initrd transition, to continue serve requests. + Should then live in its own fs namespace that inherits from the initrd's + fs tree, not from the host, to isolate it properly. Should set + PR_SET_DUMPABLE so that it cannot be ptraced from the host. Should have + CAP_SYS_BPF as only service around. -* introduce DefaultSlice= or so in system.conf that allows changing where we - place our units by default, i.e. change system.slice to something - else. Similar, ManagerSlice= should exist so that PID1's own scope unit could - be moved somewhere else too. Finally machined and logind should get similar - options so that it is possible to move user session scopes and machines to a - different slice too by default. Use case: people who want to put resources on - the entire system, with the exception of one specific service. See: - https://lists.freedesktop.org/archives/systemd-devel/2018-February/040369.html +- SIGRTMIN+18 and memory pressure handling should still be added to: hostnamed, + localed, oomd, timedated. -* calenderspec: add support for week numbers and day numbers within a - year. This would allow us to define "bi-weekly" triggers safely. +- socket units: allow creating a udev monitor socket with ListenDevices= or so, + with matches, then activate app through that passing socket over -* sd-bus: add vtable flag, that may be used to request client creds implicitly - and asynchronously before dispatching the operation +- special case some calls of chase() to use openat2() internally, so + that the kernel does what we otherwise do. -* sd-bus: parse addresses given in sd_bus_set_addresses immediately and not - only when used. Add unit tests. +- Split vconsole-setup in two, of which the second is started via udev (instead + of the "restart" job it currently fires). That way, boot becomes purely + positive again, and we can nicely order the two against each other. -* make use of ethtool veth peer info in machined, for automatically finding out - host-side interface pointing to the container. +- start making use of the new --graceful switch to util-linux' umount command -* add some special mode to LogsDirectory=/StateDirectory=… that allows - declaring these directories without necessarily pulling in deps for them, or - creating them when starting up. That way, we could declare that - systemd-journald writes to /var/log/journal, which could be useful when we - doing disk usage calculations and so on. +- start using STATX_SUBVOL in btrfs_is_subvol(). Also, make use of it + generically, so that image discovery recognizes bcachefs subvols too. -* deprecate RootDirectoryStartOnly= in favour of a new ExecStart= prefix char +- storagetm: maybe also serve the specified disk via HTTP? we have glue for + microhttpd anyway already. Idea would also be serve currently booted UKI as + separate HTTP resource, so that EFI http boot on another system could + directly boot from our system, with full access to the hdd. -* support projid-based quota in machinectl for containers +- **storagetm:** + - add USB mass storage device logic, so that all local disks are also exposed + as mass storage devices on systems that have a USB controller that can + operate in device mode + - add NVMe authentication -* add a way to lock down cgroup migration: a boolean, which when set for a unit - makes sure the processes in it can never migrate out of it +- support boot into nvme-over-tcp: add generator that allows specifying nvme + devices on kernel cmdline + credentials. Also maybe add interactive mode + (where the user is prompted for nvme info), in order to boot from other + system's HDD. -* blog about fd store and restartable services +- support crash reporting operation modes (https://live.gnome.org/GnomeOS/Design/Whiteboards/ProblemReporting) -* document Environment=SYSTEMD_LOG_LEVEL=debug drop-in in debugging document +- support projid-based quota in machinectl for containers -* rework ExecOutput and ExecInput enums so that EXEC_OUTPUT_NULL loses its - magic meaning and is no longer upgraded to something else if set explicitly. +- Support ReadWritePaths/ReadOnlyPaths/InaccessiblePaths in systemd --user instances + via the new unprivileged Landlock LSM (https://landlock.io) -* in the long run: permit a system with /etc/machine-id linked to /dev/null, to - make it lose its identity, i.e. be anonymous. For this we'd have to patch - through the whole tree to make all code deal with the case where no machine - ID is available. +- support specifying download hash sum in systemd-import-generator expression + to pin image/tarball. -* optionally, collect cgroup resource data, and store it in per-unit RRD files, - suitable for processing with rrdtool. Add bus API to access this data, and - possibly implement a CPULoad property based on it. +- sync dynamic uids/gids between host+portable service (i.e. if DynamicUser=1 is set for a service, make sure that the + selected user is resolvable in the service even if it ships its own /etc/passwd) -* beef up pam_systemd to take unit file settings such as cgroups properties as - parameters +- synchronize console access with BSD locks: + https://lists.freedesktop.org/archives/systemd-devel/2014-October/024582.html -* In DynamicUser= mode: before selecting a UID, use disk quota APIs on relevant - disks to see if the UID is already in use. +- sysext: before applying a sysext, do a superficial validation run so that + things are not rearranged to wildy. I.e. protect against accidental fuckups, + such as masking out /usr/lib/ or so. We should probably refuse if existing + inodes are replaced by other types of inodes or so. -* Add AddUser= setting to unit files, similar to DynamicUser=1 which however - creates a static, persistent user rather than a dynamic, transient user. We - can leverage code from sysusers.d for this. +- sysext: measure all activated sysext into a TPM PCR -* add some optional flag to ReadWritePaths= and friends, that has the effect - that we create the dir in question when the service is started. Example: +- system BPF LSM policy that enforces that block device backed mounts may only + be established on top of dm-crypt or dm-verity devices, or an allowlist of + file systems (which should probably include vfat, for compat with the ESP) - ReadWritePaths=:/var/lib/foobar +- system BPF LSM policy that prohibits creating files owned by "nobody" + system-wide -* Add ExecMonitor= setting. May be used multiple times. Forks off a process in - the service cgroup, which is supposed to monitor the service, and when it - exits the service is considered failed by its monitor. +- system BPF LSM policy that prohibits creating or opening device nodes outside + of devtmpfs/tmpfs, except if they are the pseudo-devices /dev/null, + /dev/zero, /dev/urandom and so on. -* track the per-service PAM process properly (i.e. as an additional control - process), so that it may be queried on the bus and everything. +- "systemctl preset-all" should probably order the unit files it + operates on lexicographically before starting to work, in order to + ensure deterministic behaviour if two unit files conflict (like DMs + do, for example) -* add a new "debug" job mode, that is propagated to unit_start() and for - services results in two things: we raise SIGSTOP right before invoking - execve() and turn off watchdog support. Then, use that to implement - "systemd-gdb" for attaching to the start-up of any system service in its - natural habitat. +- systemctl, machinectl, loginctl: port "status" commands over to + format-table.c's vertical output logic. -* add a percentage syntax for TimeoutStopSec=, e.g. TimeoutStopSec=150%, and - then use that for the setting used in user@.service. It should be understood - relative to the configured default value. +- **systemctl:** + - add systemctl switch to dump transaction without executing it + - Add a verbose mode to "systemctl start" and friends that explains what is being done or not done + - print nice message from systemctl --failed if there are no entries shown, and hook that into ExecStartPre of rescue.service/emergency.service + - add new command to systemctl: "systemctl system-reexec" which reexecs as many daemons as virtually possible + - systemctl enable: fail if target to alias into does not exist? maybe show how many units are enabled afterwards? + - systemctl: "Journal has been rotated since unit was started." message is misleading + - if some operation fails, show log output? -* enable LockMLOCK to take a percentage value relative to physical memory +- systemd-analyze inspect-elf should show other notes too, at least build-id. -* Permit masking specific netlink APIs with RestrictAddressFamily= +- systemd-analyze netif that explains predictable interface (or networkctl) -* define gpt header bits to select volatility mode +- systemd-analyze: port "pcrs" verb to talk directly to TPM device, instead of + using sysfs interface (well, or maybe not, as that would require privileges?) -* ProtectClock= (drops CAP_SYS_TIMES, adds seecomp filters for settimeofday, adjtimex), sets DeviceAllow o /dev/rtc +- systemd-boot: maybe add support for collapsing menu entries of the same OS + into one item that can be opened (like in a "tree view" UI element) or + collapsed. If only a single OS is installed, disable this mode, but if + multiple OSes are installed might make sense to default to it, so that user + is not immediately bombarded with a multitude of Linux kernel versions but + only one for each OS. -* ProtectTracing= (drops CAP_SYS_PTRACE, blocks ptrace syscall, makes /sys/kernel/tracing go away) +- systemd-creds: extend encryption logic to support asymmetric + encryption/authentication. Idea: add new verb "systemd-creds public-key" + which generates a priv/pub key pair on the TPM2 and stores the priv key + locally in /var. It then outputs a certificate for the pub part to stdout. + This can then be copied/taken elsewhere, and can be used for encrypting creds + that only the host on its specific hw can decrypt. Then, support a drop-in + dir with certificates that can be used to authenticate credentials. Flow of + operations is then this: build image with owner certificate, then after + boot up issue "systemd-creds public-key" to acquire pubkey of the machine. + Then, when passing data to the machine, sign with privkey belonging to one of + the dropped in certs and encrypted with machine pubkey, and pass to machine. + Machine is then able to authenticate you, and confidentiality is guaranteed. -* ProtectMount= (drop mount/umount/pivot_root from seccomp, disallow fuse via DeviceAllow, imply Mountflags=slave) +- systemd-cryptenroll: add --firstboot or so, that will interactively ask user + whether recovery key shall be enrolled and do so -* ProtectKeyRing= to take keyring calls away +- systemd-dissect: add --cat switch for dumping files such as /etc/os-release -* RemoveKeyRing= to remove all keyring entries of the specified user +- systemd-dissect: show available versions inside of a disk image, i.e. if + multiple versions are around of the same resource, show which ones. (in other + words: show partition labels). -* ProtectReboot= that masks reboot() and kexec_load() syscalls, prohibits kill - on PID 1 with the relevant signals, and makes relevant files in /sys and - /proc (such as the sysrq stuff) unavailable +- systemd-firstboot: optionally install an ssh key for root for offline use. -* Support ReadWritePaths/ReadOnlyPaths/InaccessiblePaths in systemd --user instances - via the new unprivileged Landlock LSM (https://landlock.io) +- systemd-gpt-auto-generator: add kernel cmdline option to override block + device to dissect. also support dissecting a regular file. useccase: include + encrypted/verity root fs in UKI. -* make sure the ratelimit object can deal with USEC_INFINITY as way to turn off things +- systemd-inhibit: make taking delay locks useful: support sending SIGINT or SIGTERM on PrepareForSleep() -* in nss-systemd, if we run inside of RootDirectory= with PrivateUsers= set, - find a way to map the User=/Group= of the service to the right name. This way - a user/group for a service only has to exist on the host for the right - mapping to work. +- **systemd-measure tool:** + - pre-calculate PCR 12 (command line) + PCR 13 (sysext) the same way we can precalculate PCR 11 + - add --pcrpkey-auto as an alternative to --pcrpkey=, where it would just use + the same public key specified with --public-key= (or the one automatically + derived from --private-key=). + - allow multiple --initrd=, --efifw=, --dtbauto=, etc., params and pad and + concatenate the contents in the same way that ukify does, so we end up with + the expected measurement. + +- systemd-mount should only consider modern file systems when mounting, similar + to systemd-dissect -* add bus API for creating unit files in /etc, reusing the code for transient units +- systemd-path: Add "private" runtime/state/cache dir enum, mapping to + $RUNTIME_DIRECTORY, $STATE_DIRECTORY and such -* add bus API to remove unit files from /etc +- **systemd-pcrextend:** + - once we have that start measuring every sysext we apply, every confext, + every RootImage= we apply, every nspawn and so on. All in separate fake + PCRs. -* add bus API to retrieve current unit file contents (i.e. implement "systemctl cat" on the bus only) +- **systemd-repart:** + - implement Integrity=data/meta and Integrity=inline for non-LUKS + case. Currently, only Integrity=inline combined with Encrypt= is implemented + and uses libcryptsetup features. Add support for plain dm-integrity setups when + integrity tags are stored by the device (inline), interleaved with data (data), + and on a separate device (meta). + - add --ghost, that creates file systems, updates the kernel's + partition table but does *not* update partition table on disk. This way, we + have disk backed file systems that go effectively disappear on reboot. This + is useful when booting from a "live" usb stick that is writable, as it means + we do not have to place everything in memory. Moreover, we could then migrate + the file systems to disk later (using btrfs device replacement), if needed as + part of an installer logic. + - make useful to duplicate current OS onto a second disk, so + that we can sanely copy ESP contents, /usr/ images, and then set up btrfs + raid for the root fs to extend/mirror the existing install. This would be + very similar to the concept of live-install-through-btrfs-migration. + - add --installer or so, that will interactively ask for a + target disk, maybe ask for confirmation, and install something on disk. Then, + hook that into installer.target or so, so that it can be used to + install/replicate installs + - should probably enable btrfs' "temp_fsid" feature for all file + systems it creates, as we have no interest in RAID for repart, and it should + make sure that we can mount them trivially everywhere. + - add support for formatting dm-crypt + dm-integrity file + systems. + - also derive the volume key from the seed value, for the + aforementioned purpose. + - in addition to the existing "factory reset" mode (which + simply empties existing partitions marked for that). add a mode where + partitions marked for it are entirely removed. Use case: remove secondary OS + copy, and redundant partitions entirely, and recreate them anew. + - if the GPT *disk* UUID (i.e. the one global for the entire + disk) is set to all FFFFF then use this as trigger for factory reset, in + addition to the existing mechanisms via EFI variables and kernel command + line. Benefit: works also on non-EFI systems, and can be requested on one + boot, for the next. + - read LUKS encryption key from $CREDENTIALS_DIRECTORY + - support setting up dm-integrity with HMAC + - maybe remove half-initialized image on failure. It fails + if the output file exists, so a repeated invocation will usually fail if + something goes wrong on the way. + - by default generate minimized partition tables (i.e. tables + that only cover the space actually used, excluding any free space at the + end), in order to maximize dd'ability. Requires libfdisk work, see + https://github.com/karelzak/util-linux/issues/907 + - MBR partition table support. Care needs to be taken regarding + Type=, so that partition definitions can sanely apply to both the GPT and the + MBR case. Idea: accept syntax "Type=gpt:home mbr:0x83" for setting the types + for the two partition types explicitly. And provide an internal mapping so + that "Type=linux-generic" maps to the right types for both partition tables + automatically. + - allow sizing partitions as factor of available RAM, so that + we can reasonably size swap partitions for hibernation. + - allow boolean option that ensures that if existing partition + doesn't exist within the configured size bounds the whole command fails. This + is useful to implement ESP vs. XBOOTLDR schemes in installers: have one set + of repart files for the case where ESP is large enough and one where it isn't + and XBOOTLDR is added in instead. Then apply the former first, and if it + fails to apply use the latter. + - add per-partition option to never reuse existing partition + and always create anew even if matching partition already exists. + - add per-partition option to fail if partition already exist, + i.e. is not added new. Similar, add option to fail if partition does not exist yet. + - allow disabling growing of specific partitions, or making + them (think ESP: we don't ever want to grow it, since we cannot resize vfat) + Also add option to disable operation via kernel command line. + - make it a static checker during early boot for existence and + absence of other partitions for trusted boot environments + - add support for SD_GPT_FLAG_GROWFS also on real systems, i.e. + generate some unit to actually enlarge the fs after growing the partition + during boot. + - do not print "Successfully resized …" when no change was done. + +- systemd-stub: maybe store a "boot counter" in the ESP, and pass it down to + userspace to allow ordering boots (for example in journalctl). The counter + would be monotonically increased on every boot. -* rework fopen_temporary() to make use of open_tmpfile_linkable() (problem: the - kernel doesn't support linkat() that replaces existing files, currently) +- systemd-sysext: add "exec" command or so that is a bit like "refresh" but + runs it in a new namespace and then just executes the selected binary within + it. Could be useful to run one-off binaries inside a sysext as a CLI tool. -* transient units: don't bother with actually setting unit properties, we - reload the unit file anyway +- systemd-tmpfiles: add concept for conditionalizing lines on factory reset + boot, or on first boot. -* optionally, also require WATCHDOG=1 notifications during service start-up and shutdown +- systemd-tpm2-setup should support a mode where we refuse booting if the SRK + changed. (Must be opt-in, to not break systems which are supposed to be + migratable between PCs) -* cache sd_event_now() result from before the first iteration... +- systemd-tpm2-support: add a some logic that detects if system is in DA + lockout mode, and queries the user for TPM recovery PIN then. -* PID1: find a way how we can reload unit file configuration for - specific units only, without reloading the whole of systemd +- systemd: add storage API via varlink, where everyone can drop a socket in a + dir, similar, do the same thing for networking -* add an explicit parser for LimitRTPRIO= that verifies - the specified range and generates sane error messages for incorrect - specifications. +- $SYSTEMD_EXECPID that the service manager sets should + be augmented with $SYSTEMD_EXECPIDFD (and similar for + other env vars we might send). -* when we detect that there are waiting jobs but no running jobs, do something +- **sysupdate:** + - add fuzzing to the pattern parser + - support casync as download mechanism + - "systemd-sysupdate update --all" support, that iterates through all components + defined on the host, plus all images installed into /var/lib/machines/, + /var/lib/portable/ and so on. + - Allow invocation with a single transfer definition, i.e. with + --definitions= pointing to a file rather than a dir. + - add ability to disable implicit decompression of downloaded artifacts, + i.e. a Compress=no option in the transfer definitions + - download multiple arbitrary patterns from same source + - SHA256SUMS format with bearer tokens for each resource to download + - decrypt SHA256SUMS with key from tpm + - clean up stuff on disk that disappears from SHA256SUMS + - turn http backend stuff int plugin via varlink + - for each transfer support looking at multiple sources, + pick source with newest entry. If multiple sources have the same entry, use + first configured source. Usecase: "sideload" components from local dirs, + without disabling remote sources. + - support "revoked" items, which cause the client to + downgrade/upgrade + - introduce per-user version that can update per-user installed dDIs + - make transport pluggable, so people can plug casync or + similar behind it, instead of http. + +- sysusers: allow specifying a path to an inode *and* a literal UID in the UID + column, so that if the inode exists it is used, and if not the literal UID is + used. Use this for services such as the imds one, which run under their own + UID in the initrd, and whose data should survive to the host, properly owned. + +- teach ConditionKernelCommandLine= globs or regexes (in order to match foobar={no,0,off}) + +- teach nspawn/machined a new bus call/verb that gets you a + shell in containers that have no sensible pid1, via joining the container, + and invoking a shell directly. Then provide another new bus call/vern that is + somewhat automatic: if we detect that pid1 is running and fully booted up we + provide a proper login shell, otherwise just a joined shell. Then expose that + as primary way into the container. -* PID 1 should send out sd_notify("WATCHDOG=1") messages (for usage in the --user mode, and when run via nspawn) +- teach parse_timestamp() timezones like the calendar spec already knows it -* there's probably something wrong with having user mounts below /sys, - as we have for debugfs. for example, src/core/mount.c handles mounts - prefixed with /sys generally special. - https://lists.freedesktop.org/archives/systemd-devel/2015-June/032962.html +- teach systemd-nspawn the boot assessment logic: hook up vpick's try counters + with success notifications from nspawn payloads. When this is enabled, + automatically support reverting back to older OS version images if newer ones + fail to boot. -* fstab-generator: default to tmpfs-as-root if only usr= is specified on the kernel cmdline +- **test/:** + - add unit tests for config_parse_device_allow() -* docs: bring https://systemd.io/MY_SERVICE_CANT_GET_REALTIME up to date +- The bind(AF_UNSPEC) construct (for resetting sockets to their initial state) + should be blocked in many cases because it punches holes in many sandboxes. -* add a job mode that will fail if a transaction would mean stopping - running units. Use this in timedated to manage the NTP service - state. - https://lists.freedesktop.org/archives/systemd-devel/2015-April/030229.html +- the pub/priv key pair generated on the TPM2 should probably also be one you + can use to get a remote attestation quote. -* The udev blkid built-in should expose a property that reflects +- The udev blkid built-in should expose a property that reflects whether media was sensed in USB CF/SD card readers. This should then be used to control SYSTEMD_READY=1/0 so that USB card readers aren't picked up by systemd unless they contain a medium. This would mirror the behaviour we already have for CD drives. -* hostnamectl: show root image uuid - -* Find a solution for SMACK capabilities stuff: - https://lists.freedesktop.org/archives/systemd-devel/2014-December/026188.html +- There's currently no way to cancel fsck (used to be possible via C-c or c on the console) -* synchronize console access with BSD locks: - https://lists.freedesktop.org/archives/systemd-devel/2014-October/024582.html +- there's probably something wrong with having user mounts below /sys, + as we have for debugfs. for example, src/core/mount.c handles mounts + prefixed with /sys generally special. + https://lists.freedesktop.org/archives/systemd-devel/2015-June/032962.html -* as soon as we have sender timestamps, revisit coalescing multiple parallel daemon reloads: - https://lists.freedesktop.org/archives/systemd-devel/2014-December/025862.html +- think about requeuing jobs when daemon-reload is issued? use case: + the initrd issues a reload after fstab from the host is accessible + and we might want to requeue the mounts local-fs acquired through + that automatically. -* figure out when we can use the coarse timers +- **timer units:** + - timer units should get the ability to trigger when DST changes + - Modulate timer frequency based on battery state -* maybe allow timer units with an empty Units= setting, so that they - can be used for resuming the system but nothing else. +- timesyncd: add ugly bus calls to set NTP servers per-interface, for usage by NM -* what to do about udev db binary stability for apps? (raw access is not an option) +- timesyncd: when saving/restoring clock try to take boot time into account. + Specifically, along with the saved clock, store the current boot ID. When + starting, check if the boot id matches. If so, don't do anything (we are on + the same boot and clock just kept running anyway). If not, then read + CLOCK_BOOTTIME (which started at boot), and add it to the saved clock + timestamp, to compensate for the time we spent booting. If EFI timestamps are + available, also include that in the calculation. With this we'll then only + miss the time spent during shutdown after timesync stopped and before the + system actually reset. -* exponential backoff in timesyncd when we cannot reach a server +- tiny varlink service that takes a fd passed in and serves it via http. Then + make use of that in networkd, and expose some EFI binary of choice for + DHCP/HTTP base EFI boot. -* timesyncd: add ugly bus calls to set NTP servers per-interface, for usage by NM +- **tmpfiles:** + - allow time-based cleanup in r and R too + - instead of ignoring unknown fields, reject them. + - creating new directories/subvolumes/fifos/device nodes + should not follow symlinks. None of the other adjustment or creation + calls follow symlinks. + - teach tmpfiles.d q/Q logic something sensible in the context of XFS/ext4 + project quota + - teach tmpfiles.d m/M to move / atomic move + symlink old -> new + - add new line type for setting btrfs subvolume attributes (i.e. rw/ro) + - tmpfiles: add new line type for setting fcaps + - add -n as shortcut for --dry-run in tmpfiles & sysusers & possibly other places + - add new line type for moving files from some source dir to some + target dir. then use that to move sysexts/confexts and stuff from initrd + tmpfs to /run/, so that host can pick things up. -* add systemd.abort_on_kill or some other such flag to send SIGABRT instead of SIGKILL - (throughout the codebase, not only PID1) +- To mimic the new tpm2-measure-pcr= crypttab option and tpm2-measure-nvpcr= + veritytab option, add the same to integritytab (measuring the HMAC key if one + is used) -* drop nss-myhostname in favour of nss-resolve? +- tpm2-setup: reboot if we detect SRK changed -* resolved: - - mDNS/DNS-SD - - service registration - - service/domain/types browsing - - avahi compat - - DNS-SD service registration from socket units - - resolved should optionally register additional per-interface LLMNR - names, so that for the container case we can establish the same name - (maybe "host") for referencing the server, everywhere. - - allow clients to request DNSSEC for a single lookup even if DNSSEC is off (?) +- tpm2: add (optional) support for generating a local signing key from PCR 15 + state. use private key part to sign PCR 7+14 policies. stash signatures for + expected PCR7+14 policies in EFI var. use public key part in disk encryption. + generate new sigs whenever db/dbx/mok/mokx gets updated. that way we can + securely bind against SecureBoot/shim state, without having to renroll + everything on each update (but we still have to generate one sig on each + update, but that should be robust/idempotent). needs rollback protection, as + usual. -* refcounting in sd-resolve is borked +- TPM2: auto-reenroll in cryptsetup, as fallback for hosed firmware upgrades + and such -* add new gpt type for btrfs volumes +- track the per-service PAM process properly (i.e. as an additional control + process), so that it may be queried on the bus and everything. -* generator that automatically discovers btrfs subvolumes, identifies their purpose based on some xattr on them. +- transient units: don't bother with actually setting unit properties, we + reload the unit file anyway -* a way for container managers to turn off getty starting via $container_headless= or so... +- **transient units:** + - add field to transient units that indicate whether systemd or somebody else saves/restores its settings, for integration with libvirt -* figure out a nice way how we can let the admin know what child/sibling unit causes cgroup membership for a specific unit +- Turn systemd-networkd-wait-online into a small varlink service that people + can talk to and specify exactly what to wait for via a method call, and get a + response back once that level of "online" is reached. -* For timer units: add some mechanisms so that timer units that trigger immediately on boot do not have the services - they run added to the initial transaction and thus confuse Type=idle. - -* add bus api to query unit file's X fields. - -* gpt-auto-generator: - - Make /home automount rather than mount? - -* add generator that pulls in systemd-network from containers when - CAP_NET_ADMIN is set, more than the loopback device is defined, even - when it is otherwise off - -* MessageQueueMessageSize= (and suchlike) should use parse_iec_size(). - -* implement Distribute= in socket units to allow running multiple - service instances processing the listening socket, and open this up - for ReusePort= - -* cgroups: - - implement per-slice CPUFairScheduling=1 switch - - introduce high-level settings for RT budget, swappiness - - how to reset dynamically changed unit cgroup attributes sanely? - - when reloading configuration, apply new cgroup configuration - - when recursively showing the cgroup hierarchy, optionally also show - the hierarchies of child processes - - add settings for cgroup.max.descendants and cgroup.max.depth, - maybe use them for user@.service - -* transient units: - - add field to transient units that indicate whether systemd or somebody else saves/restores its settings, for integration with libvirt - -* libsystemd-journal, libsystemd-login, libudev: add calls to easily attach these objects to sd-event event loops - -* be more careful what we export on the bus as (usec_t) 0 and (usec_t) -1 - -* rfkill,backlight: we probably should run the load tools inside of the udev rules so that the state is properly initialized by the time other software sees it - -* If we try to find a unit via a dangling symlink, generate a clean - error. Currently, we just ignore it and read the unit from the search - path anyway. - -* refuse boot if /usr/lib/os-release is missing or /etc/machine-id cannot be set up - -* man: the documentation of Restart= currently is very misleading and suggests the tools from ExecStartPre= might get restarted. - -* There's currently no way to cancel fsck (used to be possible via C-c or c on the console) - -* add option to sockets to avoid activation. Instead just drop packets/connections, see http://cyberelk.net/tim/2012/02/15/portreserve-systemd-solution/ - -* make sure systemd-ask-password-wall does not shutdown systemd-ask-password-console too early +- tweak journald context caching. In addition to caching per-process attributes + keyed by PID, cache per-cgroup attributes (i.e. the various xattrs we read) + keyed by cgroup path, and guarded by ctime changes. This should provide us + with a nice speed-up on services that have many processes running in the same + cgroup. -* verify that the AF_UNIX sockets of a service in the fs still exist - when we start a service in order to avoid confusion when a user - assumes starting a service is enough to make it accessible +- tweak sd-event's child watching: keep a prioq of children to watch and use + waitid() only on the children with the highest priority until one is waitable + and ignore all lower-prio ones from that point on -* Make it possible to set the keymap independently from the font on - the kernel cmdline. Right now setting one resets also the other. +- **udev-link-config:** + - Make sure ID_PATH is always exported and complete for + network devices where possible, so we can safely rely + on Path= matching -* and a dbus call to generate target from current state +- **udev:** + - move to LGPL + - kill scsi_id + - add trigger --subsystem-match=usb/usb_device device + - reimport udev db after MOVE events for devices without dev_t + - re-enable ProtectClock= once only cgroupsv2 is supported. + See f562abe2963bad241d34e0b308e48cf114672c84. -* investigate whether the gnome pty helper should be moved into systemd, to provide cgroup support. +- **udevadm: to make symlink querying with udevadm nicer:** + - do not enable the pager for queries like 'udevadm info -q symlink -r' + - add mode with newlines instead of spaces (for grep)? -* dot output for --test showing the 'initial transaction' +- udevd: extend memory pressure logic: also kill any idle worker processes -* be able to specify a forced restart of service A where service B depends on, in case B - needs to be auto-respawned? +- unify how blockdev_get_root() and sysupdate find the default root block device -* pid1: - - When logging about multiple units (stopping BoundTo units, conflicts, etc.), - log both units as UNIT=, so that journalctl -u triggers on both. - - generate better errors when people try to set transient properties - that are not supported... - https://lists.freedesktop.org/archives/systemd-devel/2015-February/028076.html - - recreate systemd's D-Bus private socket file on SIGUSR2 - - when we automatically restart a service, ensure we restart its rdeps, too. - - hide PAM options in fragment parser when compile time disabled - - Support --test based on current system state - - If we show an error about a unit (such as not showing up) and it has no Description string, then show a description string generated form the reverse of unit_name_mangle(). - - after deserializing sockets in socket.c we should reapply sockopts and things - - drop PID 1 reloading, only do reexecing (difficult: Reload() - currently is properly synchronous, Reexec() is weird, because we - cannot delay the response properly until we are back, so instead of - being properly synchronous we just keep open the fd and close it - when done. That means clients do not get a successful method reply, - but much rather a disconnect on success. - - when breaking cycles drop services from /run first, then from /etc, then from /usr - - when a bus name of a service disappears from the bus make sure to queue further activation requests - - maybe introduce CoreScheduling=yes/no to optionally set a PR_SCHED_CORE cookie, so that all - processes in a service's cgroup share the same cookie and are guaranteed not to share SMT cores - with other units https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/hw-vuln/core-scheduling.rst - - ExtensionImages= deduplication for services is currently only applied to disk images without GPT envelope. - This should be extended to work with proper DDIs too, as well as directory confext/sysext. Moreover, - system-wide confext/sysext should support this too. - - Pin the mount namespace via FD by sending it back from sd-exec to the manager, and use it - for live mounting, instead of doing it via PID +- **unify on openssl:** + - figure out what to do about libmicrohttpd: + - 1.x is stable and has a hard dependency on gnutls + - 2.x is in development and has openssl support + - Worth testing against 2.x in our CI? + - port fsprg over to openssl -* unit files: +- **unit files:** - allow port=0 in .socket units - maybe introduce ExecRestartPre= - implement Register= switch in .socket units to enable registration @@ -2296,601 +2718,214 @@ Features: - Allow multiple ExecStart= for all Type= settings, so that we can cover rescue.service nicely - add verification of [Install] section to systemd-analyze verify -* timer units: - - timer units should get the ability to trigger when DST changes - - Modulate timer frequency based on battery state - -* add libsystemd-password or so to query passwords during boot using the password agent logic - -* clean up date formatting and parsing so that all absolute/relative timestamps we format can also be parsed - -* on shutdown: move utmp, wall, audit logic all into PID 1 (or logind?) - -* make repeated alt-ctrl-del presses printing a dump - -* currently x-systemd.timeout is lost in the initrd, since crypttab is copied into dracut, but fstab is not - -* add a pam module that on password changes updates any LUKS slot where the password matches - -* test/: - - add unit tests for config_parse_device_allow() - -* seems that when we follow symlinks to units we prefer the symlink - destination path over /etc and /usr. We should not do that. Instead - /etc should always override /run+/usr and also any symlink - destination. +- **unit install:** + - "systemctl mask" should find all names by which a unit is accessible + (i.e. by scanning for symlinks to it) and link them all to /dev/null -* when isolating, try to figure out a way how we implicitly can order - all units we stop before the isolating unit... +- update HACKING.md to suggest developing systemd with the ideas from: + https://0pointer.net/blog/testing-my-system-code-in-usr-without-modifying-usr.html + https://0pointer.net/blog/running-an-container-off-the-host-usr.html -* teach ConditionKernelCommandLine= globs or regexes (in order to match foobar={no,0,off}) +- use name_to_handle_at() with AT_HANDLE_FID instead of .st_ino (inode + number) for identifying inodes, for example in copy.c when finding hard + links, or loop-util.c for tracking backing files, and other places. -* Add ConditionDirectoryNotEmpty= handle non-absoute paths as a search path or add - ConditionConfigSearchPathNotEmpty= or different syntax? See the discussion starting at - https://github.com/systemd/systemd/pull/15109#issuecomment-607740136. +- use sd-event ratelimit feature optionally for journal stream clients that log + too much -* BootLoaderSpec: define a way how an installer can figure out whether a BLS - compliant boot loader is installed. +- userdb: allow existence checks -* BootLoaderSpec: document @saved pseudo-entry, update mention in BLI +- userdb: when synthesizing NSS records, pick "best" password from defined + passwords, not just the first. i.e. if there are multiple defined, prefer + unlocked over locked and prefer non-empty over empty. -* think about requeuing jobs when daemon-reload is issued? use case: - the initrd issues a reload after fstab from the host is accessible - and we might want to requeue the mounts local-fs acquired through - that automatically. +- userdbd: implement an additional varlink service socket that provides the + host user db in restricted form, then allow this to be bind mounted into + sandboxed environments that want the host database in minimal form. All + records would be stripped of all meta info, except the basic UID/name + info. Then use this in portabled environments that do not use PrivateUsers=1. -* systemd-inhibit: make taking delay locks useful: support sending SIGINT or SIGTERM on PrepareForSleep() +- validatefs: validate more things: check if image id + os id of initrd match + target mount, so that we refuse early any attempts to boot into different + images with the wrong kernels. check min/max kernel version too. all encoded + via xattrs in the target fs. -* remove any syslog support from log.c — we probably cannot do this before split-off udev is gone for good +- Varlinkification of the following command line tools, to open them up to + other programs via IPC: + - coredumpctl + - systemd-bless-boot + - systemd-measure + - systemd-cryptenroll (to allow UIs to enroll FIDO2 keys and such) + - systemd-dissect + - systemd-sysupdate + - systemd-analyze + - kernel-install + - systemd-mount (with PK so that desktop environments could use it to mount disks) -* shutdown logging: store to EFI var, and store to USB stick? +- verify that the AF_UNIX sockets of a service in the fs still exist + when we start a service in order to avoid confusion when a user + assumes starting a service is enough to make it accessible -* merge unit_kill_common() and unit_kill_context() +- vmspawn switch default swtpm PCR bank to SHA384-only (away from SHA256), at + least on 64bit archs, simply because SHA384 is typically double the hashing + speed than SHA256 on 64bit archs (since based on 64bit words unlike SHA256 + which uses 32bit words). -* add a dependency on standard-conf.xml and other included files to man pages +- **vmspawn:** + - --ephemeral support + - --read-only support + - automatically suspend/resume the VM if the host suspends. Use logind + suspend inhibitor to implement this. request clean suspend by generating + suspend key presses. + - support for "real" networking via "-n" and --network-bridge= + - translate SIGTERM to clean ACPI shutdown event + - implement hotkeys ^]^]r and ^]^]p like nspawn -* MountFlags=shared acts as MountFlags=slave right now. +- **vmspawn disk hotplug:** + - virtio-blk-pci — the simplest path. Each disk is an independent PCI + device. QMP sequence (two steps): + + blockdev-add {driver: "raw", node-name: "disk1", + file: {driver: "file", filename: "/path/to/img"}} + device_add {driver: "virtio-blk-pci", id: "disk1", drive: "disk1"} + + Removal (three steps): + + device_del {id: "disk1"} + ... wait for DEVICE_DELETED event (guest acknowledges unplug) ... + blockdev-del {node-name: "disk1"} + + Works on both i440fx (legacy PCI) and q35 (PCIe) machine types. PCI + address auto-assigned by QEMU — no topology pre-configuration needed. + Each disk independently hotpluggable. Guest sees a virtio block + device (/dev/vdX). Well-tested path — used by libvirt, Incus, and all + major VM managers. No special boot-time setup required. + + - NVMe — two-level model: controller + namespace(s). The controller is + a PCIe device; namespaces live on an internal NVMe bus attached to + the controller. Key limitation: namespaces are NOT hotpluggable — + TYPE_NVME_BUS has no HotplugHandler, so device_add of nvme-ns at + runtime fails with "Bus does not support hotplugging". The only + option is hotplugging the entire controller, which embeds one + namespace via its "drive" property: + + blockdev-add {driver: "raw", node-name: "disk1", + file: {driver: "file", filename: "/path/to/img"}} + device_add {driver: "nvme", id: "disk1", drive: "disk1", serial: "disk1"} + + Same two-step pattern as virtio-blk, with these limitations: + + 1. PCIe-only (implements INTERFACE_PCIE_DEVICE). Does not work on + i440fx. Requires q35 or virt (aarch64). + 2. Requires pre-configured PCIe root ports that exist at boot. + Without them, device_add fails with "no slot/function available". + vmspawn would need to create empty root ports at QEMU startup to + reserve hotplug slots. + 3. No namespace-level granularity. Each hotplugged disk burns a full + PCIe slot (controller + one namespace). Cannot add multiple + namespaces to a single controller at runtime. + 4. Serial property required (up to 20 chars). virtio-blk does not + require it. + + - virtio-scsi — shared virtio-scsi-pci controller with individual + scsi-hd devices attached. Incus uses this as its default bus. The + controller must exist at boot, but individual disks (LUNs) can be + hotplugged onto it without burning PCI slots. Scales better than + virtio-blk when many disks are needed, but adds complexity + (controller management, LUN assignment). + +- **vmspawn AcquireQMP():** implement as id-rewriting proxy with FD + passing. vmspawn acts as a QMP multiplexer. When a client calls + AcquireQMP(): + + 1. Create socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0). + 2. Return one end to the client via sd_varlink_push_fd() + + sd_varlink_reply(). + 3. Add an sd-event I/O source on vmspawn's end for the client socket. + 4. Send a synthetic QMP greeting on the client socket, handle + qmp_capabilities locally. Maybe we don't even need that and just + document that it's a fully initialized connection. + 5. For client commands: read from the client socket, rewrite id to + vmspawn's internal counter (store mapping internal_id -> + (client_fd, original_id_json_variant)), forward to QEMU. + 6. For QEMU responses: match internal id, look up original client id, + rewrite back, send to the correct client socket. + 7. Broadcast QMP events (no id) to all AcquireQMP clients AND to + SubscribeEvents subscribers. + 8. On client EOF: remove the I/O source, clean up id mappings. + + This keeps vmspawn in full control of the QMP connection — VMControl + handlers and multiple AcquireQMP clients can coexist without id + collisions. The server needs SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT + (already set in machined's pattern). + + AcquireQMP() also requires server-side Varlink protocol upgrades. + mvo's WIP branch: + + +- we probably needs .pcrpkeyrd or so as additional PE section in UKIs, + which contains a separate public key for PCR values that only apply in the + initrd, i.e. in the boot phase "enter-initrd". Then, consumers in userspace + can easily bind resources to just the initrd. Similar, maybe one more for + "enter-initrd:leave-initrd" for resources that shall be accessible only + before unprivileged user code is allowed. (we only need this for .pcrpkey, + not for .pcrsig, since the latter is a list of signatures anyway). With that, + when you enroll a LUKS volume or similar, pick either the .pcrkey (for + coverage through all phases of the boot, but excluding shutdown), the + .pcrpkeyrd (for coverage in the initrd only) and .pcrpkeybt (for coverage + until users are allowed to log in). -* properly handle loop back mounts via fstab, especially regards to fsck/passno +- we probably should have some infrastructure to acquire sysexts with + drivers/firmware for local hardware automatically. Idea: reuse the modalias + logic of the kernel for this: make the main OS image install a hwdb file + that matches against local modalias strings, and adds properties to relevant + devices listing names of sysexts needed to support the hw. Then provide some + tool that goes through all devices and tries to acquire/download the + specified images. -* initialize the hostname from the fs label of /, if /etc/hostname does not exist? +- We should probably replace /etc/rc.d/README with a symlink to doc + content. After all it is constant vendor data. -* sd-bus: - - EBADSLT handling - - GetAllProperties() on a non-existing object does not result in a failure currently - - port to sd-resolve for connecting to TCP dbus servers - - see if we can introduce a new sd_bus_get_owner_machine_id() call to retrieve the machine ID of the machine of the bus itself - - see if we can drop more message validation on the sending side - - add API to clone sd_bus_message objects - - longer term: priority inheritance - - dbus spec updates: - - NameLost/NameAcquired obsolete - - path escaping - - update systemd.special(7) to mention that dbus.socket is only about the compatibility socket now +- We should start measuring all services, containers, and system extensions we + activate. probably into PCR 13. i.e. add --tpm2-measure-pcr= or so to + systemd-nspawn, and MeasurePCR= to unit files. Should contain a measurement + of the activated configuration and the image that is being activated (in case + verity is used, hash of the root hash). -* sd-event - - allow multiple signal handlers per signal? - - document chaining of signal handler for SIGCHLD and child handlers - - define more intervals where we will shift wakeup intervals around in, 1h, 6h, 24h, ... - - maybe support iouring as backend, so that we allow hooking read and write - operations instead of IO ready events into event loops. See considerations - here: - http://blog.vmsplice.net/2020/07/rethinking-event-loop-integration-for.html +- what to do about udev db binary stability for apps? (raw access is not an option) -* dbus: when a unit failed to load (i.e. is in UNIT_ERROR state), we - should be able to safely try another attempt when the bus call LoadUnit() is invoked. +- when importing an fs tree with machined, complain if image is not an OS -* document org.freedesktop.MemoryAllocation1 +- when importing an fs tree with machined, optionally apply userns-rec-chown -* maybe do not install getty@tty1.service symlink in /etc but in /usr? +- when isolating, try to figure out a way how we implicitly can order + all units we stop before the isolating unit... -* print a nicer explanation if people use variable/specifier expansion in ExecStart= for the first word +- when killing due to service watchdog timeout maybe detect whether target + process is under ptracing and then log loudly and continue instead. -* mount: turn dependency information from /proc/self/mountinfo into dependency information between systemd units. +- when mounting disk images: if IMAGE_ID/IMAGE_VERSION is set in os-release + data in the image, make sure the image filename actually matches this, so + that images cannot be misused. -* EFI: - - honor language efi variables for default language selection (if there are any?) - - honor timezone efi variables for default timezone selection (if there are any?) -* bootctl - - recognize the case when not booted on EFI +- when no locale is configured, default to UEFI's PlatformLang variable -* bootctl: - - show whether UEFI audit mode is available - - teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation - - teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host +- when switching root from initrd to host, set the machine_id env var so that + if the host has no machine ID set yet we continue to use the random one the + initrd had set. -* logind: - - logind: optionally, ignore idle-hint logic for autosuspend, block suspend as long as a session is around - - logind: wakelock/opportunistic suspend support - - Add pretty name for seats in logind - - logind: allow showing logout dialog from system? - - add Suspend() bus calls which take timestamps to fix double suspend issues when somebody hits suspend and closes laptop quickly. - - if pam_systemd is invoked by su from a process that is outside of a - any session we should probably just become a NOP, since that's - usually not a real user session but just some system code that just - needs setuid(). - - logind: make the Suspend()/Hibernate() bus calls wait for the for - the job to be completed. before returning, so that clients can wait - for "systemctl suspend" to finish to know when the suspending is - complete. - - logind: when the power button is pressed short, just popup a - logout dialog. If it is pressed for 1s, do the usual - shutdown. Inspiration are Macs here. - - expose "Locked" property on logind session objects - - maybe allow configuration of the StopTimeout for session scopes - - rename session scope so that it includes the UID. THat way - the session scope can be arranged freely in slices and we don't have - make assumptions about their slice anymore. - - follow PropertiesChanged state more closely, to deal with quick logouts and - relogins - - (optionally?) spawn seat-manager@$SEAT.service whenever a seat shows up that as CanGraphical set +- when systemd-sysext learns mutable /usr/ (and systemd-confext mutable /etc/) + then allow them to store the result in a .v/ versioned subdir, for some basic + snapshot logic -* move multiseat vid/pid matches from logind udev rule to hwdb +- when we detect that there are waiting jobs but no running jobs, do something -* delay activation of logind until somebody logs in, or when /dev/tty0 pulls it - in or lingering is on (so that containers don't bother with it until PAM is used). also exit-on-idle +- whenever we receive fds via SCM_RIGHTS make sure none got dropped due to the + reception limit the kernel silently enforces. -* journal: - - consider introducing implicit _TTY= + _PPID= + _EUID= + _EGID= + _FSUID= + _FSGID= fields - - journald: also get thread ID from client, plus thread name - - journal: when waiting for journal additions in the client always sleep at least 1s or so, in order to minimize wakeups - - add API to close/reopen/get fd for journal client fd in libsystemd-journal. - - fall back to /dev/log based logging in libsystemd-journal, if we cannot log natively? - - declare the local journal protocol stable in the wiki interface chart - - sd-journal: speed up sd_journal_get_data() with transparent hash table in bg - - journald: when dropping msgs due to ratelimit make sure to write - "dropped %u messages" not only when we are about to print the next - message that works, but already after a short timeout - - check if we can make journalctl by default use --follow mode inside of less if called without args? - - maybe add API to send pairs of iovecs via sd_journal_send - - journal: add a setgid "systemd-journal" utility to invoke from libsystemd-journal, which passes fds via STDOUT and does PK access - - journalctl: support negative filtering, i.e. FOOBAR!="waldo", - and !FOOBAR for events without FOOBAR. - - journal: store timestamp of journal_file_set_offline() in the header, - so it is possible to display when the file was last synced. - - journal-send.c, log.c: when the log socket is clogged, and we drop, count this and write a message about this when it gets unclogged again. - - journal: find a way to allow dropping history early, based on priority, other rules - - journal: When used on NFS, check payload hashes - - journald: add kernel cmdline option to disable ratelimiting for debug purposes - - refuse taking lower-case variable names in sd_journal_send() and friends. - - journald: we currently rotate only after MaxUse+MaxFilesize has been reached. - - journal: deal nicely with byte-by-byte copied files, especially regards header - - journal: sanely deal with entries which are larger than the individual file size, but where the components would fit - - Replace utmp, wtmp, btmp, and lastlog completely with journal - - journalctl: instead --after-cursor= maybe have a --cursor=XYZ+1 syntax? - - when a kernel driver logs in a tight loop, we should ratelimit that too. - - journald: optionally, log debug messages to /run but everything else to /var - - journald: when we drop syslog messages because the syslog socket is - full, make sure to write how many messages are lost as first thing - to syslog when it works again. - - journald: allow per-priority and per-service retention times when rotating/vacuuming - - journald: make use of uid-range.h to manage uid ranges to split - journals in. - - journalctl: add the ability to look for the most recent process of a binary. - journalctl /usr/bin/X11 --invocation=-1 - - systemctl: change 'status' to show logs for the last invocation, not a fixed - number of lines - - improve journalctl performance by loading journal files - lazily. Encode just enough information in the file name, so that we - do not have to open it to know that it is not interesting for us, for - the most common operations. - - man: document that corrupted journal files is nothing to act on - - rework journald sigbus stuff to use mutex - - Set RLIMIT_NPROC for systemd-journal-xyz, and all other of our - services that run under their own user ids, and use User= (but only - in a world where userns is ubiquitous since otherwise we cannot - invoke those daemons on the host AND in a container anymore). Also, - if LimitNPROC= is used without User= we should warn and refuse - operation. - - journalctl --verify: don't show files that are currently being - written to as FAIL, but instead show that they are being written to. - - add journalctl -H that talks via ssh to a remote peer and passes through - binary logs data - - add a version of --merge which also merges /var/log/journal/remote - - journalctl: -m should access container journals directly by enumerating - them via machined, and also watch containers coming and going. - Benefit: nspawn --ephemeral would start working nicely with the journal. - - assign MESSAGE_ID to log messages about failed services - - check if loop in decompress_blob_xz() is necessary - -* journald: support RFC3164 fully for the incoming syslog transport, see - https://github.com/systemd/systemd/issues/19251#issuecomment-816601955 - -* Hook up journald's FSS logic with TPM2: seal the verification disk by - time-based policy, so that the verification key can remain on host and ve - validated via TPM. - -* rework journalctl -M to be based on a machined method that generates a mount - fd of the relevant journal dirs in the container with uidmapping applied to - allow the host to read it, while making everything read-only. - -* journald: add varlink service that allows subscribing to certain log events, - for example matching by message ID, or log level returns a list of journal - cursors as they happen. - -* journald: also collect CLOCK_BOOTTIME timestamps per log entry. Then, derive - "corrected" CLOCK_REALTIME information on display from that and the timestamp - info of the newest entry of the specific boot (as identified by the boot - ID). This way, if a system comes up without a valid clock but acquires a - better clock later, we can "fix" older entry timestamps on display, by - calculating backwards. We cannot use CLOCK_MONOTONIC for this, since it does - not account for suspend phases. This would then also enable us to correct the - kmsg timestamping we consume (where we erroneously assume the clock was in - CLOCK_MONOTONIC, but it actually is CLOCK_BOOTTIME as per kernel). - -* in journald, write out a recognizable log record whenever the system clock is - changed ("stepped"), and in timesyncd whenever we acquire an NTP fix - ("slewing"). Then, in journalctl for each boot time we come across, find - these records, and use the structured info they include to display - "corrected" wallclock time, as calculated from the monotonic timestamp in the - log record, adjusted by the delta declared in the structured log record. - -* in journald: whenever we start a new journal file because the boot ID - changed, let's generate a recognizable log record containing info about old - and new ID. Then, when displaying log stream in journalctl look for these - records, to be able to order them. - -* journald: generate recognizable log events whenever we shutdown journald - cleanly, and when we migrate run → var. This way tools can verify that a - previous boot terminated cleanly, because either of these two messages must - be safely written to disk, then. - -* hook up journald with TPMs? measure new journal records to the TPM in regular - intervals, validate the journal against current TPM state with that. (taking - inspiration from IMA log) - -* sd-journal puts a limit on parallel journal files to view at once. journald - should probably honour that same limit (JOURNAL_FILES_MAX) when vacuuming to - ensure we never generate more files than we can actually view. - -* bsod: maybe use graphical mode. Use DRM APIs directly, see - https://github.com/dvdhrm/docs/blob/master/drm-howto/modeset.c for an example - for doing that. - -* maybe implicitly attach monotonic+realtime timestamps to outgoing messages in - log.c and sd-journal-send - -* journalctl/timesyncd: whenever timesyncd acquires a synchronization from NTP, - create a structured log entry that contains boot ID, monotonic clock and - realtime clock (I mean, this requires no special work, as these three fields - are implicit). Then in journalctl when attempting to display the realtime - timestamp of a log entry, first search for the closest later log entry - of this kinda that has a matching boot id, and convert the monotonic clock - timestamp of the entry to the realtime clock using this info. This way we can - retroactively correct the wallclock timestamps, in particular for systems - without RTC, i.e. where initially wallclock timestamps carry rubbish, until - an NTP sync is acquired. - -* introduce per-unit (i.e. per-slice, per-service) journal log size limits. - -* journald: do journal file writing out-of-process, with one writer process per - client UID, so that synthetic hash table collisions can slow down a specific - user's journal stream down but not the others. - -* tweak journald context caching. In addition to caching per-process attributes - keyed by PID, cache per-cgroup attributes (i.e. the various xattrs we read) - keyed by cgroup path, and guarded by ctime changes. This should provide us - with a nice speed-up on services that have many processes running in the same - cgroup. - -* maybe add call sd_journal_set_block_timeout() or so to set SO_SNDTIMEO for - the sd-journal logging socket, and, if the timeout is set to 0, sets - O_NONBLOCK on it. That way people can control if and when to block for - logging. - -* journalctl: make sure -f ends when the container indicated by -M terminates - -* journald: sigbus API via a signal-handler safe function that people may call - from the SIGBUS handler - -* add a test if all entries in the catalog are properly formatted. - (Adding dashes in a catalog entry currently results in the catalog entry - being silently skipped. journalctl --update-catalog must warn about this, - and we should also have a unit test to check that all our message are OK.) - -* build short web pages out of each catalog entry, build them along with man - pages, and include hyperlinks to them in the journal output - -* homed: - - when user tries to log into record signed by unrecognized key, automatically add key to our chain after polkit auth - - rollback when resize fails mid-operation - - GNOME's side for forget key on suspend (requires rework so that lock screen runs outside of uid) - - update LUKS password on login if we find there's a password that unlocks the JSON record but not the LUKS device. - - create on activate? - - properties: icon url?, administrator bool (which translates to 'wheel' membership)?, address?, telephone?, vcard?, samba stuff?, parental controls? - - communicate clearly when usb stick is safe to remove. probably involves - beefing up logind to make pam session close hook synchronous and wait until - systemd --user is shut down. - - logind: maybe keep a "busy fd" as long as there's a non-released session around or the user@.service - - maybe make automatic, read-only, time-based reflink-copies of LUKS disk - images (and btrfs snapshots of subvolumes) (think: time machine) - - distinguish destroy / remove (i.e. currently we can unregister a user, unregister+remove their home directory, but not just remove their home directory) - - fingerprint authentication, pattern authentication, … - - make sure "classic" user records can also be managed by homed - - make size of $XDG_RUNTIME_DIR configurable in user record - - move acct mgmt stuff from pam_systemd_home to pam_systemd? - - when "homectl --pkcs11-token-uri=" is used, synthesize ssh-authorized-keys records for all keys we have private keys on the stick for - - make slice for users configurable (requires logind rework) - - logind: populate auto-login list bus property from PKCS#11 token - - when determining state of a LUKS home directory, check DM suspended sysfs file - - when homed is in use, maybe start the user session manager in a mount namespace with MS_SLAVE, - so that mounts propagate down but not up - eg, user A setting up a backup volume - doesn't mean user B sees it - - use credentials logic/TPM2 logic to store homed signing key - - permit multiple user record signing keys to be used locally, and pick - the right one for signing records automatically depending on a pre-existing - signature - - add a way to "take possession" of a home directory, i.e. strip foreign signatures - and insert a local signature instead. - - as an extension to the directory+subvolume backend: if located on - especially marked fs, then sync down password into LUKS header of that fs, - and always verify passwords against it too. Bootstrapping is a problem - though: if no one is logged in (or no other user even exists yet), how do you - unlock the volume in order to create the first user and add the first pw. - - support new FS_IOC_ADD_ENCRYPTION_KEY ioctl for setting up fscrypt - - maybe pre-create ~/.cache as subvol so that it can have separate quota - easily? - - store PKCS#11 + FIDO2 token info in LUKS2 header, compatible with - systemd-cryptsetup, so that it can unlock homed volumes - - maybe make all *.home files owned by `systemd-home` user or so, so that we - can easily set overall quota for all users - - on login, if we can't fallocate initially, but rebalance is on, then allow - login in discard mode, then immediately rebalance, then turn off discard - - add "homectl unbind" command to remove local user record of an inactive - home dir - -* add a new switch --auto-definitions=yes/no or so to systemd-repart. If - specified, synthesize a definition automatically if we can: enlarge last - partition on disk, but only if it is marked for growing and not read-only. - -* systemd-repart: read LUKS encryption key from $CREDENTIALS_DIRECTORY - -* systemd-repart: add a switch to factory reset the partition table without - immediately applying the new configuration again. i.e. --factory-reset=leave - or so. (this is useful to factory reset an image, then putting it into - another machine, ensuring that luks key is generated on new machine, not old) - -* systemd-repart: support setting up dm-integrity with HMAC - -* systemd-repart: maybe remove half-initialized image on failure. It fails - if the output file exists, so a repeated invocation will usually fail if - something goes wrong on the way. - -* systemd-repart: by default generate minimized partition tables (i.e. tables - that only cover the space actually used, excluding any free space at the - end), in order to maximize dd'ability. Requires libfdisk work, see - https://github.com/karelzak/util-linux/issues/907 - -* systemd-repart: MBR partition table support. Care needs to be taken regarding - Type=, so that partition definitions can sanely apply to both the GPT and the - MBR case. Idea: accept syntax "Type=gpt:home mbr:0x83" for setting the types - for the two partition types explicitly. And provide an internal mapping so - that "Type=linux-generic" maps to the right types for both partition tables - automatically. - -* systemd-repart: allow sizing partitions as factor of available RAM, so that - we can reasonably size swap partitions for hibernation. - -* systemd-repart: allow boolean option that ensures that if existing partition - doesn't exist within the configured size bounds the whole command fails. This - is useful to implement ESP vs. XBOOTLDR schemes in installers: have one set - of repart files for the case where ESP is large enough and one where it isn't - and XBOOTLDR is added in instead. Then apply the former first, and if it - fails to apply use the latter. - -* systemd-repart: add per-partition option to never reuse existing partition - and always create anew even if matching partition already exists. - -* systemd-repart: add per-partition option to fail if partition already exist, - i.e. is not added new. Similar, add option to fail if partition does not exist yet. - -* systemd-repart: allow disabling growing of specific partitions, or making - them (think ESP: we don't ever want to grow it, since we cannot resize vfat) - Also add option to disable operation via kernel command line. - -* systemd-repart: make it a static checker during early boot for existence and - absence of other partitions for trusted boot environments - -* systemd-repart: add support for SD_GPT_FLAG_GROWFS also on real systems, i.e. - generate some unit to actually enlarge the fs after growing the partition - during boot. - -* systemd-repart: do not print "Successfully resized …" when no change was done. - -* document: - - document that deps in [Unit] sections ignore Alias= fields in - [Install] units of other units, unless those units are disabled - - document that service reload may be implemented as service reexec - - add a man page containing packaging guidelines and recommending usage of things like Documentation=, PrivateTmp=, PrivateNetwork= and ReadOnlyDirectories=/etc /usr. - - document systemd-journal-flush.service properly - - documentation: recommend to connect the timer units of a service to the service via Also= in [Install] - - man: document the very specific env the shutdown drop-in tools live in - - man: add more examples to man pages, - - in particular an example how to do the equivalent of switching runlevels - - man: maybe sort directives in man pages, and take sections from --help and apply them to man too - - document root=gpt-auto properly - -* systemctl: - - add systemctl switch to dump transaction without executing it - - Add a verbose mode to "systemctl start" and friends that explains what is being done or not done - - print nice message from systemctl --failed if there are no entries shown, and hook that into ExecStartPre of rescue.service/emergency.service - - add new command to systemctl: "systemctl system-reexec" which reexecs as many daemons as virtually possible - - systemctl enable: fail if target to alias into does not exist? maybe show how many units are enabled afterwards? - - systemctl: "Journal has been rotated since unit was started." message is misleading - -* introduce an option (or replacement) for "systemctl show" that outputs all - properties as JSON, similar to busctl's new JSON output. In contrast to that - it should skip the variant type string though. - -* Add a "systemctl list-units --by-slice" mode or so, which rearranges the - output of "systemctl list-units" slightly by showing the tree structure of - the slices, and the units attached to them. - -* add "systemctl wait" or so, which does what "systemd-run --wait" does, but - for all units. It should be both a way to pin units into memory as well as a - wait to retrieve their exit data. - -* show whether a service has out-of-date configuration in "systemctl status" by - using mtime data of ConfigurationDirectory=. - -* "systemctl preset-all" should probably order the unit files it - operates on lexicographically before starting to work, in order to - ensure deterministic behaviour if two unit files conflict (like DMs - do, for example) - -* systemctl: if some operation fails, show log output? - -* Add a new verb "systemctl top" - -* unit install: - - "systemctl mask" should find all names by which a unit is accessible - (i.e. by scanning for symlinks to it) and link them all to /dev/null - -* nspawn: - - emulate /dev/kmsg using CUSE and turn off the syslog syscall - with seccomp. That should provide us with a useful log buffer that - systemd can log to during early boot, and disconnect container logs - from the kernel's logs. - - as soon as networkd has a bus interface, hook up --network-interface=, - --network-bridge= with networkd, to trigger netdev creation should an - interface be missing - - a nice way to boot up without machine id set, so that it is set at boot - automatically for supporting --ephemeral. Maybe hash the host machine id - together with the machine name to generate the machine id for the container - - fix logic always print a final newline on output. - https://github.com/systemd/systemd/pull/272#issuecomment-113153176 - - should optionally support receiving WATCHDOG=1 messages from its payload - PID 1... - - optionally automatically add FORWARD rules to iptables whenever nspawn is - running, remove them when shut down. - - add support for sysext extensions, too. i.e. a new --extension= switch that - takes one or more arguments, and applies the extensions already during - startup. - - when main nspawn supervisor process gets suspended due to SIGSTOP/SIGTTOU - or so, freeze the payload too. - - support time namespaces - - on cgroupsv1 issue cgroup empty handler process based on host events, so - that we make cgroup agent logic safe - - add API to invoke binary in container, then use that as fallback in - "machinectl shell" - - make nspawn suitable for shell pipelines: instead of triggering a hangup - when input is finished, send ^D, which synthesizes an EOF. Then wait for - hangup or ^D before passing on the EOF. - - greater control over selinux label? - - support that /proc, /sys/, /dev are pre-mounted - - maybe allow TPM passthrough, backed by swtpm, and measure --image= hash - into its PCR 11, so that nspawn instances can be TPM enabled, and partake - in measurements/remote attestation and such. swtpm would run outside of - control of container, and ideally would itself bind its encryption keys to - host TPM. - - make boot assessment do something sensible in a container. i.e send an - sd_notify() from payload to container manager once boot-up is completed - successfully, and use that in nspawn for dealing with boot counting, - implemented in the partition table labels and directory names. - - optionally set up nftables/iptables routes that forward UDP/TCP traffic on - port 53 to resolved stub 127.0.0.54 - - maybe optionally insert .nspawn file as GPT partition into images, so that - such container images are entirely stand-alone and can be updated as one. - - The subreaper logic we currently have seems overly complex. We should - investigate whether creating the inner child with CLONE_PARENT isn't better. - - Reduce the number of sockets that are currently in use and just rely on one - or two sockets. - -* machined: - - add an API so that libvirt-lxc can inform us about network interfaces being - removed or added to an existing machine - - "machinectl migrate" or similar to copy a container from or to a - difference host, via ssh - - introduce systemd-nspawn-ephemeral@.service, and hook it into - "machinectl start" with a new --ephemeral switch - - "machinectl status" should also show internal logs of the container in - question - - "machinectl history" - - "machinectl diff" - - "machinectl commit" that takes a writable snapshot of a tree, invokes a - shell in it, and marks it read-only after use - -* udev: - - move to LGPL - - kill scsi_id - - add trigger --subsystem-match=usb/usb_device device - - reimport udev db after MOVE events for devices without dev_t - - re-enable ProtectClock= once only cgroupsv2 is supported. - See f562abe2963bad241d34e0b308e48cf114672c84. - -* coredump: - - save coredump in Windows/Mozilla minidump format - - when truncating coredumps, also log the full size that the process had, and make a metadata field so we can report truncated coredumps - - add examples for other distros in PACKAGE_METADATA_FOR_EXECUTABLE_FILES - -* support crash reporting operation modes (https://live.gnome.org/GnomeOS/Design/Whiteboards/ProblemReporting) - -* tmpfiles: - - allow time-based cleanup in r and R too - - instead of ignoring unknown fields, reject them. - - creating new directories/subvolumes/fifos/device nodes - should not follow symlinks. None of the other adjustment or creation - calls follow symlinks. - - teach tmpfiles.d q/Q logic something sensible in the context of XFS/ext4 - project quota - - teach tmpfiles.d m/M to move / atomic move + symlink old -> new - - add new line type for setting btrfs subvolume attributes (i.e. rw/ro) - - tmpfiles: add new line type for setting fcaps - - add -n as shortcut for --dry-run in tmpfiles & sysusers & possibly other places - -* udev-link-config: - - Make sure ID_PATH is always exported and complete for - network devices where possible, so we can safely rely - on Path= matching - -* sd-rtnl: - - add support for more attribute types - - inbuilt piping support (essentially degenerate async)? see loopback-setup.c and other places - -* networkd: - - add more keys to [Route] and [Address] sections - - add support for more DHCPv4 options (and, longer term, other kinds of dynamic config) - - add reduced [Link] support to .network files - - properly handle routerless dhcp leases - - work with non-Ethernet devices - - dhcp: do we allow configuring dhcp routes on interfaces that are not the one we got the dhcp info from? - - the DHCP lease data (such as NTP/DNS) is still made available when - a carrier is lost on a link. It should be removed instantly. - - expose in the API the following bits: - - option 15, domain name - - option 12, hostname and/or option 81, fqdn - - option 123, 144, geolocation - - option 252, configure http proxy (PAC/wpad) - - provide a way to define a per-network interface default metric value - for all routes to it. possibly a second default for DHCP routes. - - allow Name= to be specified repeatedly in the [Match] section. Maybe also - support Name=foo*|bar*|baz ? - - whenever uplink info changes, make DHCP server send out FORCERENEW - -* in networkd, when matching device types, fix up DEVTYPE rubbish the kernel passes to us - -* Figure out how to do unittests of networkd's state serialization - -* dhcp: - - figure out how much we can increase Maximum Message Size - -* dhcp6: - - add functions to set previously stored IPv6 addresses on startup and get - them at shutdown; store them in client->ia_na - - write more test cases - - implement reconfigure support, see 5.3., 15.11. and 22.20. - - implement support for temporary addresses (IA_TA) - - implement dhcpv6 authentication - - investigate the usefulness of Confirm messages; i.e. are there any - situations where the link changes without any loss in carrier detection - or interface down - - some servers don't do rapid commit without a filled in IA_NA, verify - this behavior - - RouteTable= ? - -* shared/wall: Once more programs are taught to prefer sd-login over utmp, - switch the default wall implementation to wall_logind - (https://github.com/systemd/systemd/pull/29051#issuecomment-1704917074) - -* Hook up systemd-journal-upload with RESTART_RESET=1 logic (should probably - be conditioned on the num of successfully uploaded entries?) +- write a document explaining how to write correct udev rules. Mention things + such as: + 1. do not do lists of vid/pid matches, use hwdb for that + 2. add|change action matches are typically wrong, should be != remove + 3. use GOTO, make rules short + 4. people shouldn't try to make rules file non-world-readable diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in index 6f6ca9c4ea851..68ef28d1974c0 100644 --- a/catalog/systemd.catalog.in +++ b/catalog/systemd.catalog.in @@ -1015,3 +1015,12 @@ non-volatile indexes (NV Indexes), could not be initialized. This typically means the persistent NV index memory available on the TPM is taken up by other resources, or is extremely limited. A TPM reset might help recovering space (but will invalidate all TPM bound keys and resources). + +-- 8f07a5b814ca4762b89fcc3082e48aed +Subject: TPM NV index backed PCRs not supported on the local TPM +Defined-By: systemd +Support: %SUPPORT_URL% + +The Trusted Platform Module's (TPM) NV index support is too limited to properly +implement NV index backed additional PCRs. NvPCRs will not be allocated or +initialized, and will not be available on the system. diff --git a/coccinelle/check-pointer-deref.cocci b/coccinelle/check-pointer-deref.cocci new file mode 100644 index 0000000000000..dd058fae3bfcb --- /dev/null +++ b/coccinelle/check-pointer-deref.cocci @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Detect pointer parameters that are dereferenced without a prior NULL check + * or assertion. In systemd style, non-optional pointer parameters should have + * an assert() at the top of the function. + * + * Usage: + * spatch --sp-file coccinelle/check-pointer-deref.cocci --dir src/boot/ + * + * Note: this is a context-mode rule (flags, does not auto-fix). Each flagged + * dereference should be reviewed: if the parameter is never NULL, add + * assert(param) at the top. If it can legitimately be NULL, add an if() guard. + */ +@@ +identifier fn, param; +identifier is_set =~ "_is_set$"; +type T; +position p; +@@ + +fn(..., T *param, ...) { + ... when != assert(param) + when != assert(param != NULL) + when != assert_se(param) + when != assert_se(param != NULL) + when != assert_return(param, ...) + when != ASSERT_PTR(param) + when != POINTER_MAY_BE_NULL(param) + /* Any foo_is_set(param) guard implies param != NULL, since all *_is_set() + * helpers in systemd return false for NULL input. Note the is_set regex + * in identifier. */ + when != assert(is_set(param)) + when != assert_return(is_set(param), ...) + when != \( is_set(param) \) + when != \( param == NULL \| param != NULL \| !param \) +* *param@p + ... +} diff --git a/coccinelle/dup-fcntl.cocci b/coccinelle/dup-fcntl.cocci index 2c87f70dc3d4f..434e48d51415a 100644 --- a/coccinelle/dup-fcntl.cocci +++ b/coccinelle/dup-fcntl.cocci @@ -1,8 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -@@ /* We want to stick with dup() in test-fd-util.c */ -position p : script:python() { p[0].file != "src/test/test-fd-util.c" }; +@ depends on !(file in "src/test/test-fd-util.c") @ expression fd; @@ -- dup@p(fd) +- dup(fd) + fcntl(fd, F_DUPFD, 3) diff --git a/coccinelle/isempty.cocci b/coccinelle/isempty.cocci index 2089970886499..4a266c4195329 100644 --- a/coccinelle/isempty.cocci +++ b/coccinelle/isempty.cocci @@ -1,103 +1,99 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -@@ /* Disable this transformation for the test-string-util.c */ -position p : script:python() { p[0].file != "src/test/test-string-util.c" }; +@ depends on !(file in "src/test/test-string-util.c") @ expression s; @@ ( -- strv_length@p(s) == 0 +- strv_length(s) == 0 + strv_isempty(s) | -- strv_length@p(s) <= 0 +- strv_length(s) <= 0 + strv_isempty(s) | -- strv_length@p(s) > 0 +- strv_length(s) > 0 + !strv_isempty(s) | -- strv_length@p(s) != 0 +- strv_length(s) != 0 + !strv_isempty(s) | -- strlen@p(s) == 0 +- strlen(s) == 0 + isempty(s) | -- strlen@p(s) <= 0 +- strlen(s) <= 0 + isempty(s) | -- strlen@p(s) > 0 +- strlen(s) > 0 + !isempty(s) | -- strlen@p(s) != 0 +- strlen(s) != 0 + !isempty(s) | -- strlen_ptr@p(s) == 0 +- strlen_ptr(s) == 0 + isempty(s) | -- strlen_ptr@p(s) <= 0 +- strlen_ptr(s) <= 0 + isempty(s) | -- strlen_ptr@p(s) > 0 +- strlen_ptr(s) > 0 + !isempty(s) | -- strlen_ptr@p(s) != 0 +- strlen_ptr(s) != 0 + !isempty(s) ) -@@ /* Disable this transformation for the hashmap.h, set.h, test-hashmap.c, test-hashmap-plain.c */ -position p : script:python() { - p[0].file != "src/basic/hashmap.h" and - p[0].file != "src/basic/set.h" and - p[0].file != "src/test/test-hashmap.c" and - p[0].file != "src/test/test-hashmap-plain.c" - }; +@ depends on !(file in "src/basic/hashmap.h") + && !(file in "src/basic/set.h") + && !(file in "src/test/test-hashmap.c") + && !(file in "src/test/test-hashmap-plain.c") @ expression s; @@ ( -- hashmap_size@p(s) == 0 +- hashmap_size(s) == 0 + hashmap_isempty(s) | -- hashmap_size@p(s) <= 0 +- hashmap_size(s) <= 0 + hashmap_isempty(s) | -- hashmap_size@p(s) > 0 +- hashmap_size(s) > 0 + !hashmap_isempty(s) | -- hashmap_size@p(s) != 0 +- hashmap_size(s) != 0 + !hashmap_isempty(s) | -- ordered_hashmap_size@p(s) == 0 +- ordered_hashmap_size(s) == 0 + ordered_hashmap_isempty(s) | -- ordered_hashmap_size@p(s) <= 0 +- ordered_hashmap_size(s) <= 0 + ordered_hashmap_isempty(s) | -- ordered_hashmap_size@p(s) > 0 +- ordered_hashmap_size(s) > 0 + !ordered_hashmap_isempty(s) | -- ordered_hashmap_size@p(s) != 0 +- ordered_hashmap_size(s) != 0 + !ordered_hashmap_isempty(s) | -- set_size@p(s) == 0 +- set_size(s) == 0 + set_isempty(s) | -- set_size@p(s) <= 0 +- set_size(s) <= 0 + set_isempty(s) | -- set_size@p(s) > 0 +- set_size(s) > 0 + !set_isempty(s) | -- set_size@p(s) != 0 +- set_size(s) != 0 + !set_isempty(s) | -- ordered_set_size@p(s) == 0 +- ordered_set_size(s) == 0 + ordered_set_isempty(s) | -- ordered_set_size@p(s) <= 0 +- ordered_set_size(s) <= 0 + ordered_set_isempty(s) | -- ordered_set_size@p(s) > 0 +- ordered_set_size(s) > 0 + !ordered_set_isempty(s) | -- ordered_set_size@p(s) != 0 +- ordered_set_size(s) != 0 + !ordered_set_isempty(s) ) @@ diff --git a/coccinelle/memcmp.cocci b/coccinelle/memcmp.cocci new file mode 100644 index 0000000000000..aa0effa4f8c4b --- /dev/null +++ b/coccinelle/memcmp.cocci @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Disable this transformation for iovec-util.h and the unit test */ +@ depends on !(file in "src/basic/iovec-util.h") + && !(file in "src/test/test-iovec-util.c") @ +expression a, b; +@@ +( +- iovec_memcmp(a, b) == 0 ++ iovec_equal(a, b) +| +- iovec_memcmp(a, b) != 0 ++ !iovec_equal(a, b) +| +- ASSERT_EQ(iovec_memcmp(a, b), 0) ++ ASSERT_TRUE(iovec_equal(a, b)) +| +- ASSERT_NE(iovec_memcmp(a, b), 0) ++ ASSERT_FALSE(iovec_equal(a, b)) +) diff --git a/coccinelle/parsing_hacks.h b/coccinelle/parsing_hacks.h index f88dae0c86b65..bc84235557719 100644 --- a/coccinelle/parsing_hacks.h +++ b/coccinelle/parsing_hacks.h @@ -61,23 +61,50 @@ /* Coccinelle doesn't know this keyword, so just drop it, since it's not important for any of our rules. */ #define thread_local +/* Coccinelle can't handle the __attribute__((__cleanup__(x))) GCC extension used by our _cleanup_* + * macros. Without this, any variable declared with _cleanup_free_ or _cleanup_(foo) makes the whole + * function unparsable. Drop the attribute since it's not relevant for semantic checks. */ +#define _cleanup_free_ +#define _cleanup_(x) + /* Coccinelle fails to parse these from the included headers, so let's just drop them. */ #define PAM_EXTERN #define STACK_OF(x) /* Mark a couple of iterator explicitly as iterators, otherwise Coccinelle gets a bit confused. Coccinelle * can usually infer this information automagically, but in these specific cases it needs a bit of help. */ +#define FOREACH_ARGUMENT(entry, ...) YACFE_ITERATOR #define FOREACH_ARRAY(i, array, num) YACFE_ITERATOR -#define FOREACH_ELEMENT(i, array) YACFE_ITERATOR +#define FOREACH_DIRENT(de, d, on_error) YACFE_ITERATOR #define FOREACH_DIRENT_ALL(de, d, on_error) YACFE_ITERATOR +#define FOREACH_DIRENT_IN_BUFFER(de, buf, sz) YACFE_ITERATOR +#define FOREACH_ELEMENT(i, array) YACFE_ITERATOR #define FOREACH_STRING(x, y, ...) YACFE_ITERATOR #define HASHMAP_FOREACH(e, h) YACFE_ITERATOR +#define HASHMAP_FOREACH_KEY(e, k, h) YACFE_ITERATOR #define LIST_FOREACH(name, i, head) YACFE_ITERATOR +#define LIST_FOREACH_BACKWARDS(name, i, start) YACFE_ITERATOR +#define NULSTR_FOREACH(s, l) YACFE_ITERATOR +#define NULSTR_FOREACH_PAIR(i, j, l) YACFE_ITERATOR #define ORDERED_HASHMAP_FOREACH(e, h) YACFE_ITERATOR +#define ORDERED_HASHMAP_FOREACH_KEY(e, k, h) YACFE_ITERATOR #define SET_FOREACH(e, s) YACFE_ITERATOR -#define STRV_FOREACH_BACKWARDS YACFE_ITERATOR +#define SET_FOREACH_MOVE(e, d, s) YACFE_ITERATOR +#define STRV_FOREACH(s, l) YACFE_ITERATOR +#define STRV_FOREACH_BACKWARDS(s, l) YACFE_ITERATOR +#define STRV_FOREACH_PAIR(x, y, l) YACFE_ITERATOR /* Coccinelle really doesn't like multiline macros that are not in the "usual" do { ... } while(0) format, so * let's help it a little here by providing simplified one-line versions. */ #define CMSG_BUFFER_TYPE(x) union { uint8_t align_check[(size) >= CMSG_SPACE(0) && (size) == CMSG_ALIGN(size) ? 1 : -1]; } #define SD_ID128_MAKE(...) ((const sd_id128) {}) + +/* sizeof() does not evaluate its argument, so *ptr inside sizeof() is not a real dereference. + * The SIZEOF() macro is an alias for sizeof() that hides the argument from coccinelle to avoid + * false positives from check-pointer-deref.cocci. See assert-fundamental.h for the definition. */ +#define SIZEOF(x) 8 + +/* Work around a bug in zlib.h parsing on Fedora (and possibly others) + * See: https://github.com/coccinelle/coccinelle/issues/413 */ +#define Z_EXPORT +#define Z_EXTERN diff --git a/coccinelle/run-coccinelle.sh b/coccinelle/run-coccinelle.sh index ecefbf5302d4e..8323a3abc3022 100755 --- a/coccinelle/run-coccinelle.sh +++ b/coccinelle/run-coccinelle.sh @@ -4,11 +4,13 @@ set -e # Exclude following paths from the Coccinelle transformations EXCLUDED_PATHS=( - "src/boot/efi/*" - "src/basic/include/linux/*" + "src/boot/*" + "src/include/uapi/*" # Symlinked to test-bus-vtable-cc.cc, which causes issues with the IN_SET macro "src/libsystemd/sd-bus/test-bus-vtable.c" "src/libsystemd/sd-journal/lookup3.c" + # Our BPF programs don't have access to systemd stuff + "src/network/bpf/*" # Ignore man examples, as they redefine some macros we use internally, which makes Coccinelle complain # and ignore code that tries to use the redefined stuff "man/*" diff --git a/coccinelle/sd_build_pair.cocci b/coccinelle/sd_build_pair.cocci index f0724ef8241c0..e97c273e71f34 100644 --- a/coccinelle/sd_build_pair.cocci +++ b/coccinelle/sd_build_pair.cocci @@ -1,26 +1,21 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -@@ +/* Disable this transformation on test-json.c */ +@ depends on !(file in "src/test/test-json.c") @ expression key, val; @@ +( - SD_JSON_BUILD_PAIR(key, SD_JSON_BUILD_BOOLEAN(val)) + SD_JSON_BUILD_PAIR_BOOLEAN(key, val) -@@ -expression key, val; -@@ +| - SD_JSON_BUILD_PAIR(key, SD_JSON_BUILD_INTEGER(val)) + SD_JSON_BUILD_PAIR_INTEGER(key, val) -@@ -expression key, val; -@@ +| - SD_JSON_BUILD_PAIR(key, SD_JSON_BUILD_STRING(val)) + SD_JSON_BUILD_PAIR_STRING(key, val) -@@ -expression key, val; -@@ +| - SD_JSON_BUILD_PAIR(key, SD_JSON_BUILD_UNSIGNED(val)) + SD_JSON_BUILD_PAIR_UNSIGNED(key, val) -@@ -expression key, val; -@@ +| - SD_JSON_BUILD_PAIR(key, SD_JSON_BUILD_VARIANT(val)) + SD_JSON_BUILD_PAIR_VARIANT(key, val) +) diff --git a/coccinelle/timestamp-is-set.cocci b/coccinelle/timestamp-is-set.cocci index 2d251fa2057e9..34f37458f74f3 100644 --- a/coccinelle/timestamp-is-set.cocci +++ b/coccinelle/timestamp-is-set.cocci @@ -1,9 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -@@ +/* We want to stick with the literal expression in the implementation of timestamp_is_set(), i.e. in time-util.h */ +@ depends on !(file in "src/basic/time-util.h") @ expression x; constant USEC_INFINITY = USEC_INFINITY; -/* We want to stick with the literal expression in the implementation of timestamp_is_set(), i.e. in time-util.c */ -position p : script:python() { p[0].file != "src/basic/time-util.h" }; @@ ( - x > 0 && x < USEC_INFINITY @@ -12,7 +11,7 @@ position p : script:python() { p[0].file != "src/basic/time-util.h" }; - x < USEC_INFINITY && x > 0 + timestamp_is_set(x) | -- x@p > 0 && x != USEC_INFINITY +- x > 0 && x != USEC_INFINITY + timestamp_is_set(x) | - x != USEC_INFINITY && x > 0 diff --git a/coccinelle/xsprintf.cocci b/coccinelle/xsprintf.cocci index 3b38090652e79..669734946caa4 100644 --- a/coccinelle/xsprintf.cocci +++ b/coccinelle/xsprintf.cocci @@ -1,8 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ @@ -position p : script:python() { not p[0].file.startswith("man/") }; expression e, fmt; expression list vaargs; @@ -- snprintf@p(e, sizeof(e), fmt, vaargs); +- snprintf(e, sizeof(e), fmt, vaargs); + xsprintf(e, fmt, vaargs); diff --git a/coccinelle/zz-drop-braces.cocci b/coccinelle/zz-drop-braces.cocci index 7a3382c9a7b9e..5ca2f15681ca8 100644 --- a/coccinelle/zz-drop-braces.cocci +++ b/coccinelle/zz-drop-braces.cocci @@ -1,13 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -@@ -position p : script:python() { p[0].file != "src/journal/lookup3.c" }; -expression e,e1; +@ depends on !(file in "src/libsystemd/sd-journal/lookup3.c") @ +expression e, e1; @@ - if (e) { + if (e) ( - e1@p; + e1; | - return e1@p; + return e1; ) - } diff --git a/docs/CODE_QUALITY.md b/docs/CODE_QUALITY.md index a9e663bd05790..46ee8d6c8ad37 100644 --- a/docs/CODE_QUALITY.md +++ b/docs/CODE_QUALITY.md @@ -70,10 +70,6 @@ available functionality: 13. When building systemd from a git checkout the build scripts will automatically enable a git commit hook that ensures whitespace cleanliness. -14. [CodeQL](https://codeql.github.com/) analyzes each PR and every commit - pushed to `main`. The list of active alerts can be found - [here](https://github.com/systemd/systemd/security/code-scanning). - 15. Each PR is automatically tested with [Address Sanitizer](https://clang.llvm.org/docs/AddressSanitizer.html) and [Undefined Behavior Sanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html). See [Testing systemd using sanitizers](/TESTING_WITH_SANITIZERS) diff --git a/docs/CODING_STYLE.md b/docs/CODING_STYLE.md index 767ab6734bb83..085c97df5ce44 100644 --- a/docs/CODING_STYLE.md +++ b/docs/CODING_STYLE.md @@ -754,9 +754,9 @@ SPDX-License-Identifier: LGPL-2.1-or-later section of the `alloca(3)` man page. - If you want to concatenate two or more strings, consider using `strjoina()` - or `strjoin()` rather than `asprintf()`, as the latter is a lot slower. This - matters particularly in inner loops (but note that `strjoina()` cannot be - used there). + or `strjoin()` rather than `asprintf()` or `asprintf_safe`, as the latter is + a lot slower. This matters particularly in inner loops (but note that + `strjoina()` cannot be used there). ## Runtime Behaviour diff --git a/docs/CONTAINER_INTERFACE.md b/docs/CONTAINER_INTERFACE.md index cb7719557fd17..72f6f4dc7ec3b 100644 --- a/docs/CONTAINER_INTERFACE.md +++ b/docs/CONTAINER_INTERFACE.md @@ -403,9 +403,9 @@ its user to 2 (to effectively disallow `fork()`ing) you cannot run more than one Avahi instance on the entire system... People have been asking to be able to run systemd without `CAP_SYS_ADMIN` and -`CAP_SYS_MKNOD` in the container. This is now supported to some level in +`CAP_MKNOD` in the container. This is now supported to some level in systemd, but we recommend against it (see above). If `CAP_SYS_ADMIN` and -`CAP_SYS_MKNOD` are missing from the container systemd will now gracefully turn +`CAP_MKNOD` are missing from the container systemd will now gracefully turn off `PrivateTmp=`, `PrivateNetwork=`, `ProtectHome=`, `ProtectSystem=` and others, because those capabilities are required to implement these options. The services using these settings (which include many of systemd's own) will hence diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 970510a3123b7..0b4214de0ce81 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -53,7 +53,8 @@ See [reporting of security vulnerabilities](https://systemd.io/SECURITY). ## Using AI Code Generators If you use an AI code generator such as ChatGPT, Claude, Copilot, Llama or a similar tool, this must be -disclosed in the commit messages and pull request description. +disclosed in the commit messages by adding e.g. `Co-developed-by: Claude Opus 4.6 ` +and pull request description. The quality bar for contributions to this project is high, and unlikely to be met by an unattended AI tool, without significant manual corrections. Always thoroughly review and correct any such outputs, for example diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 5390754661879..3804cdfefaa51 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -72,6 +72,10 @@ All tools: `/etc/veritytab`. Only useful for debugging. Currently only supported by `systemd-veritysetup-generator`. +* `$SYSTEMD_CLONETAB` — if set, use this path instead of + `/etc/clonetab`. Only useful for debugging. Currently only supported by + `systemd-clonesetup-generator`. + * `$SYSTEMD_DEFAULT_HOSTNAME` — override the compiled-in fallback hostname (relevant in particular for the system manager and `systemd-hostnamed`). Must be a valid hostname (either a single label or a FQDN). @@ -679,6 +683,11 @@ SYSTEMD_HOME_DEBUG_SUFFIX=foo \ string format. Overrides the default maximum allowed size for a file-descriptor based input record to be stored in the journal. +* `$SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE` – path to a configuration file for + `systemd-journal-remote`. When set, the specified file is used instead of the + default configuration file and drop-in directories. If set to the empty string + or `/dev/null`, configuration file parsing is skipped entirely. + * `$SYSTEMD_CATALOG` – path to the compiled catalog database file to use for `journalctl -x`, `journalctl --update-catalog`, `journalctl --list-catalog` and related calls. diff --git a/docs/INHIBITOR_LOCKS.md b/docs/INHIBITOR_LOCKS.md index 220c085e09a43..80f09d21e09db 100644 --- a/docs/INHIBITOR_LOCKS.md +++ b/docs/INHIBITOR_LOCKS.md @@ -26,12 +26,10 @@ Seven distinct inhibitor lock types may be taken, or a combination of them: 1. _sleep_ inhibits system suspend and hibernation requested by (unprivileged) **users** 2. _shutdown_ inhibits high-level system power-off and reboot requested by (unprivileged) **users** 3. _idle_ inhibits that the system goes into idle mode, possibly resulting in **automatic** system suspend or shutdown depending on configuration. - -- _handle-power-key_ inhibits the low-level (i.e. logind-internal) handling of the system power **hardware** key, allowing (possibly unprivileged) external code to handle the event instead. - -4. Similar, _handle-suspend-key_ inhibits the low-level handling of the system **hardware** suspend key. -5. Similar, _handle-hibernate-key_ inhibits the low-level handling of the system **hardware** hibernate key. -6. Similar, _handle-lid-switch_ inhibits the low-level handling of the systemd **hardware** lid switch. +4. _handle-power-key_ inhibits the low-level (i.e. logind-internal) handling of the system power **hardware** key, allowing (possibly unprivileged) external code to handle the event instead. +5. Similar, _handle-suspend-key_ inhibits the low-level handling of the system **hardware** suspend key. +6. Similar, _handle-hibernate-key_ inhibits the low-level handling of the system **hardware** hibernate key. +7. Similar, _handle-lid-switch_ inhibits the low-level handling of the systemd **hardware** lid switch. Two different modes of locks are supported: diff --git a/docs/JOURNAL_FILE_FORMAT.md b/docs/JOURNAL_FILE_FORMAT.md index ddd4a2de1abeb..9907c622d7347 100644 --- a/docs/JOURNAL_FILE_FORMAT.md +++ b/docs/JOURNAL_FILE_FORMAT.md @@ -202,7 +202,7 @@ also supposed to be updated whenever the file was opened for any form of writing, including when opened to mark it as archived. This behaviour has been deemed problematic since without an associated boot ID the **tail_entry_monotonic** field is useless. To indicate whether the boot ID is -updated only on append the JOURNAL_COMPATIBLE_TAIL_ENTRY_BOOT_ID is set. If it +updated only on append the `JOURNAL_COMPATIBLE_TAIL_ENTRY_BOOT_ID` is set. If it is not set, the **tail_entry_monotonic** field is not usable). The currently used part of the file is the **header_size** plus the @@ -291,27 +291,27 @@ enum { }; ``` -HEADER_INCOMPATIBLE_COMPRESSED_XZ indicates that the file includes DATA objects -that are compressed using XZ. Similarly, HEADER_INCOMPATIBLE_COMPRESSED_LZ4 +`HEADER_INCOMPATIBLE_COMPRESSED_XZ` indicates that the file includes DATA objects +that are compressed using XZ. Similarly, `HEADER_INCOMPATIBLE_COMPRESSED_LZ4` indicates that the file includes DATA objects that are compressed with the LZ4 -algorithm. And HEADER_INCOMPATIBLE_COMPRESSED_ZSTD indicates that there are +algorithm. And `HEADER_INCOMPATIBLE_COMPRESSED_ZSTD` indicates that there are objects compressed with ZSTD. -HEADER_INCOMPATIBLE_KEYED_HASH indicates that instead of the unkeyed Jenkins +`HEADER_INCOMPATIBLE_KEYED_HASH` indicates that instead of the unkeyed Jenkins hash function the keyed siphash24 hash function is used for the two hash tables, see below. -HEADER_INCOMPATIBLE_COMPACT indicates that the journal file uses the new binary +`HEADER_INCOMPATIBLE_COMPACT` indicates that the journal file uses the new binary format that uses less space on disk compared to the original format. -HEADER_COMPATIBLE_SEALED indicates that the file includes TAG objects required +`HEADER_COMPATIBLE_SEALED` indicates that the file includes TAG objects required for Forward Secure Sealing. -HEADER_COMPATIBLE_TAIL_ENTRY_BOOT_ID indicates whether the +`HEADER_COMPATIBLE_TAIL_ENTRY_BOOT_ID` indicates whether the **tail_entry_boot_id** field is strictly updated on initial creation of the file and whenever an entry is updated (in which case the flag is set), or also when the file is archived (in which case it is unset). New files should always -set this flag (and thus not update the **tail_entry_boot_id** except when +set this flag (and thus not update **tail_entry_boot_id** except when creating the file and when appending an entry to it. ## Dirty Detection @@ -406,11 +406,11 @@ _packed_ struct ObjectHeader { ``` The **type** field is one of the object types listed above. The **flags** field -currently knows three flags: OBJECT_COMPRESSED_XZ, OBJECT_COMPRESSED_LZ4 and -OBJECT_COMPRESSED_ZSTD. It is only valid for DATA objects and indicates that +currently knows three flags: `OBJECT_COMPRESSED_XZ`, `OBJECT_COMPRESSED_LZ4` and +`OBJECT_COMPRESSED_ZSTD`. It is only valid for DATA objects and indicates that the data payload is compressed with XZ/LZ4/ZSTD. If one of the -OBJECT_COMPRESSED_* flags is set for an object then the matching -HEADER_INCOMPATIBLE_COMPRESSED_XZ/HEADER_INCOMPATIBLE_COMPRESSED_LZ4/HEADER_INCOMPATIBLE_COMPRESSED_ZSTD +`OBJECT_COMPRESSED_*` flags is set for an object then the matching +`HEADER_INCOMPATIBLE_COMPRESSED_XZ`/`HEADER_INCOMPATIBLE_COMPRESSED_LZ4`/`HEADER_INCOMPATIBLE_COMPRESSED_ZSTD` flag must be set for the file as well. At most one of these three bits may be set. The **size** field encodes the size of the object including all its headers and payload. @@ -465,7 +465,7 @@ number of ENTRY objects that reference this object, i.e. the sum of all ENTRY_ARRAYS chained up from this object, plus 1. The **payload[]** field contains the field name and date unencoded, unless -OBJECT_COMPRESSED_XZ/OBJECT_COMPRESSED_LZ4/OBJECT_COMPRESSED_ZSTD is set in the +`OBJECT_COMPRESSED_XZ`/`OBJECT_COMPRESSED_LZ4`/`OBJECT_COMPRESSED_ZSTD` is set in the `ObjectHeader`, in which case the payload is compressed with the indicated compression algorithm. diff --git a/docs/MEMORY_PRESSURE.md b/docs/MEMORY_PRESSURE.md index 3d3832cac7ea2..95e8a9af9e721 100644 --- a/docs/MEMORY_PRESSURE.md +++ b/docs/MEMORY_PRESSURE.md @@ -1,241 +1,4 @@ --- -title: Memory Pressure Handling -category: Interfaces -layout: default -SPDX-License-Identifier: LGPL-2.1-or-later +layout: forward +target: /PRESSURE --- - -# Memory Pressure Handling in systemd - -When the system is under memory pressure (i.e. some component of the OS -requires memory allocation but there is only very little or none available), -it can attempt various things to make more memory available again ("reclaim"): - -* The kernel can flush out memory pages backed by files on disk, under the - knowledge that it can reread them from disk when needed again. Candidate - pages are the many memory mapped executable files and shared libraries on - disk, among others. - -* The kernel can flush out memory pages not backed by files on disk - ("anonymous" memory, i.e. memory allocated via `malloc()` and similar calls, - or `tmpfs` file system contents) if there's swap to write it to. - -* Userspace can proactively release memory it allocated but doesn't immediately - require back to the kernel. This includes allocation caches, and other forms - of caches that are not required for normal operation to continue. - -The latter is what we want to focus on in this document: how to ensure -userspace process can detect mounting memory pressure early and release memory -back to the kernel as it happens, relieving the memory pressure before it -becomes too critical. - -The effects of memory pressure during runtime generally are growing latencies -during operation: when a program requires memory but the system is busy writing -out memory to (relatively slow) disks in order make some available, this -generally surfaces in scheduling latencies, and applications and services will -slow down until memory pressure is relieved. Hence, to ensure stable service -latencies it is essential to release unneeded memory back to the kernel early -on. - -On Linux the [Pressure Stall Information -(PSI)](https://docs.kernel.org/accounting/psi.html) Linux kernel interface is -the primary way to determine the system or a part of it is under memory -pressure. PSI makes available to userspace a `poll()`-able file descriptor that -gets notifications whenever memory pressure latencies for the system or a -control group grow beyond some level. - -`systemd` itself makes use of PSI, and helps applications to do so too. -Specifically: - -* Most of systemd's long running components watch for PSI memory pressure - events, and release allocation caches and other resources once seen. - -* systemd's service manager provides a protocol for asking services to monitor - PSI events and configure the appropriate pressure thresholds. - -* systemd's `sd-event` event loop API provides a high-level call - `sd_event_add_memory_pressure()` enabling programs using it to efficiently - hook into the PSI memory pressure protocol provided by the service manager, - with very few lines of code. - -## Memory Pressure Service Protocol - -If memory pressure handling for a specific service is enabled via -`MemoryPressureWatch=` the memory pressure service protocol is used to tell the -service code about this. Specifically two environment variables are set by the -service manager, and typically consumed by the service: - -* The `$MEMORY_PRESSURE_WATCH` environment variable will contain an absolute - path in the file system to the file to watch for memory pressure events. This - will usually point to a PSI file such as the `memory.pressure` file of the - service's cgroup. In order to make debugging easier, and allow later - extension it is recommended for applications to also allow this path to refer - to an `AF_UNIX` stream socket in the file system or a FIFO inode in the file - system. Regardless of which of the three types of inodes this absolute path - refers to, all three are `poll()`-able for memory pressure events. The - variable can also be set to the literal string `/dev/null`. If so the service - code should take this as indication that memory pressure monitoring is not - desired and should be turned off. - -* The `$MEMORY_PRESSURE_WRITE` environment variable is optional. If set by the - service manager it contains Base64 encoded data (that may contain arbitrary - binary values, including NUL bytes) that should be written into the path - provided via `$MEMORY_PRESSURE_WATCH` right after opening it. Typically, if - talking directly to a PSI kernel file this will contain information about the - threshold settings configurable in the service manager. - -When a service initializes it hence should look for -`$MEMORY_PRESSURE_WATCH`. If set, it should try to open the specified path. If -it detects the path to refer to a regular file it should assume it refers to a -PSI kernel file. If so, it should write the data from `$MEMORY_PRESSURE_WRITE` -into the file descriptor (after Base64-decoding it, and only if the variable is -set) and then watch for `POLLPRI` events on it. If it detects the paths refers -to a FIFO inode, it should open it, write the `$MEMORY_PRESSURE_WRITE` data -into it (as above) and then watch for `POLLIN` events on it. Whenever `POLLIN` -is seen it should read and discard any data queued in the FIFO. If the path -refers to an `AF_UNIX` socket in the file system, the application should -`connect()` a stream socket to it, write `$MEMORY_PRESSURE_WRITE` into it (as -above) and watch for `POLLIN`, discarding any data it might receive. - -To summarize: - -* If `$MEMORY_PRESSURE_WATCH` points to a regular file: open and watch for - `POLLPRI`, never read from the file descriptor. - -* If `$MEMORY_PRESSURE_WATCH` points to a FIFO: open and watch for `POLLIN`, - read/discard any incoming data. - -* If `$MEMORY_PRESSURE_WATCH` points to an `AF_UNIX` socket: connect and watch - for `POLLIN`, read/discard any incoming data. - -* If `$MEMORY_PRESSURE_WATCH` contains the literal string `/dev/null`, turn off - memory pressure handling. - -(And in each case, immediately after opening/connecting to the path, write the -decoded `$MEMORY_PRESSURE_WRITE` data into it.) - -Whenever a `POLLPRI`/`POLLIN` event is seen the service is under memory -pressure. It should use this as hint to release suitable redundant resources, -for example: - -* glibc's memory allocation cache, via - [`malloc_trim()`](https://man7.org/linux/man-pages/man3/malloc_trim.3.html). Similar, - allocation caches implemented in the service itself. - -* Any other local caches, such DNS caches, or web caches (in particular if - service is a web browser). - -* Terminate any idle worker threads or processes. - -* Run a garbage collection (GC) cycle, if the runtime environment supports it. - -* Terminate the process if idle, and can be automatically started when - needed next. - -Which actions precisely to take depends on the service in question. Note that -the notifications are delivered when memory allocation latency already degraded -beyond some point. Hence when discussing which resources to keep and which to -discard, keep in mind it's typically acceptable that latencies incurred -recovering discarded resources at a later point are acceptable, given that -latencies *already* are affected negatively. - -In case the path supplied via `$MEMORY_PRESSURE_WATCH` points to a PSI kernel -API file, or to an `AF_UNIX` opening it multiple times is safe and reliable, -and should deliver notifications to each of the opened file descriptors. This -is specifically useful for services that consist of multiple processes, and -where each of them shall be able to release resources on memory pressure. - -The `POLLPRI`/`POLLIN` conditions will be triggered every time memory pressure -is detected, but not continuously. It is thus safe to keep `poll()`-ing on the -same file descriptor continuously, and executing resource release operations -whenever the file descriptor triggers without having to expect overloading the -process. - -(Currently, the protocol defined here only allows configuration of a single -"degree" of memory pressure, there's no distinction made on how strong the -pressure is. In future, if it becomes apparent that there's clear need to -extend this we might eventually add different degrees, most likely by adding -additional environment variables such as `$MEMORY_PRESSURE_WRITE_LOW` and -`$MEMORY_PRESSURE_WRITE_HIGH` or similar, which may contain different settings -for lower or higher memory pressure thresholds.) - -## Service Manager Settings - -The service manager provides two per-service settings that control the memory -pressure handling: - -* The - [`MemoryPressureWatch=`](https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryPressureWatch=) - setting controls whether to enable the memory pressure protocol for the - service in question. - -* The `MemoryPressureThresholdSec=` setting allows configuring the threshold - when to signal memory pressure to the services. It takes a time value - (usually in the millisecond range) that defines a threshold per 1s time - window: if memory allocation latencies grow beyond this threshold - notifications are generated towards the service, requesting it to release - resources. - -The `/etc/systemd/system.conf` file provides two settings that may be used to -select the default values for the above settings. If the threshold isn't -configured via the per-service nor system-wide option, it defaults to 100ms. - -When memory pressure monitoring is enabled for a service via -`MemoryPressureWatch=` this primarily does three things: - -* It enables cgroup memory accounting for the service (this is a requirement - for per-cgroup PSI) - -* It sets the aforementioned two environment variables for processes invoked - for the service, based on the control group of the service and provided - settings. - -* The `memory.pressure` PSI control group file associated with the service's - cgroup is delegated to the service (i.e. permissions are relaxed so that - unprivileged service payload code can open the file for writing). - -## Memory Pressure Events in `sd-event` - -The -[`sd-event`](https://www.freedesktop.org/software/systemd/man/latest/sd-event.html) -event loop library provides two API calls that encapsulate the -functionality described above: - -* The - [`sd_event_add_memory_pressure()`](https://www.freedesktop.org/software/systemd/man/latest/sd_event_add_memory_pressure.html) - call implements the service-side of the memory pressure protocol and - integrates it with an `sd-event` event loop. It reads the two environment - variables, connects/opens the specified file, writes the specified data to it, - then watches it for events. - -* The `sd_event_trim_memory()` call may be called to trim the calling - processes' memory. It's a wrapper around glibc's `malloc_trim()`, but first - releases allocation caches maintained by libsystemd internally. This function - serves as the default when a NULL callback is supplied to - `sd_event_add_memory_pressure()`. - -When implementing a service using `sd-event`, for automatic memory pressure -handling, it's typically sufficient to add a line such as: - -```c -(void) sd_event_add_memory_pressure(event, NULL, NULL, NULL); -``` - -– right after allocating the event loop object `event`. - -## Other APIs - -Other programming environments might have native APIs to watch memory -pressure/low memory events. Most notable is probably GLib's -[GMemoryMonitor](https://docs.gtk.org/gio/iface.MemoryMonitor.html). As of GLib -2.86.0, it uses the per-cgroup PSI kernel file to monitor for memory pressure, -but does not yet read the environment variables recommended above. - -In older versions, it used the per-system Linux PSI interface as the backend, but operated -differently than the above: memory pressure events were picked up by a system -service, which then propagated this through D-Bus to the applications. This was -typically less than ideal, since this means each notification event had to -traverse three processes before being handled. This traversal created -additional latencies at a time where the system is already experiencing adverse -latencies. Moreover, it focused on system-wide PSI events, even though -service-local ones are generally the better approach. diff --git a/docs/PRESSURE.md b/docs/PRESSURE.md new file mode 100644 index 0000000000000..29efc07e5cf13 --- /dev/null +++ b/docs/PRESSURE.md @@ -0,0 +1,255 @@ +--- +title: Resource Pressure Handling +category: Interfaces +layout: default +SPDX-License-Identifier: LGPL-2.1-or-later +--- + +# Resource Pressure Handling in systemd + +On Linux the [Pressure Stall Information +(PSI)](https://docs.kernel.org/accounting/psi.html) Linux kernel interface +provides a way to monitor resource pressure — situations where tasks are +stalled waiting for a resource to become available. PSI covers three types of +resources: + +* **Memory pressure**: tasks are stalled because the system is low on memory + and the kernel is busy reclaiming it (e.g. writing out pages to swap or + flushing file-backed pages). + +* **CPU pressure**: tasks are stalled waiting for CPU time because the CPU is + oversubscribed. + +* **IO pressure**: tasks are stalled waiting for IO operations to complete + because the IO subsystem is saturated. + +PSI makes available to userspace a `poll()`-able file descriptor that gets +notifications whenever pressure latencies for the system or a control group +grow beyond some configured level. + +When the system is under memory pressure, userspace can proactively release +memory it allocated but doesn't immediately require back to the kernel. This +includes allocation caches, and other forms of caches that are not required for +normal operation to continue. Similarly, when CPU or IO pressure is detected, +services can take appropriate action such as reducing parallelism, deferring +background work, or shedding load. + +The effects of resource pressure during runtime generally are growing latencies +during operation: applications and services slow down until pressure is +relieved. Hence, to ensure stable service latencies it is essential to detect +pressure early and respond appropriately. + +`systemd` itself makes use of PSI, and helps applications to do so too. +Specifically: + +* Most of systemd's long running components watch for PSI memory pressure + events, and release allocation caches and other resources once seen. + +* systemd's service manager provides a protocol for asking services to monitor + PSI events and configure the appropriate pressure thresholds, for memory, CPU, + and IO pressure independently. + +* systemd's `sd-event` event loop API provides high-level calls + `sd_event_add_memory_pressure()`, `sd_event_add_cpu_pressure()`, and + `sd_event_add_io_pressure()` enabling programs using it to efficiently hook + into the PSI pressure protocol provided by the service manager, with very few + lines of code. + +## Pressure Service Protocol + +For each resource type, if pressure handling for a specific service is enabled +via the corresponding `*PressureWatch=` setting (i.e. `MemoryPressureWatch=`, +`CPUPressureWatch=`, or `IOPressureWatch=`), two environment variables are set +by the service manager: + +* `$MEMORY_PRESSURE_WATCH` / `$CPU_PRESSURE_WATCH` / `$IO_PRESSURE_WATCH` — + contains an absolute path in the file system to the file to watch for + pressure events. This will usually point to a PSI file such as the + `memory.pressure`, `cpu.pressure`, or `io.pressure` file of the service's + cgroup. In order to make debugging easier, and allow later extension it is + recommended for applications to also allow this path to refer to an `AF_UNIX` + stream socket in the file system or a FIFO inode in the file system. + Regardless of which of the three types of inodes this absolute path refers + to, all three are `poll()`-able for pressure events. The variable can also be + set to the literal string `/dev/null`. If so the service code should take this + as indication that pressure monitoring for this resource is not desired and + should be turned off. + +* `$MEMORY_PRESSURE_WRITE` / `$CPU_PRESSURE_WRITE` / `$IO_PRESSURE_WRITE` — + optional. If set by the service manager it contains Base64 encoded data (that + may contain arbitrary binary values, including NUL bytes) that should be + written into the path provided via the corresponding `*_PRESSURE_WATCH` + variable right after opening it. Typically, if talking directly to a PSI + kernel file this will contain information about the threshold settings + configurable in the service manager. + +The protocol works the same for all three resource types. The remainder of this +section uses memory pressure as the example, but the same logic applies to CPU +and IO pressure with the corresponding environment variable names. + +When a service initializes it hence should look for +`$MEMORY_PRESSURE_WATCH`. If set, it should try to open the specified path. If +it detects the path to refer to a regular file it should assume it refers to a +PSI kernel file. If so, it should write the data from `$MEMORY_PRESSURE_WRITE` +into the file descriptor (after Base64-decoding it, and only if the variable is +set) and then watch for `POLLPRI` events on it. If it detects the path refers +to a FIFO inode, it should open it, write the `$MEMORY_PRESSURE_WRITE` data +into it (as above) and then watch for `POLLIN` events on it. Whenever `POLLIN` +is seen it should read and discard any data queued in the FIFO. If the path +refers to an `AF_UNIX` socket in the file system, the application should +`connect()` a stream socket to it, write `$MEMORY_PRESSURE_WRITE` into it (as +above) and watch for `POLLIN`, discarding any data it might receive. + +To summarize: + +* If `$MEMORY_PRESSURE_WATCH` points to a regular file: open and watch for + `POLLPRI`, never read from the file descriptor. + +* If `$MEMORY_PRESSURE_WATCH` points to a FIFO: open and watch for `POLLIN`, + read/discard any incoming data. + +* If `$MEMORY_PRESSURE_WATCH` points to an `AF_UNIX` socket: connect and watch + for `POLLIN`, read/discard any incoming data. + +* If `$MEMORY_PRESSURE_WATCH` contains the literal string `/dev/null`, turn off + memory pressure handling. + +(And in each case, immediately after opening/connecting to the path, write the +decoded `$MEMORY_PRESSURE_WRITE` data into it.) + +Whenever a `POLLPRI`/`POLLIN` event is seen the service is under pressure. It +should use this as hint to release suitable redundant resources, for example: + +* glibc's memory allocation cache, via + [`malloc_trim()`](https://man7.org/linux/man-pages/man3/malloc_trim.3.html). Similarly, + allocation caches implemented in the service itself. + +* Any other local caches, such as DNS caches, or web caches (in particular if + service is a web browser). + +* Terminate any idle worker threads or processes. + +* Run a garbage collection (GC) cycle, if the runtime environment supports it. + +* Terminate the process if idle, and can be automatically started when + needed next. + +Which actions precisely to take depends on the service in question and the type +of pressure. Note that the notifications are delivered when resource latency +already degraded beyond some point. Hence when discussing which resources to +keep and which to discard, keep in mind it's typically acceptable that latencies +incurred recovering discarded resources at a later point are acceptable, given +that latencies *already* are affected negatively. + +In case the path supplied via `$MEMORY_PRESSURE_WATCH` points to a PSI kernel +API file, or to an `AF_UNIX` socket, opening it multiple times is safe and reliable, +and should deliver notifications to each of the opened file descriptors. This +is specifically useful for services that consist of multiple processes, and +where each of them shall be able to release resources on memory pressure. + +The `POLLPRI`/`POLLIN` conditions will be triggered every time pressure is +detected, but not continuously. It is thus safe to keep `poll()`-ing on the +same file descriptor continuously, and executing resource release operations +whenever the file descriptor triggers without having to expect overloading the +process. + +(Currently, the protocol defined here only allows configuration of a single +"degree" of pressure per resource type, there's no distinction made on how +strong the pressure is. In future, if it becomes apparent that there's clear +need to extend this we might eventually add different degrees, most likely by +adding additional environment variables such as `$MEMORY_PRESSURE_WRITE_LOW` +and `$MEMORY_PRESSURE_WRITE_HIGH` or similar, which may contain different +settings for lower or higher pressure thresholds.) + +## Service Manager Settings + +The service manager provides two per-service settings for each resource type +that control pressure handling: + +* `MemoryPressureWatch=` / `CPUPressureWatch=` / `IOPressureWatch=` controls + whether to enable the pressure protocol for the respective resource type for + the service in question. See + [`systemd.resource-control(5)`](https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryPressureWatch=) + for details. + +* `MemoryPressureThresholdSec=` / `CPUPressureThresholdSec=` / + `IOPressureThresholdSec=` allows configuring the threshold when to signal + pressure to the services. It takes a time value (usually in the millisecond + range) that defines a threshold per 2s time window: if resource latencies grow + beyond this threshold notifications are generated towards the service, + requesting it to release resources. + +The `/etc/systemd/system.conf` file provides two settings for each resource +type that may be used to select the default values for the above settings. If +the threshold isn't configured via the per-service nor system-wide option, it +defaults to 200ms. + +When pressure monitoring is enabled for a service this primarily does three +things: + +* It enables the corresponding cgroup accounting for the service (this is a + requirement for per-cgroup PSI). + +* It sets the aforementioned two environment variables for processes invoked + for the service, based on the control group of the service and provided + settings. + +* The corresponding PSI control group file (`memory.pressure`, `cpu.pressure`, + or `io.pressure`) associated with the service's cgroup is delegated to the + service (i.e. permissions are relaxed so that unprivileged service payload + code can open the file for writing). + +## Pressure Events in `sd-event` + +The +[`sd-event`](https://www.freedesktop.org/software/systemd/man/latest/sd-event.html) +event loop library provides API calls that encapsulate the functionality +described above: + +* [`sd_event_add_memory_pressure()`](https://www.freedesktop.org/software/systemd/man/latest/sd_event_add_memory_pressure.html), + `sd_event_add_cpu_pressure()`, and `sd_event_add_io_pressure()` implement the + service-side of the pressure protocol for each resource type and integrate it + with an `sd-event` event loop. Each reads the corresponding two environment + variables, connects/opens the specified file, writes the specified data to it, + then watches it for events. + +* The `sd_event_trim_memory()` call may be called to trim the calling + processes' memory. It's a wrapper around glibc's `malloc_trim()`, but first + releases allocation caches maintained by libsystemd internally. This function + serves as the default when a NULL callback is supplied to + `sd_event_add_memory_pressure()`. Note that the default handler for + `sd_event_add_cpu_pressure()` and `sd_event_add_io_pressure()` is a no-op; + a custom callback should be provided for CPU and IO pressure to take + meaningful action. + +When implementing a service using `sd-event`, for automatic memory pressure +handling, it's typically sufficient to add a line such as: + +```c +(void) sd_event_add_memory_pressure(event, NULL, NULL, NULL); +``` + +– right after allocating the event loop object `event`. For CPU and IO pressure, +a custom handler should be provided to take appropriate action: + +```c +(void) sd_event_add_cpu_pressure(event, NULL, my_cpu_pressure_handler, userdata); +(void) sd_event_add_io_pressure(event, NULL, my_io_pressure_handler, userdata); +``` + +## Other APIs + +Other programming environments might have native APIs to watch memory +pressure/low memory events. Most notable is probably GLib's +[GMemoryMonitor](https://docs.gtk.org/gio/iface.MemoryMonitor.html). As of GLib +2.86.0, it uses the per-cgroup PSI kernel file to monitor for memory pressure, +but does not yet read the environment variables recommended above. + +In older versions, it used the per-system Linux PSI interface as the backend, but operated +differently than the above: memory pressure events were picked up by a system +service, which then propagated this through D-Bus to the applications. This was +typically less than ideal, since this means each notification event had to +traverse three processes before being handled. This traversal created +additional latencies at a time where the system is already experiencing adverse +latencies. Moreover, it focused on system-wide PSI events, even though +service-local ones are generally the better approach. diff --git a/docs/SECURITY.md b/docs/SECURITY.md index f9f2e91ad681e..6a3102a717416 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -8,11 +8,22 @@ SPDX-License-Identifier: LGPL-2.1-or-later # Reporting of Security Vulnerabilities If you discover a security vulnerability, we'd appreciate a non-public disclosure. -systemd developers can be contacted privately on the **[systemd-security@redhat.com](mailto:systemd-security@redhat.com) mailing list**. +systemd developers can be contacted privately by creating a new **[Security Advisory on GitHub](https://github.com/systemd/systemd/security/advisories/new)** +or via the **[systemd-security@redhat.com](mailto:systemd-security@redhat.com) mailing list**. The disclosure will be coordinated with distributions. (The [issue tracker](https://github.com/systemd/systemd/issues) and [systemd-devel mailing list](https://lists.freedesktop.org/mailman/listinfo/systemd-devel) are fully public.) -Subscription to the systemd-security mailing list is open to **regular systemd contributors and people working in the security teams of various distributions**. +Subscription to the Security Advisories and/or systemd-security mailing list is open to **regular systemd contributors and people working in the security teams of various distributions**. Those conditions should be backed by publicly accessible information (ideally, a track of posts and commits from the mail address in question). -If you fall into one of those categories and wish to be subscribed, submit a **[subscription request](https://www.redhat.com/mailman/listinfo/systemd-security)**. +If you fall into one of those categories and wish to be subscribed, +contact the maintainers or submit a **[subscription request](https://www.redhat.com/mailman/listinfo/systemd-security)**. + +# Requirements for a Valid Report + +- Please ensure the issue is reproducible on main. +- Please ensure a fully working, end-to-end reproducer is provided. +- Please ensure the reproducer is real-world and not simulated or abstracted. +- Please ensure the reproducer demonstrably violates a security boundary. +- Please understand that most of our maintainers are volunteers and already have a heavy review burden. While we will try to triage and fix issues in a timely manner, we cannot guarantee any fixed timeline for issue resolution. +- While modern industry practices around coordinated disclosures encourage public disclosure to avoid vendors stonewalling researchers, we are an open source project that would gain little from needlessly stonewalling researchers. We thus kindly request that reporters do not publicly disclose issues they have reported to us before an agreed-to disclosure date. diff --git a/docs/TPM2_PCR_MEASUREMENTS.md b/docs/TPM2_PCR_MEASUREMENTS.md index 53317b732748e..571a4c1077201 100644 --- a/docs/TPM2_PCR_MEASUREMENTS.md +++ b/docs/TPM2_PCR_MEASUREMENTS.md @@ -293,3 +293,18 @@ volume name, a ":" separator, the UUID of the LUKS superblock, a ":" separator, a brief string identifying the unlock mechanism, a ":" separator, and finally the LUKS slot number used. Example string: `cryptsetup-keyslot:root:1e023a55-60f9-4b6b-9b80-67438dc5f065:tpm2:1` + +## PCR/NvPCR Measurements Made by `systemd-veritysetup` + image dissection logic (Userspace) + +### NvPCR `verity` (base+2), Verity root hash + signature info of activated Verity images + +The `systemd-veritysetup@.service` service as well as any component using the +image dissection logic (i.e. `RootImage=` in unit files, or `systemd-nspawn +--image=`, `systemd-tmpfiles --image=` and similar) will measure information +about activated Verity images before they are activated. + +→ **Measured hash** covers the string `verity:`, followed by the Verity device +name, followed by `:`, followed by a hexadecimal formatted string indicating +the root hash of the Verity image, followed by `:`, followed by a comma +separatec list of PKCS#7 signature key's serial (formatted in hexadecimal), `/`, and +key issuer (formatted in Base64). diff --git a/docs/USER_RECORD.md b/docs/USER_RECORD.md index 9d6d8c1d03b88..5335e145b5f71 100644 --- a/docs/USER_RECORD.md +++ b/docs/USER_RECORD.md @@ -273,6 +273,9 @@ This must be a string, and should follow the semantics defined in the It's probably wise to use a location string processable by geo-location subsystems, but this is not enforced nor required. Example: `Berlin, Germany` or `Basement, Room 3a`. +`birthDate` → A string in ISO 8601 calendar date format (`YYYY-MM-DD`) indicating the user's date +of birth. The earliest representable year is 1900. This field is optional. + `disposition` → A string, one of `intrinsic`, `system`, `dynamic`, `regular`, `container`, `foreign`, `reserved`. If specified clarifies the disposition of the user, i.e. the context it is defined in. diff --git a/docs/VARLINK.md b/docs/VARLINK.md index 844c4ca516bd8..65f1950800b59 100644 --- a/docs/VARLINK.md +++ b/docs/VARLINK.md @@ -63,11 +63,22 @@ SPDX-License-Identifier: LGPL-2.1-or-later * `JSON_DISPATCH_ENUM_DEFINE` - creates a `json_dispatch_*` function that accepts both the original and the underscorified enum value as valid input. + For example, a `LogTarget` field outputs `"journal_or_kmsg"` (underscore + form), but on input both `"journal_or_kmsg"` and `"journal-or-kmsg"` are + accepted. This is handled automatically by `JSON_DISPATCH_ENUM_DEFINE`: + it first tries the value as-is via `_from_string()`, and if that fails, + replaces underscores with dashes and retries. + - An internal enum may be exposed as a simple string field instead of a Varlink enum type when the field is output-only and never provided or controlled by the user. However, such fields should avoid using dashes to prevent breaking changes if they are later converted into enums (see below). + For example, in `io.systemd.Unit`, configuration settings that users select + in unit files (e.g. `ProtectSystem`, `ExecInputType`) should be proper varlink + enum types. Runtime state fields that only the engine determines (e.g. + `ActiveState`, `SubState`) may remain plain strings. + - A varlink string field that has a finite set of possible values may later be converted into an enum without introducing a breaking change. This allows the interface to evolve from loosely defined string values to a more explicit and diff --git a/docs/WRITING_VM_AND_CONTAINER_MANAGERS.md b/docs/WRITING_VM_AND_CONTAINER_MANAGERS.md index 724d3d6dafb94..e23de1a746d86 100644 --- a/docs/WRITING_VM_AND_CONTAINER_MANAGERS.md +++ b/docs/WRITING_VM_AND_CONTAINER_MANAGERS.md @@ -24,7 +24,8 @@ their own. All virtual machines and containers should be registered with the [machined](https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.machine1) mini service that is part of systemd. This provides integration into the core OS at various points. For example, tools like ps, cgls, gnome-system-manager use this registration information to show machine information for running processes, as each of the VM's/container's processes can reliably attributed to a registered machine. The various systemd tools (like systemctl, journalctl, loginctl, systemd-run, ...) all support a -M switch that operates on machines registered with machined. -"machinectl" may be used to execute operations on any such machine. +Note that the -M switch and interactive commands like "machinectl shell" and "machinectl login" currently only work for containers, not for VMs. +For VMs, registration with machined still provides process attribution, cgroup placement, and visibility in tools like ps and systemctl. When a machine is registered via machined its processes will automatically be placed in a systemd scope unit (that is located in the machines.slice slice) and thus appear in "systemctl" and similar commands. The scope unit name is based on the machine meta information passed to machined at registration. @@ -34,7 +35,5 @@ For more details on the APIs provided by machine consult [the bus API interface As container virtualization is much less comprehensive, and the guest is less isolated from the host, there are a number of interfaces defined how the container manager can set up the environment for systemd running inside a container. These Interfaces are documented in [Container Interface of systemd](/CONTAINER_INTERFACE). -VM virtualization is more comprehensive and fewer integration APIs are available. -In fact there's only one: a VM manager may initialize the SMBIOS DMI field "Product UUUID" to a UUID uniquely identifying this virtual machine instance. -This is read in the guest via `/sys/class/dmi/id/product_uuid`, and used as configuration source for `/etc/machine-id` if in the guest, if that file is not initialized yet. -Note that this is currently only supported for kvm hosts, but may be extended to other managers as well. +VM virtualization is more comprehensive and fewer integration APIs are available compared to containers. +See [The VM Interface](/VM_INTERFACE) for the full list of integration points, which includes system credentials via SMBIOS Type 11 vendor strings, readiness notification via `AF_VSOCK`, SSH access via `AF_VSOCK`, machine ID initialization from SMBIOS Product UUID, and kernel command line extension. diff --git a/docs/_includes/footer.html b/docs/_includes/footer.html index 1800c53ea39b9..da81b1a48d843 100644 --- a/docs/_includes/footer.html +++ b/docs/_includes/footer.html @@ -1,7 +1,7 @@ diff --git a/docs/style.css b/docs/style.css index ee0fc7f754ec6..d5072cca8a15d 100644 --- a/docs/style.css +++ b/docs/style.css @@ -111,10 +111,15 @@ a { text-decoration: none; color: var(--sd-link-color); cursor: pointer; + overflow-wrap: anywhere; } a:hover { text-decoration: underline; } +img { + max-width: 100%; + height: auto; +} b { font-weight: 600; } @@ -567,6 +572,7 @@ tbody td { code.highlighter-rouge { padding: 2px 6px; background-color: var(--sd-highlight-inline-bg); + overflow-wrap: anywhere; } a code.highlighter-rouge { diff --git a/hwdb.d/20-OUI.hwdb b/hwdb.d/20-OUI.hwdb index 0235e11838b22..ace393450eda4 100644 --- a/hwdb.d/20-OUI.hwdb +++ b/hwdb.d/20-OUI.hwdb @@ -9804,7 +9804,7 @@ OUI:000CDD* ID_OUI_FROM_DATABASE=AOS technologies AG OUI:000CDE* - ID_OUI_FROM_DATABASE=ABB AG. + ID_OUI_FROM_DATABASE=ABB AG OUI:000CDF* ID_OUI_FROM_DATABASE=JAI Manufacturing @@ -14319,7 +14319,7 @@ OUI:0012BE* ID_OUI_FROM_DATABASE=Astek Corporation OUI:0012BF* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:0012C0* ID_OUI_FROM_DATABASE=HotLava Systems, Inc. @@ -17676,7 +17676,7 @@ OUI:00171D* ID_OUI_FROM_DATABASE=DIGIT OUI:00171E* - ID_OUI_FROM_DATABASE=Theo Benning GmbH & Co. KG + ID_OUI_FROM_DATABASE=Benning Elektrotechnik und Elektronik GmbH & Co. KG OUI:00171F* ID_OUI_FROM_DATABASE=IMV Corporation @@ -20016,7 +20016,7 @@ OUI:001A29* ID_OUI_FROM_DATABASE=Johnson Outdoors Marine Electronics d/b/a Minnkota OUI:001A2A* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:001A2B* ID_OUI_FROM_DATABASE=Ayecom Technology Co., Ltd. @@ -22869,7 +22869,7 @@ OUI:001D18* ID_OUI_FROM_DATABASE=Power Innovation GmbH OUI:001D19* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:001D1A* ID_OUI_FROM_DATABASE=OvisLink S.A. @@ -27426,7 +27426,7 @@ OUI:002307* ID_OUI_FROM_DATABASE=FUTURE INNOVATION TECH CO.,LTD OUI:002308* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:002309* ID_OUI_FROM_DATABASE=Janam Technologies LLC @@ -28683,7 +28683,7 @@ OUI:0024AD* ID_OUI_FROM_DATABASE=Adolf Thies Gmbh & Co. KG OUI:0024AE* - ID_OUI_FROM_DATABASE=IDEMIA FRANCE SAS + ID_OUI_FROM_DATABASE=IDEMIA PUBLIC SECURITY FRANCE OUI:0024AF* ID_OUI_FROM_DATABASE=Dish Technologies Corp @@ -29913,7 +29913,7 @@ OUI:00264C* ID_OUI_FROM_DATABASE=Shanghai DigiVision Technology Co., Ltd. OUI:00264D* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:00264E* ID_OUI_FROM_DATABASE=r2p GmbH @@ -34214,6 +34214,9 @@ OUI:007686* OUI:0076B1* ID_OUI_FROM_DATABASE=Somfy-Protect By Myfox SAS +OUI:0076B6* + ID_OUI_FROM_DATABASE=Ford Motor Company + OUI:00778D* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -34256,6 +34259,9 @@ OUI:007E56* OUI:007E95* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:007F1D* + ID_OUI_FROM_DATABASE=Fantasia Trading LLC + OUI:007F28* ID_OUI_FROM_DATABASE=Actiontec Electronics, Inc @@ -43028,6 +43034,9 @@ OUI:08CD9B* OUI:08CE94* ID_OUI_FROM_DATABASE=EM Microelectronic +OUI:08D01E* + ID_OUI_FROM_DATABASE=Juniper Networks + OUI:08D09F* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -45377,6 +45386,9 @@ OUI:10394E* OUI:1039E9* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:103A5D* + ID_OUI_FROM_DATABASE=Emerson + OUI:103B59* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -45443,6 +45455,9 @@ OUI:1047E7* OUI:1048B1* ID_OUI_FROM_DATABASE=Beijing Duokan Technology Limited +OUI:10490E* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:104963* ID_OUI_FROM_DATABASE=HARTING K.K. @@ -46133,6 +46148,9 @@ OUI:10C0D5* OUI:10C172* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:10C197* + ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd + OUI:10C22F* ID_OUI_FROM_DATABASE=China Entropy Co., Ltd. @@ -46364,6 +46382,9 @@ OUI:10E4C2* OUI:10E66B* ID_OUI_FROM_DATABASE=Kaon Broadband CO., LTD. +OUI:10E676* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:10E68F* ID_OUI_FROM_DATABASE=KWANGSUNG ELECTRONICS KOREA CO.,LTD. @@ -46526,6 +46547,9 @@ OUI:1409B4* OUI:1409DC* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:140A02* + ID_OUI_FROM_DATABASE=SHENZHEN BILIAN ELECTRONIC CO.,LTD + OUI:140A29* ID_OUI_FROM_DATABASE=Tiinlab Corporation @@ -48564,7 +48588,7 @@ OUI:188331* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd OUI:1883BF* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:188410* ID_OUI_FROM_DATABASE=CoreTrust Inc. @@ -49196,6 +49220,9 @@ OUI:18D9EF* OUI:18DBF2* ID_OUI_FROM_DATABASE=Dell Inc. +OUI:18DC12* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:18DC56* ID_OUI_FROM_DATABASE=Yulong Computer Telecommunication Scientific (Shenzhen) Co.,Ltd @@ -49316,6 +49343,9 @@ OUI:18F46A* OUI:18F46B* ID_OUI_FROM_DATABASE=Telenor Connexion AB +OUI:18F58B* + ID_OUI_FROM_DATABASE=GlobalReach Technology EMEA Ltd + OUI:18F643* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -50832,7 +50862,7 @@ OUI:1CC586* ID_OUI_FROM_DATABASE=Absolute Acoustics OUI:1CC63C* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:1CC72D* ID_OUI_FROM_DATABASE=Shenzhen Huapu Digital CO.,Ltd @@ -50972,6 +51002,9 @@ OUI:1CE209* OUI:1CE2CC* ID_OUI_FROM_DATABASE=Texas Instruments +OUI:1CE4DD* + ID_OUI_FROM_DATABASE=Technicolor (China) Technology Co., Ltd. + OUI:1CE504* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -51569,6 +51602,9 @@ OUI:20415A* OUI:204181* ID_OUI_FROM_DATABASE=ESYSE GmbH Embedded Systems Engineering +OUI:2041BC* + ID_OUI_FROM_DATABASE=ANY Electronics Co., Ltd + OUI:2043A8* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -52097,6 +52133,48 @@ OUI:20B001* OUI:20B0F7* ID_OUI_FROM_DATABASE=Enclustra GmbH +OUI:20B37F0* + ID_OUI_FROM_DATABASE=Chelsio Communications Inc + +OUI:20B37F1* + ID_OUI_FROM_DATABASE=TDK-Lambda UK + +OUI:20B37F2* + ID_OUI_FROM_DATABASE=Aina Computers ,Inc. + +OUI:20B37F3* + ID_OUI_FROM_DATABASE=QT medical inc + +OUI:20B37F4* + ID_OUI_FROM_DATABASE=OTP CO.,LTD. + +OUI:20B37F5* + ID_OUI_FROM_DATABASE=Shenzhen HantangFengyun Technology Co.,Ltd + +OUI:20B37F6* + ID_OUI_FROM_DATABASE=Kitchen Armor + +OUI:20B37F7* + ID_OUI_FROM_DATABASE=Luxedo + +OUI:20B37F8* + ID_OUI_FROM_DATABASE=Xconnect LLP + +OUI:20B37F9* + ID_OUI_FROM_DATABASE=Annapurna labs + +OUI:20B37FA* + ID_OUI_FROM_DATABASE=ShenZhen C&D Electronics CO.Ltd. + +OUI:20B37FB* + ID_OUI_FROM_DATABASE=B810 SPA + +OUI:20B37FC* + ID_OUI_FROM_DATABASE=EGSTON Power Electronics GmbH + +OUI:20B37FD* + ID_OUI_FROM_DATABASE=Xunmu Information Technology (Shanghai) Co., Ltd. + OUI:20B399* ID_OUI_FROM_DATABASE=Enterasys @@ -53219,6 +53297,9 @@ OUI:247E12* OUI:247E51* ID_OUI_FROM_DATABASE=zte corporation +OUI:247E7F* + ID_OUI_FROM_DATABASE=D-Fend Solutions A.D Ltd + OUI:247F20* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS @@ -54701,6 +54782,9 @@ OUI:28852D* OUI:2885BB* ID_OUI_FROM_DATABASE=Zen Exim Pvt. Ltd. +OUI:28875F* + ID_OUI_FROM_DATABASE=Annapurna labs + OUI:288761* ID_OUI_FROM_DATABASE=LG Innotek @@ -56654,6 +56738,9 @@ OUI:2CABA4* OUI:2CABEB* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:2CABEE* + ID_OUI_FROM_DATABASE=EM Microelectronic + OUI:2CAC44* ID_OUI_FROM_DATABASE=CONEXTOP @@ -56693,6 +56780,9 @@ OUI:2CB301* OUI:2CB43A* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:2CB471* + ID_OUI_FROM_DATABASE=Tuya Smart Inc. + OUI:2CB68F* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -57167,6 +57257,9 @@ OUI:30074D* OUI:30075C* ID_OUI_FROM_DATABASE=43403 +OUI:30084D* + ID_OUI_FROM_DATABASE=Trumpf Hüttinger + OUI:3009C0* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company @@ -57344,6 +57437,9 @@ OUI:301ABA* OUI:301B97* ID_OUI_FROM_DATABASE=Lierda Science & Technology Group Co.,Ltd +OUI:301C22* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:301D49* ID_OUI_FROM_DATABASE=Firmus Technologies Pty Ltd @@ -58133,6 +58229,9 @@ OUI:30A452* OUI:30A612* ID_OUI_FROM_DATABASE=ShenZhen Hugsun Technology Co.,Ltd. +OUI:30A771* + ID_OUI_FROM_DATABASE=Jiang Su Fulian Communication Technology Co.,Ltd + OUI:30A889* ID_OUI_FROM_DATABASE=DECIMATOR DESIGN @@ -60926,6 +61025,9 @@ OUI:386BBB* OUI:386C9B* ID_OUI_FROM_DATABASE=Ivy Biomedical +OUI:386DED* + ID_OUI_FROM_DATABASE=Juniper Networks + OUI:386E21* ID_OUI_FROM_DATABASE=Wasion Group Ltd. @@ -61196,6 +61298,9 @@ OUI:38A067* OUI:38A28C* ID_OUI_FROM_DATABASE=SHENZHEN RF-LINK TECHNOLOGY CO.,LTD. +OUI:38A3E0* + ID_OUI_FROM_DATABASE=1Finity Inc + OUI:38A44B* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -61325,6 +61430,51 @@ OUI:38AFD7* OUI:38B12D* ID_OUI_FROM_DATABASE=Sonotronic Nagel GmbH +OUI:38B14E0* + ID_OUI_FROM_DATABASE=Shenzhen Tongchuang Mechatronics co,LtD. + +OUI:38B14E1* + ID_OUI_FROM_DATABASE=Shenzhen Mondo Technology Co,.Ltd + +OUI:38B14E2* + ID_OUI_FROM_DATABASE=Marssun + +OUI:38B14E3* + ID_OUI_FROM_DATABASE=QRONOZ CO., Ltd. + +OUI:38B14E4* + ID_OUI_FROM_DATABASE=Noitom Robotics Technology (Beijing) Co.,Ltd. + +OUI:38B14E5* + ID_OUI_FROM_DATABASE=Brookhaven National Laboratory + +OUI:38B14E6* + ID_OUI_FROM_DATABASE=Universal Robots A/S + +OUI:38B14E7* + ID_OUI_FROM_DATABASE=NACE + +OUI:38B14E8* + ID_OUI_FROM_DATABASE=QNION Co.,Ltd + +OUI:38B14E9* + ID_OUI_FROM_DATABASE=DCL COMMUNICATION PTE. LTD. + +OUI:38B14EA* + ID_OUI_FROM_DATABASE=Amissiontech Co., Ltd + +OUI:38B14EB* + ID_OUI_FROM_DATABASE=Huizhou GYXX Technology Co., Ltd + +OUI:38B14EC* + ID_OUI_FROM_DATABASE=Guangzhou Sunrise Technology Co., Ltd. + +OUI:38B14ED* + ID_OUI_FROM_DATABASE=Private + +OUI:38B14EE* + ID_OUI_FROM_DATABASE=Knit Sound Company + OUI:38B19E0* ID_OUI_FROM_DATABASE=Triple Jump Medical @@ -61595,6 +61745,9 @@ OUI:38E08E* OUI:38E13D* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:38E158* + ID_OUI_FROM_DATABASE=Flaircomm Microelectronics,Inc. + OUI:38E1AA* ID_OUI_FROM_DATABASE=zte corporation @@ -62672,6 +62825,9 @@ OUI:3C7D0A* OUI:3C7DB1* ID_OUI_FROM_DATABASE=Texas Instruments +OUI:3C7F6E* + ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd + OUI:3C7F6F* ID_OUI_FROM_DATABASE=Telechips, Inc. @@ -62966,6 +63122,9 @@ OUI:3CB87A* OUI:3CB8D6* ID_OUI_FROM_DATABASE=Bluebank Communication Technology Co.,Ltd. +OUI:3CB922* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:3CB9A6* ID_OUI_FROM_DATABASE=Belden Deutschland GmbH @@ -64061,6 +64220,9 @@ OUI:407911* OUI:407912* ID_OUI_FROM_DATABASE=Texas Instruments +OUI:407955* + ID_OUI_FROM_DATABASE=Datacolor + OUI:407A80* ID_OUI_FROM_DATABASE=Nokia Corporation @@ -64430,6 +64592,9 @@ OUI:40B8C2* OUI:40B93C* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise +OUI:40BA09* + ID_OUI_FROM_DATABASE=Dell Inc. + OUI:40BA61* ID_OUI_FROM_DATABASE=ARIMA Communications Corp. @@ -65249,6 +65414,9 @@ OUI:444988* OUI:444A37* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd +OUI:444A4C* + ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. + OUI:444A65* ID_OUI_FROM_DATABASE=Silverflare Ltd. @@ -65510,6 +65678,9 @@ OUI:447654* OUI:4476E7* ID_OUI_FROM_DATABASE=TECNO MOBILE LIMITED +OUI:447831* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:44783E* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -65618,6 +65789,9 @@ OUI:448F17* OUI:449046* ID_OUI_FROM_DATABASE=Honor Device Co., Ltd. +OUI:4490BA* + ID_OUI_FROM_DATABASE=CHINA DRAGON TECHNOLOGY LIMITED + OUI:4490BB* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -65648,6 +65822,9 @@ OUI:44962B* OUI:44975A* ID_OUI_FROM_DATABASE=SHENZHEN FAST TECHNOLOGIES CO.,LTD +OUI:449A52* + ID_OUI_FROM_DATABASE=zte corporation + OUI:449B78* ID_OUI_FROM_DATABASE=The Now Factory @@ -65813,6 +65990,9 @@ OUI:44AEAB* OUI:44AF28* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:44B176* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:44B295* ID_OUI_FROM_DATABASE=Sichuan AI-Link Technology Co., Ltd. @@ -67196,6 +67376,9 @@ OUI:48A9D2* OUI:48AA5D* ID_OUI_FROM_DATABASE=Store Electronic Systems +OUI:48AABB* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:48AD08* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -67761,7 +67944,7 @@ OUI:4C09B4* ID_OUI_FROM_DATABASE=zte corporation OUI:4C09D4* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:4C09FA* ID_OUI_FROM_DATABASE=FRONTIER SMART TECHNOLOGIES LTD @@ -69584,6 +69767,9 @@ OUI:50338B* OUI:5033F0* ID_OUI_FROM_DATABASE=YICHEN (SHENZHEN) TECHNOLOGY CO.LTD +OUI:5037CD* + ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co., Ltd. + OUI:50382F* ID_OUI_FROM_DATABASE=ASE Group Chung-Li @@ -69875,6 +70061,9 @@ OUI:50617E* OUI:506184* ID_OUI_FROM_DATABASE=Avaya Inc +OUI:506188* + ID_OUI_FROM_DATABASE=PLANET Technology Corporation + OUI:5061BF* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -69935,6 +70124,9 @@ OUI:506255E* OUI:506313* ID_OUI_FROM_DATABASE=Hon Hai Precision Ind. Co.,Ltd. +OUI:506382* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:506391* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -70068,7 +70260,7 @@ OUI:507D02* ID_OUI_FROM_DATABASE=BIODIT OUI:507E5D* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:50804A* ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. @@ -71051,6 +71243,9 @@ OUI:541310* OUI:541379* ID_OUI_FROM_DATABASE=Hon Hai Precision Ind. Co.,Ltd. +OUI:54138F* + ID_OUI_FROM_DATABASE=GEOIDE Crypto&Com + OUI:5413CA* ID_OUI_FROM_DATABASE=ITEL MOBILE LIMITED @@ -72131,6 +72326,9 @@ OUI:54E63F* OUI:54E6FC* ID_OUI_FROM_DATABASE=TP-LINK TECHNOLOGIES CO.,LTD. +OUI:54E6FD* + ID_OUI_FROM_DATABASE=Sony Interactive Entertainment Inc. + OUI:54E7D5* ID_OUI_FROM_DATABASE=Sun Cupid Technology (HK) LTD @@ -72422,6 +72620,9 @@ OUI:5820B1* OUI:582136* ID_OUI_FROM_DATABASE=KMB systems, s.r.o. +OUI:58219D* + ID_OUI_FROM_DATABASE=Shanghai Timar Integrated Circuit Co., LTD + OUI:5821E9* ID_OUI_FROM_DATABASE=TWPI @@ -72497,6 +72698,9 @@ OUI:58278C* OUI:582A93* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:582ABD* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:582AF7* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -74459,6 +74663,9 @@ OUI:5C80B6* OUI:5C81A7* ID_OUI_FROM_DATABASE=Network Devices Pty Ltd +OUI:5C8217* + ID_OUI_FROM_DATABASE=DSE srl + OUI:5C836C* ID_OUI_FROM_DATABASE=Ruckus Wireless @@ -74741,6 +74948,9 @@ OUI:5CA721* OUI:5CA86A* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:5CA931* + ID_OUI_FROM_DATABASE=38436 + OUI:5CA933* ID_OUI_FROM_DATABASE=Luma Home @@ -74825,6 +75035,9 @@ OUI:5CBA2C* OUI:5CBA37* ID_OUI_FROM_DATABASE=Microsoft Corporation +OUI:5CBA75* + ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co., Ltd. + OUI:5CBAEF* ID_OUI_FROM_DATABASE=CHONGQING FUGUI ELECTRONICS CO.,LTD. @@ -74979,7 +75192,7 @@ OUI:5CDC49* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd OUI:5CDC96* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:5CDD70* ID_OUI_FROM_DATABASE=Hangzhou H3C Technologies Co., Limited @@ -75746,6 +75959,9 @@ OUI:6052D0* OUI:605317* ID_OUI_FROM_DATABASE=Sandstone Technologies +OUI:605355* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:605375* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -76073,6 +76289,9 @@ OUI:609316* OUI:609532* ID_OUI_FROM_DATABASE=Zebra Technologies Inc. +OUI:609578* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:6095BD* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -77678,6 +77897,9 @@ OUI:64A965* OUI:64AC2B* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:64ACE0* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:64AE0C* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -77819,6 +78041,9 @@ OUI:64C905* OUI:64C944* ID_OUI_FROM_DATABASE=LARK Technologies, Inc +OUI:64CA80* + ID_OUI_FROM_DATABASE=Realme Chongqing Mobile Telecommunications Corp.,Ltd. + OUI:64CB5D* ID_OUI_FROM_DATABASE=SIA TeleSet @@ -78464,6 +78689,9 @@ OUI:684749* OUI:684898* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:6848B4* + ID_OUI_FROM_DATABASE=AltoBeam Inc. + OUI:684983* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -78962,6 +79190,9 @@ OUI:689E19* OUI:689E29* ID_OUI_FROM_DATABASE=zte corporation +OUI:689E67* + ID_OUI_FROM_DATABASE=SHENZHEN FOCUSCOM TECHNOLOGIES CO., LTD + OUI:689E6A* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -79133,6 +79364,9 @@ OUI:68C63A* OUI:68C6AC* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:68C8C0* + ID_OUI_FROM_DATABASE=GSD VIET NAM TECHNOLOGY COMPANY LIMITED + OUI:68C8EB* ID_OUI_FROM_DATABASE=Rockwell Automation @@ -79358,6 +79592,9 @@ OUI:68EE4B* OUI:68EE88* ID_OUI_FROM_DATABASE=Shenzhen TINNO Mobile Technology Corp. +OUI:68EE8F* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:68EE96* ID_OUI_FROM_DATABASE=Cisco SPVTG @@ -80648,6 +80885,9 @@ OUI:6CBAB8* OUI:6CBEE9* ID_OUI_FROM_DATABASE=Alcatel-Lucent IPD +OUI:6CBF2F* + ID_OUI_FROM_DATABASE=eero inc. + OUI:6CBFB5* ID_OUI_FROM_DATABASE=Noon Technology Co., Ltd @@ -80831,6 +81071,9 @@ OUI:6CE01E* OUI:6CE0B0* ID_OUI_FROM_DATABASE=SOUND4 +OUI:6CE20C* + ID_OUI_FROM_DATABASE=Hangzhou SDIC Microelectronics Inc. + OUI:6CE2D3* ID_OUI_FROM_DATABASE=New H3C Technologies Co., Ltd @@ -81713,6 +81956,9 @@ OUI:70708B* OUI:7070AA* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. +OUI:7070D5* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:7070FC* ID_OUI_FROM_DATABASE=GOLD&WATER INDUSTRIAL LIMITED @@ -90894,7 +91140,7 @@ OUI:70B3D5B77* ID_OUI_FROM_DATABASE=Motec Pty Ltd OUI:70B3D5B78* - ID_OUI_FROM_DATABASE=HOERMANN GmbH + ID_OUI_FROM_DATABASE=Hörmann Warnsysteme GmbH OUI:70B3D5B79* ID_OUI_FROM_DATABASE=Dadacon GmbH @@ -94826,6 +95072,9 @@ OUI:7412B3* OUI:7412BB* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD +OUI:74136A* + ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company + OUI:7413EA* ID_OUI_FROM_DATABASE=Intel Corporate @@ -94988,6 +95237,9 @@ OUI:74249F* OUI:7424CA* ID_OUI_FROM_DATABASE=Guangzhou Shiyuan Electronic Technology Company Limited +OUI:742554* + ID_OUI_FROM_DATABASE=NVIDIA Corporation + OUI:7425840* ID_OUI_FROM_DATABASE=Alcon Wireless Private Limited @@ -95109,7 +95361,7 @@ OUI:7430AF* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD OUI:743170* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:743174* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -96431,6 +96683,9 @@ OUI:78078F* OUI:78084D* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:780A57* + ID_OUI_FROM_DATABASE=Shanghai Lightningsemi Technology Co.,Ltd. + OUI:780AC7* ID_OUI_FROM_DATABASE=Baofeng TV Co., Ltd. @@ -96839,6 +97094,9 @@ OUI:7845B3* OUI:7845C4* ID_OUI_FROM_DATABASE=Dell Inc. +OUI:7845DC* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:78465C* ID_OUI_FROM_DATABASE=CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. @@ -98598,7 +98856,7 @@ OUI:7C4F7D* ID_OUI_FROM_DATABASE=Sawwave OUI:7C4FB5* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:7C5049* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -98642,6 +98900,9 @@ OUI:7C5A1C* OUI:7C5A67* ID_OUI_FROM_DATABASE=JNC Systems, Inc. +OUI:7C5C8D* + ID_OUI_FROM_DATABASE=EM Microelectronic + OUI:7C5CF8* ID_OUI_FROM_DATABASE=Intel Corporate @@ -102075,7 +102336,7 @@ OUI:849CA4* ID_OUI_FROM_DATABASE=Mimosa Networks OUI:849CA6* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:849D4B* ID_OUI_FROM_DATABASE=Shenzhen Boomtech Industrial Corporation @@ -102182,6 +102443,9 @@ OUI:84AD58* OUI:84AD8D* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:84AEDE* + ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd + OUI:84AF1F* ID_OUI_FROM_DATABASE=GopherTec Inc. @@ -102684,7 +102948,7 @@ OUI:88034C* ID_OUI_FROM_DATABASE=WEIFANG GOERTEK ELECTRONICS CO.,LTD OUI:880355* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:8803E9* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -102837,7 +103101,7 @@ OUI:882510* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise OUI:88252C* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:882593* ID_OUI_FROM_DATABASE=TP-LINK TECHNOLOGIES CO.,LTD. @@ -104777,6 +105041,9 @@ OUI:8C1F640A0* OUI:8C1F640A2* ID_OUI_FROM_DATABASE=BEST +OUI:8C1F640A3* + ID_OUI_FROM_DATABASE=Fischer & Connectors SA + OUI:8C1F640A4* ID_OUI_FROM_DATABASE=Dynamic Research, Inc. @@ -104786,6 +105053,9 @@ OUI:8C1F640A5* OUI:8C1F640A8* ID_OUI_FROM_DATABASE=SamabaNova Systems +OUI:8C1F640A9* + ID_OUI_FROM_DATABASE=RFT Corp. + OUI:8C1F640AA* ID_OUI_FROM_DATABASE=DI3 INFOTECH LLP @@ -105185,6 +105455,9 @@ OUI:8C1F6417C* OUI:8C1F6417E* ID_OUI_FROM_DATABASE=MI Inc. +OUI:8C1F6417F* + ID_OUI_FROM_DATABASE=BCMTECH + OUI:8C1F64180* ID_OUI_FROM_DATABASE=Structural Integrity Services @@ -105308,6 +105581,9 @@ OUI:8C1F641C2* OUI:8C1F641C4* ID_OUI_FROM_DATABASE=EDGX bv +OUI:8C1F641C5* + ID_OUI_FROM_DATABASE=Breas Medical AB + OUI:8C1F641C9* ID_OUI_FROM_DATABASE=Pneumax Spa @@ -105458,6 +105734,9 @@ OUI:8C1F64211* OUI:8C1F64215* ID_OUI_FROM_DATABASE=XLOGIC srl +OUI:8C1F64216* + ID_OUI_FROM_DATABASE=Hiwin Mikrosystem Corp. + OUI:8C1F64219* ID_OUI_FROM_DATABASE=Guangzhou Desam Audio Co.,Ltd @@ -105605,6 +105884,9 @@ OUI:8C1F64269* OUI:8C1F6426B* ID_OUI_FROM_DATABASE=Profcon AB +OUI:8C1F6426C* + ID_OUI_FROM_DATABASE=Diatech co.,ltd. + OUI:8C1F6426E* ID_OUI_FROM_DATABASE=Koizumi Lighting Technology Corp. @@ -106346,6 +106628,9 @@ OUI:8C1F643DF* OUI:8C1F643E0* ID_OUI_FROM_DATABASE=YPP Corporation +OUI:8C1F643E1* + ID_OUI_FROM_DATABASE=CRUXELL Corp. + OUI:8C1F643E2* ID_OUI_FROM_DATABASE=Agrico @@ -106487,12 +106772,18 @@ OUI:8C1F6442D* OUI:8C1F6442F* ID_OUI_FROM_DATABASE=Tomorrow Companies Inc +OUI:8C1F64431* + ID_OUI_FROM_DATABASE=Pneumax Spa + OUI:8C1F64432* ID_OUI_FROM_DATABASE=Rebel Systems OUI:8C1F64434* ID_OUI_FROM_DATABASE=netmon +OUI:8C1F64436* + ID_OUI_FROM_DATABASE=Vision Systems Safety Tech + OUI:8C1F64437* ID_OUI_FROM_DATABASE=Gogo BA @@ -106598,12 +106889,18 @@ OUI:8C1F64466* OUI:8C1F6446A* ID_OUI_FROM_DATABASE=Pharsighted LLC +OUI:8C1F6446B* + ID_OUI_FROM_DATABASE=PERSOL EXCEL HR PARTNERS CO., LTD. + OUI:8C1F6446D* ID_OUI_FROM_DATABASE=MB connect line GmbH OUI:8C1F64470* ID_OUI_FROM_DATABASE=Canfield Scientific Inc +OUI:8C1F64471* + ID_OUI_FROM_DATABASE=Apantac LLC + OUI:8C1F64472* ID_OUI_FROM_DATABASE=Surge Networks, Inc. @@ -106952,9 +107249,15 @@ OUI:8C1F64519* OUI:8C1F6451A* ID_OUI_FROM_DATABASE=TELE Haase Steuergeräte Ges.m.b.H +OUI:8C1F6451E* + ID_OUI_FROM_DATABASE=Owl Home Inc. + OUI:8C1F64521* ID_OUI_FROM_DATABASE=MP-SENSOR GmbH +OUI:8C1F64522* + ID_OUI_FROM_DATABASE=CloudRAN.ai + OUI:8C1F64523* ID_OUI_FROM_DATABASE=SPEKTRA Schwingungstechnik und Akustik GmbH Dresden @@ -107165,6 +107468,9 @@ OUI:8C1F6458C* OUI:8C1F6458E* ID_OUI_FROM_DATABASE=Novanta IMS +OUI:8C1F64590* + ID_OUI_FROM_DATABASE=Teledyne Scientific and Imaging + OUI:8C1F64591* ID_OUI_FROM_DATABASE=MB connect line GmbH Fernwartungssysteme @@ -107201,6 +107507,9 @@ OUI:8C1F645A0* OUI:8C1F645A1* ID_OUI_FROM_DATABASE=Breas Medical AB +OUI:8C1F645A2* + ID_OUI_FROM_DATABASE=CMI, Inc. + OUI:8C1F645A4* ID_OUI_FROM_DATABASE=DAVE SRL @@ -107381,6 +107690,9 @@ OUI:8C1F645FB* OUI:8C1F645FC* ID_OUI_FROM_DATABASE=Lance Design LLC +OUI:8C1F645FE* + ID_OUI_FROM_DATABASE=Pneumax Spa + OUI:8C1F645FF* ID_OUI_FROM_DATABASE=DAVE SRL @@ -107519,6 +107831,9 @@ OUI:8C1F64647* OUI:8C1F64648* ID_OUI_FROM_DATABASE=Gridpulse c.o.o. +OUI:8C1F6464C* + ID_OUI_FROM_DATABASE=ACS Motion Control + OUI:8C1F6464D* ID_OUI_FROM_DATABASE=NEWONE CO.,LTD. @@ -107579,6 +107894,9 @@ OUI:8C1F64663* OUI:8C1F64664* ID_OUI_FROM_DATABASE=Thermoeye Inc +OUI:8C1F64668* + ID_OUI_FROM_DATABASE=Johnson and Johnson Medtech + OUI:8C1F6466B* ID_OUI_FROM_DATABASE=Currux Vision LLC @@ -108413,6 +108731,9 @@ OUI:8C1F64803* OUI:8C1F64804* ID_OUI_FROM_DATABASE=EA Elektro-Automatik GmbH +OUI:8C1F64805* + ID_OUI_FROM_DATABASE=ATAL s.r.o. + OUI:8C1F64806* ID_OUI_FROM_DATABASE=Matrixspace @@ -109100,6 +109421,9 @@ OUI:8C1F64972* OUI:8C1F64973* ID_OUI_FROM_DATABASE=Dorsett Technologies Inc +OUI:8C1F64976* + ID_OUI_FROM_DATABASE=JES Electronic Systems Private Limited + OUI:8C1F64978* ID_OUI_FROM_DATABASE=Planet Innovation Products Inc. @@ -109730,6 +110054,9 @@ OUI:8C1F64AC4* OUI:8C1F64AC5* ID_OUI_FROM_DATABASE=Forever Engineering Systems Pvt. Ltd. +OUI:8C1F64AC6* + ID_OUI_FROM_DATABASE=Starts Facility Service Co.,Ltd + OUI:8C1F64AC8* ID_OUI_FROM_DATABASE=Teledatics Incorporated @@ -109745,6 +110072,9 @@ OUI:8C1F64ACD* OUI:8C1F64ACE* ID_OUI_FROM_DATABASE=Rayhaan Networks +OUI:8C1F64ACF* + ID_OUI_FROM_DATABASE=PROVENRUN + OUI:8C1F64AD0* ID_OUI_FROM_DATABASE=Elektrotechnik & Elektronik Oltmann GmbH @@ -110249,6 +110579,9 @@ OUI:8C1F64BC3* OUI:8C1F64BC4* ID_OUI_FROM_DATABASE=EasyNet Industry (Shenzhen) Co., Ltd +OUI:8C1F64BC5* + ID_OUI_FROM_DATABASE=DORLET SAU + OUI:8C1F64BC6* ID_OUI_FROM_DATABASE=Chengdu ZiChen Time&Frequency Technology Co.,Ltd @@ -110273,6 +110606,9 @@ OUI:8C1F64BCD* OUI:8C1F64BCE* ID_OUI_FROM_DATABASE=BESO sp. z o.o. +OUI:8C1F64BCF* + ID_OUI_FROM_DATABASE=Erba Lachema s.r.o. + OUI:8C1F64BD0* ID_OUI_FROM_DATABASE=Mesa Labs, Inc. @@ -110507,6 +110843,9 @@ OUI:8C1F64C45* OUI:8C1F64C46* ID_OUI_FROM_DATABASE=Inex Technologies +OUI:8C1F64C48* + ID_OUI_FROM_DATABASE=AK Automation + OUI:8C1F64C4A* ID_OUI_FROM_DATABASE=SGi Technology Group Ltd. @@ -110954,6 +111293,9 @@ OUI:8C1F64D29* OUI:8C1F64D2A* ID_OUI_FROM_DATABASE=Anteus Kft. +OUI:8C1F64D2C* + ID_OUI_FROM_DATABASE=DEUTA Werke GmbH + OUI:8C1F64D2D* ID_OUI_FROM_DATABASE=Eskomar Ltd. @@ -111059,12 +111401,18 @@ OUI:8C1F64D63* OUI:8C1F64D64* ID_OUI_FROM_DATABASE=Potter Electric Signal Co. LLC +OUI:8C1F64D67* + ID_OUI_FROM_DATABASE=Groundtruth Ltd + OUI:8C1F64D69* ID_OUI_FROM_DATABASE=ADiCo Corporation OUI:8C1F64D6C* ID_OUI_FROM_DATABASE=Packetalk LLC +OUI:8C1F64D6F* + ID_OUI_FROM_DATABASE=ARKTRON ELECTRONICS + OUI:8C1F64D71* ID_OUI_FROM_DATABASE=Computech International @@ -111155,6 +111503,9 @@ OUI:8C1F64D9D* OUI:8C1F64D9E* ID_OUI_FROM_DATABASE=Wagner Group GmbH +OUI:8C1F64DA0* + ID_OUI_FROM_DATABASE=Sensata Technologies Inc. + OUI:8C1F64DA1* ID_OUI_FROM_DATABASE=Hangteng (HK) Technology Co., Limited @@ -111389,6 +111740,9 @@ OUI:8C1F64E23* OUI:8C1F64E24* ID_OUI_FROM_DATABASE=COMETA SAS +OUI:8C1F64E25* + ID_OUI_FROM_DATABASE=HEITEC AG + OUI:8C1F64E26* ID_OUI_FROM_DATABASE=HyperSilicon Co.,Ltd @@ -111818,6 +112172,9 @@ OUI:8C1F64F13* OUI:8C1F64F14* ID_OUI_FROM_DATABASE=Elektrosil GmbH +OUI:8C1F64F16* + ID_OUI_FROM_DATABASE=Schildknecht AG + OUI:8C1F64F18* ID_OUI_FROM_DATABASE=Northern Design (Electronics) Ltd @@ -112235,6 +112592,9 @@ OUI:8C1F64FED* OUI:8C1F64FEE* ID_OUI_FROM_DATABASE=Leap Info Systems Pvt. Ltd. +OUI:8C1F64FF2* + ID_OUI_FROM_DATABASE=MITSUBISHI ELECTRIC INDIA PVT. LTD. + OUI:8C1F64FF3* ID_OUI_FROM_DATABASE=Fuzhou Tucsen Photonics Co.,Ltd @@ -112550,6 +112910,9 @@ OUI:8C5109E* OUI:8C5219* ID_OUI_FROM_DATABASE=SHARP Corporation +OUI:8C5387* + ID_OUI_FROM_DATABASE=Huzhou Luxshare Precision Industry Co.LTD + OUI:8C53C3* ID_OUI_FROM_DATABASE=Beijing Xiaomi Mobile Software Co., Ltd @@ -113765,6 +114128,9 @@ OUI:900DCB* OUI:900E83* ID_OUI_FROM_DATABASE=Monico Monitoring, Inc. +OUI:900E84* + ID_OUI_FROM_DATABASE=eero inc. + OUI:900E9E* ID_OUI_FROM_DATABASE=Shenzhen SuperElectron Technology Co.,Ltd. @@ -114269,6 +114635,9 @@ OUI:90623F* OUI:90633B* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:90649B* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:9064AD* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -115769,6 +116138,9 @@ OUI:9453FF* OUI:945493* ID_OUI_FROM_DATABASE=Rigado, LLC +OUI:9454A0* + ID_OUI_FROM_DATABASE=Fosilicon CO., Ltd + OUI:9454C5* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -117110,6 +117482,9 @@ OUI:982A0A* OUI:982AFD* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:982BA6* + ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company + OUI:982CBC* ID_OUI_FROM_DATABASE=Intel Corporate @@ -117185,6 +117560,9 @@ OUI:983B16* OUI:983B67* ID_OUI_FROM_DATABASE=DWnet Technologies(Suzhou) Corporation +OUI:983B8A* + ID_OUI_FROM_DATABASE=Sekisui Jushi CAP-AI System Co.,Ltd. + OUI:983B8F* ID_OUI_FROM_DATABASE=Intel Corporate @@ -119076,7 +119454,7 @@ OUI:9C807D* ID_OUI_FROM_DATABASE=SYSCABLE Korea Inc. OUI:9C80DF* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:9C823F* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -119429,6 +119807,9 @@ OUI:9CCAD9* OUI:9CCBF7* ID_OUI_FROM_DATABASE=CLOUD STAR TECHNOLOGY CO., LTD. +OUI:9CCC01* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:9CCC83* ID_OUI_FROM_DATABASE=Juniper Networks @@ -119438,6 +119819,9 @@ OUI:9CCD42* OUI:9CCD82* ID_OUI_FROM_DATABASE=CHENG UEI PRECISION INDUSTRY CO.,LTD +OUI:9CCE22* + ID_OUI_FROM_DATABASE=PROMED Soest GmbH + OUI:9CCE88* ID_OUI_FROM_DATABASE=Ruijie Networks Co.,LTD @@ -122297,6 +122681,9 @@ OUI:A4934C* OUI:A493AD* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:A493FE* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:A49426* ID_OUI_FROM_DATABASE=Elgama-Elektronika Ltd. @@ -122675,6 +123062,9 @@ OUI:A4D73C* OUI:A4D795* ID_OUI_FROM_DATABASE=Wingtech Mobile Communications Co.,Ltd +OUI:A4D7D6* + ID_OUI_FROM_DATABASE=Shenzhen Linkoh Network Technology Co;Ltd + OUI:A4D856* ID_OUI_FROM_DATABASE=Gimbal, Inc @@ -124005,7 +124395,7 @@ OUI:A8D3C8* ID_OUI_FROM_DATABASE=Wachendorff Automation GmbH & CO.KG OUI:A8D3F7* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:A8D409* ID_OUI_FROM_DATABASE=USA 111 Inc @@ -124580,6 +124970,9 @@ OUI:AC44F2* OUI:AC4500* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:AC45B0* + ID_OUI_FROM_DATABASE=Shenzhen Jidao Technology Co Ltd + OUI:AC45CA* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -125483,6 +125876,9 @@ OUI:ACE4B5* OUI:ACE5F0* ID_OUI_FROM_DATABASE=Doppler Labs +OUI:ACE606* + ID_OUI_FROM_DATABASE=Honor Device Co., Ltd. + OUI:ACE64B* ID_OUI_FROM_DATABASE=Shenzhen Baojia Battery Technology Co., Ltd. @@ -125858,6 +126254,9 @@ OUI:B02A1F* OUI:B02A43* ID_OUI_FROM_DATABASE=Google, Inc. +OUI:B02B64* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:B02EBA* ID_OUI_FROM_DATABASE=Earda Technologies co Ltd @@ -126251,6 +126650,9 @@ OUI:B07994* OUI:B07A16* ID_OUI_FROM_DATABASE=ROEHN +OUI:B07AA4* + ID_OUI_FROM_DATABASE=Guangzhou Punp Electronics Manufacturing Co., Ltd. + OUI:B07ADF* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -126962,6 +127364,9 @@ OUI:B0EE7B* OUI:B0F00C* ID_OUI_FROM_DATABASE=Dongguan Wecxw CO.,Ltd. +OUI:B0F079* + ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + OUI:B0F1A3* ID_OUI_FROM_DATABASE=Fengfan (BeiJing) Technology Co., Ltd. @@ -128144,6 +128549,9 @@ OUI:B4B5AF* OUI:B4B5B6* ID_OUI_FROM_DATABASE=CHONGQING FUGUI ELECTRONICS CO.,LTD. +OUI:B4B650* + ID_OUI_FROM_DATABASE=Realme Chongqing Mobile Telecommunications Corp.,Ltd. + OUI:B4B676* ID_OUI_FROM_DATABASE=Intel Corporate @@ -128549,6 +128957,9 @@ OUI:B808D7* OUI:B8098A* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:B80B9A* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:B80B9D* ID_OUI_FROM_DATABASE=ROPEX Industrie-Elektronik GmbH @@ -128729,6 +129140,9 @@ OUI:B837B2* OUI:B83861* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:B83865* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:B838CA* ID_OUI_FROM_DATABASE=Kyokko Tsushin System CO.,LTD @@ -128912,6 +129326,9 @@ OUI:B856BD* OUI:B85776* ID_OUI_FROM_DATABASE=lignex1 +OUI:B857D6* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:B857D8* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -129224,6 +129641,9 @@ OUI:B894E7* OUI:B89674* ID_OUI_FROM_DATABASE=AllDSP GmbH & Co. KG +OUI:B89734* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:B8975A* ID_OUI_FROM_DATABASE=BIOSTAR Microtech Int'l Corp. @@ -129557,6 +129977,9 @@ OUI:B8D526* OUI:B8D56B* ID_OUI_FROM_DATABASE=Mirka Ltd. +OUI:B8D5AD* + ID_OUI_FROM_DATABASE=Nokia + OUI:B8D61A* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -130463,6 +130886,9 @@ OUI:BC6778* OUI:BC6784* ID_OUI_FROM_DATABASE=Environics Oy +OUI:BC68C3* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:BC69CB* ID_OUI_FROM_DATABASE=Panasonic Electric Works Networks Co., Ltd. @@ -130958,6 +131384,9 @@ OUI:BCC342* OUI:BCC427* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:BCC436* + ID_OUI_FROM_DATABASE=Nokia + OUI:BCC493* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -131849,6 +132278,9 @@ OUI:C06911* OUI:C06B55* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company +OUI:C06BC7* + ID_OUI_FROM_DATABASE=Gallagher Group Limited + OUI:C06C0C* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -133445,6 +133877,48 @@ OUI:C4823F* OUI:C4824E* ID_OUI_FROM_DATABASE=Changzhou Uchip Electronics Co., LTD. +OUI:C482720* + ID_OUI_FROM_DATABASE=Gabriel Tecnologia + +OUI:C482721* + ID_OUI_FROM_DATABASE=Private + +OUI:C482722* + ID_OUI_FROM_DATABASE=Digisine Energytech Co., Ltd. + +OUI:C482724* + ID_OUI_FROM_DATABASE=Melecs EWS GmbH + +OUI:C482725* + ID_OUI_FROM_DATABASE=Schunk SE & Co. KG + +OUI:C482726* + ID_OUI_FROM_DATABASE=Mantenimiento y paileria + +OUI:C482727* + ID_OUI_FROM_DATABASE=Mode Sensors AS + +OUI:C482728* + ID_OUI_FROM_DATABASE=Shanghai Smart Logic Technology Ltd. + +OUI:C482729* + ID_OUI_FROM_DATABASE=Satways Ltd + +OUI:C48272A* + ID_OUI_FROM_DATABASE=Tolt Technologies LLC + +OUI:C48272B* + ID_OUI_FROM_DATABASE=MyPlace Australia Pty Ltd + +OUI:C48272C* + ID_OUI_FROM_DATABASE=E2-CAD + +OUI:C48272D* + ID_OUI_FROM_DATABASE=Posital B.V. + +OUI:C48272E* + ID_OUI_FROM_DATABASE=Smart Radar System, Inc + OUI:C482E1* ID_OUI_FROM_DATABASE=Tuya Smart Inc. @@ -134355,7 +134829,7 @@ OUI:C4FFBC3* ID_OUI_FROM_DATABASE=SHENZHEN KALIF ELECTRONICS CO.,LTD OUI:C4FFBC4* - ID_OUI_FROM_DATABASE=iMageTech CO.,LTD. + ID_OUI_FROM_DATABASE=HyperNet CO., LTD OUI:C4FFBC5* ID_OUI_FROM_DATABASE=comtime GmbH @@ -134552,6 +135026,9 @@ OUI:C82496* OUI:C825E1* ID_OUI_FROM_DATABASE=Lemobile Information Technology (Beijing) Co., Ltd +OUI:C82691* + ID_OUI_FROM_DATABASE=Arista Networks, Inc. + OUI:C826E2* ID_OUI_FROM_DATABASE=CHINA DRAGON TECHNOLOGY LIMITED @@ -135140,6 +135617,9 @@ OUI:C884A1* OUI:C884CF* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:C88541* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:C88550* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -135575,6 +136055,9 @@ OUI:C8C791* OUI:C8C83F* ID_OUI_FROM_DATABASE=Texas Instruments +OUI:C8C873* + ID_OUI_FROM_DATABASE=CHIPSEN INC. + OUI:C8C9A3* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -136007,6 +136490,9 @@ OUI:CC08FB* OUI:CC09C8* ID_OUI_FROM_DATABASE=IMAQLIQ LTD +OUI:CC0C9C* + ID_OUI_FROM_DATABASE=CIG SHANGHAI CO LTD + OUI:CC0CDA* ID_OUI_FROM_DATABASE=Miljovakt AS @@ -136112,6 +136598,9 @@ OUI:CC1E56* OUI:CC1E97* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:CC1EAB* + ID_OUI_FROM_DATABASE=LEDATEL sp. z o.o. i Wspólnicy sp.k + OUI:CC1EFF* ID_OUI_FROM_DATABASE=Metrological Group BV @@ -136247,6 +136736,9 @@ OUI:CC2F71* OUI:CC3080* ID_OUI_FROM_DATABASE=VAIO Corporation +OUI:CC3089* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:CC312A* ID_OUI_FROM_DATABASE=HUIZHOU TCL COMMUNICATION ELECTRON CO.,LTD @@ -137075,6 +137567,9 @@ OUI:CCC62B* OUI:CCC760* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:CCC837* + ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. + OUI:CCC8D7* ID_OUI_FROM_DATABASE=CIAS Elettronica srl @@ -137411,6 +137906,9 @@ OUI:CCFA00* OUI:CCFA66* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:CCFA95* + ID_OUI_FROM_DATABASE=Honor Device Co., Ltd. + OUI:CCFAF1* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS @@ -138296,6 +138794,9 @@ OUI:D0817A* OUI:D081C5* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:D082EB* + ID_OUI_FROM_DATABASE=Tuya Smart Inc. + OUI:D083D4* ID_OUI_FROM_DATABASE=Xtel Wireless ApS @@ -138458,6 +138959,9 @@ OUI:D09686D* OUI:D09686E* ID_OUI_FROM_DATABASE=withnetworks +OUI:D096EA* + ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. + OUI:D096FB* ID_OUI_FROM_DATABASE=Zhone Technologies, Inc. @@ -139532,6 +140036,9 @@ OUI:D4482D* OUI:D44867* ID_OUI_FROM_DATABASE=Silicon Laboratories +OUI:D44A85* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:D44B5E* ID_OUI_FROM_DATABASE=TAIYO YUDEN CO., LTD. @@ -139568,6 +140075,9 @@ OUI:D44F68* OUI:D44F80* ID_OUI_FROM_DATABASE=Kemper Digital GmbH +OUI:D45039* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:D4503F* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -139751,6 +140261,9 @@ OUI:D464F7* OUI:D46624* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:D46663* + ID_OUI_FROM_DATABASE=Shenzhen Detran Technology Co.,Ltd. + OUI:D466A8* ID_OUI_FROM_DATABASE=Riedo Networks Ltd @@ -140138,6 +140651,9 @@ OUI:D49F29* OUI:D49FDD* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:D49FF9* + ID_OUI_FROM_DATABASE=Earda Technologies co Ltd + OUI:D4A02A* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -141143,6 +141659,9 @@ OUI:D85B27* OUI:D85B2A* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:D85C11* + ID_OUI_FROM_DATABASE=Optiview USA + OUI:D85D4C* ID_OUI_FROM_DATABASE=TP-LINK TECHNOLOGIES CO.,LTD. @@ -141176,6 +141695,9 @@ OUI:D86162* OUI:D86194* ID_OUI_FROM_DATABASE=Objetivos y Sevicios de Valor Añadido +OUI:D862CA* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:D862DB* ID_OUI_FROM_DATABASE=Eno Inc. @@ -141335,6 +141857,9 @@ OUI:D88332* OUI:D88466* ID_OUI_FROM_DATABASE=Extreme Networks Headquarters +OUI:D8855E* + ID_OUI_FROM_DATABASE=zte corporation + OUI:D885AC* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -142619,6 +143144,9 @@ OUI:DC7306* OUI:DC7385* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:DC73FC* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:DC74A8* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -142973,6 +143501,9 @@ OUI:DCB7FC* OUI:DCB808* ID_OUI_FROM_DATABASE=Extreme Networks Headquarters +OUI:DCB87D* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:DCBB3D* ID_OUI_FROM_DATABASE=Extreme Networks Headquarters @@ -144122,6 +144653,9 @@ OUI:E0806B* OUI:E08177* ID_OUI_FROM_DATABASE=GreenBytes, Inc. +OUI:E0830D* + ID_OUI_FROM_DATABASE=NOTTA PTE. LTD. + OUI:E084F3* ID_OUI_FROM_DATABASE=High Grade Controls Corporation @@ -144557,6 +145091,9 @@ OUI:E0D31A* OUI:E0D362* ID_OUI_FROM_DATABASE=TP-Link Systems Inc. +OUI:E0D38E* + ID_OUI_FROM_DATABASE=Chipsea Technologies (Shenzhen) Crop. + OUI:E0D3B4* ID_OUI_FROM_DATABASE=Cisco Meraki @@ -144764,6 +145301,9 @@ OUI:E0FFF7* OUI:E40177* ID_OUI_FROM_DATABASE=SafeOwl, Inc. +OUI:E40274* + ID_OUI_FROM_DATABASE=FW Murphy Production Controls + OUI:E4029B* ID_OUI_FROM_DATABASE=Intel Corporate @@ -144788,6 +145328,9 @@ OUI:E408E7* OUI:E40A16* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:E40A75* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:E40CFD* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -146003,6 +146546,9 @@ OUI:E4FAED* OUI:E4FAFD* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:E4FB1E* + ID_OUI_FROM_DATABASE=Microsoft Corporation + OUI:E4FB5D* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -146237,6 +146783,9 @@ OUI:E82281* OUI:E822B8* ID_OUI_FROM_DATABASE=Shenzhen Skyworth Digital Technology CO., Ltd +OUI:E823FB* + ID_OUI_FROM_DATABASE=Redder + OUI:E82404* ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. @@ -146534,6 +147083,9 @@ OUI:E866C4* OUI:E86819* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:E868B1* + ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + OUI:E868E7* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -148082,6 +148634,9 @@ OUI:EC6E79* OUI:EC6F0B* ID_OUI_FROM_DATABASE=FADU, Inc. +OUI:EC6FF9* + ID_OUI_FROM_DATABASE=Pioseed Technology(Chengdu)Co.,Ltd. + OUI:EC7097* ID_OUI_FROM_DATABASE=Commscope @@ -148091,6 +148646,9 @@ OUI:EC71DB* OUI:EC725B* ID_OUI_FROM_DATABASE=zte corporation +OUI:EC72F7* + ID_OUI_FROM_DATABASE=DJI BAIWANG TECHNOLOGY CO LTD + OUI:EC7359* ID_OUI_FROM_DATABASE=Shenzhen Cloudsky Technologies Co., Ltd. @@ -148601,6 +149159,9 @@ OUI:ECB541* OUI:ECB550* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:ECB5AF* + ID_OUI_FROM_DATABASE=RayService a.s. + OUI:ECB5FA* ID_OUI_FROM_DATABASE=Philips Lighting BV @@ -150116,6 +150677,9 @@ OUI:F0CD31* OUI:F0CF4D* ID_OUI_FROM_DATABASE=BitRecords GmbH +OUI:F0D018* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:F0D08C* ID_OUI_FROM_DATABASE=TCT mobile ltd @@ -150620,6 +151184,9 @@ OUI:F41A9C* OUI:F41AB0* ID_OUI_FROM_DATABASE=Shenzhen Xingguodu Technology Co., Ltd. +OUI:F41AF7* + ID_OUI_FROM_DATABASE=zte corporation + OUI:F41BA1* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -152186,6 +152753,9 @@ OUI:F81A67* OUI:F81B04* ID_OUI_FROM_DATABASE=Zhong Shan City Richsound Electronic Industrial Ltd +OUI:F81B2E* + ID_OUI_FROM_DATABASE=G.Tech Technology Ltd. + OUI:F81CE5* ID_OUI_FROM_DATABASE=Telefonbau Behnke GmbH @@ -153428,6 +153998,9 @@ OUI:F8DFA8* OUI:F8DFE1* ID_OUI_FROM_DATABASE=MyLight Systems +OUI:F8E000* + ID_OUI_FROM_DATABASE=FUJI ELECTRIC CO., LTD. + OUI:F8E079* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company @@ -154700,6 +155273,9 @@ OUI:FCC737* OUI:FCC897* ID_OUI_FROM_DATABASE=zte corporation +OUI:FCCA10* + ID_OUI_FROM_DATABASE=MERCUSYS TECHNOLOGIES CO., LTD. + OUI:FCCAC4* ID_OUI_FROM_DATABASE=LifeHealth, LLC @@ -154898,6 +155474,9 @@ OUI:FCE26C* OUI:FCE33C* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:FCE421* + ID_OUI_FROM_DATABASE=zhejiang Dusun Electron Co.,Ltd + OUI:FCE4980* ID_OUI_FROM_DATABASE=NTCSOFT diff --git a/hwdb.d/20-acpi-vendor.hwdb b/hwdb.d/20-acpi-vendor.hwdb index 3828fea8ef9b9..c34293fef8fe6 100644 --- a/hwdb.d/20-acpi-vendor.hwdb +++ b/hwdb.d/20-acpi-vendor.hwdb @@ -252,6 +252,9 @@ acpi:KIOX*: acpi:KOMF*: ID_VENDOR_FROM_DATABASE=Kontron France +acpi:LECA*: + ID_VENDOR_FROM_DATABASE=Theo End (Shenzhen) Computing Technology Co., Ltd. + acpi:LNRO*: ID_VENDOR_FROM_DATABASE=Linaro, Ltd. @@ -417,6 +420,9 @@ acpi:TOSB*: acpi:TXNW*: ID_VENDOR_FROM_DATABASE=Texas Instruments +acpi:TYHX*: + ID_VENDOR_FROM_DATABASE=Nanjing Tianyihexin Electronics Ltd + acpi:UBLX*: ID_VENDOR_FROM_DATABASE=u-blox AG diff --git a/hwdb.d/20-acpi-vendor.hwdb.patch b/hwdb.d/20-acpi-vendor.hwdb.patch index 99c4c4d2bb724..8c6427858ffc2 100644 --- a/hwdb.d/20-acpi-vendor.hwdb.patch +++ b/hwdb.d/20-acpi-vendor.hwdb.patch @@ -1,5 +1,5 @@ ---- 20-acpi-vendor.hwdb.base 2026-02-24 18:35:45.671934479 +0000 -+++ 20-acpi-vendor.hwdb 2026-02-24 18:35:45.675934543 +0000 +--- 20-acpi-vendor.hwdb.base 2026-03-17 17:31:25.705001902 +0000 ++++ 20-acpi-vendor.hwdb 2026-03-17 17:31:25.713002098 +0000 @@ -3,6 +3,8 @@ # Data imported from: # https://uefi.org/uefi-pnp-export @@ -9,7 +9,7 @@ acpi:3GVR*: ID_VENDOR_FROM_DATABASE=VR Technology Holdings Limited -@@ -454,6 +456,9 @@ +@@ -460,6 +462,9 @@ acpi:AAA*: ID_VENDOR_FROM_DATABASE=Avolites Ltd @@ -19,7 +19,7 @@ acpi:AAE*: ID_VENDOR_FROM_DATABASE=Anatek Electronics Inc. -@@ -481,6 +486,9 @@ +@@ -487,6 +492,9 @@ acpi:ABO*: ID_VENDOR_FROM_DATABASE=D-Link Systems Inc @@ -29,7 +29,7 @@ acpi:ABS*: ID_VENDOR_FROM_DATABASE=Abaco Systems, Inc. -@@ -526,7 +534,7 @@ +@@ -532,7 +540,7 @@ acpi:ACO*: ID_VENDOR_FROM_DATABASE=Allion Computer Inc. @@ -38,7 +38,7 @@ ID_VENDOR_FROM_DATABASE=Aspen Tech Inc acpi:ACR*: -@@ -805,6 +813,9 @@ +@@ -811,6 +819,9 @@ acpi:AMT*: ID_VENDOR_FROM_DATABASE=AMT International Industry @@ -48,7 +48,7 @@ acpi:AMX*: ID_VENDOR_FROM_DATABASE=AMX LLC -@@ -853,6 +864,9 @@ +@@ -859,6 +870,9 @@ acpi:AOA*: ID_VENDOR_FROM_DATABASE=AOpen Inc. @@ -58,7 +58,7 @@ acpi:AOE*: ID_VENDOR_FROM_DATABASE=Advanced Optics Electronics, Inc. -@@ -862,6 +876,9 @@ +@@ -868,6 +882,9 @@ acpi:AOT*: ID_VENDOR_FROM_DATABASE=Alcatel @@ -68,7 +68,7 @@ acpi:APC*: ID_VENDOR_FROM_DATABASE=American Power Conversion -@@ -1043,7 +1060,7 @@ +@@ -1049,7 +1066,7 @@ ID_VENDOR_FROM_DATABASE=ALPS ALPINE CO., LTD. acpi:AUO*: @@ -77,7 +77,7 @@ acpi:AUR*: ID_VENDOR_FROM_DATABASE=Aureal Semiconductor -@@ -1123,6 +1140,9 @@ +@@ -1129,6 +1146,9 @@ acpi:AXE*: ID_VENDOR_FROM_DATABASE=Axell Corporation @@ -87,7 +87,7 @@ acpi:AXI*: ID_VENDOR_FROM_DATABASE=American Magnetics -@@ -1282,6 +1302,9 @@ +@@ -1288,6 +1308,9 @@ acpi:BML*: ID_VENDOR_FROM_DATABASE=BIOMED Lab @@ -97,7 +97,7 @@ acpi:BMS*: ID_VENDOR_FROM_DATABASE=BIOMEDISYS -@@ -1294,6 +1317,9 @@ +@@ -1300,6 +1323,9 @@ acpi:BNO*: ID_VENDOR_FROM_DATABASE=Bang & Olufsen @@ -107,7 +107,7 @@ acpi:BNS*: ID_VENDOR_FROM_DATABASE=Boulder Nonlinear Systems -@@ -1540,6 +1566,9 @@ +@@ -1546,6 +1572,9 @@ acpi:CHA*: ID_VENDOR_FROM_DATABASE=Chase Research PLC @@ -117,7 +117,7 @@ acpi:CHD*: ID_VENDOR_FROM_DATABASE=ChangHong Electric Co.,Ltd -@@ -1705,6 +1734,9 @@ +@@ -1711,6 +1740,9 @@ acpi:COD*: ID_VENDOR_FROM_DATABASE=CODAN Pty. Ltd. @@ -127,7 +127,7 @@ acpi:COI*: ID_VENDOR_FROM_DATABASE=Codec Inc. -@@ -2123,7 +2155,7 @@ +@@ -2129,7 +2161,7 @@ ID_VENDOR_FROM_DATABASE=Dragon Information Technology acpi:DJE*: @@ -136,7 +136,7 @@ acpi:DJP*: ID_VENDOR_FROM_DATABASE=Maygay Machines, Ltd -@@ -2476,6 +2508,9 @@ +@@ -2482,6 +2514,9 @@ acpi:EIN*: ID_VENDOR_FROM_DATABASE=Elegant Invention @@ -146,7 +146,7 @@ acpi:EKA*: ID_VENDOR_FROM_DATABASE=MagTek Inc. -@@ -2746,6 +2781,9 @@ +@@ -2752,6 +2787,9 @@ acpi:FCG*: ID_VENDOR_FROM_DATABASE=First International Computer Ltd @@ -156,7 +156,7 @@ acpi:FCS*: ID_VENDOR_FROM_DATABASE=Focus Enhancements, Inc. -@@ -3122,7 +3160,7 @@ +@@ -3128,7 +3166,7 @@ ID_VENDOR_FROM_DATABASE=General Standards Corporation acpi:GSM*: @@ -165,7 +165,7 @@ acpi:GSN*: ID_VENDOR_FROM_DATABASE=Grandstream Networks, Inc. -@@ -3232,6 +3270,9 @@ +@@ -3238,6 +3276,9 @@ acpi:HEC*: ID_VENDOR_FROM_DATABASE=Hisense Electric Co., Ltd. @@ -175,7 +175,7 @@ acpi:HEL*: ID_VENDOR_FROM_DATABASE=Hitachi Micro Systems Europe Ltd -@@ -3367,6 +3408,9 @@ +@@ -3373,6 +3414,9 @@ acpi:HSD*: ID_VENDOR_FROM_DATABASE=HannStar Display Corp @@ -185,7 +185,7 @@ acpi:HSM*: ID_VENDOR_FROM_DATABASE=AT&T Microelectronics -@@ -3493,6 +3537,9 @@ +@@ -3499,6 +3543,9 @@ acpi:ICI*: ID_VENDOR_FROM_DATABASE=Infotek Communication Inc @@ -195,7 +195,7 @@ acpi:ICM*: ID_VENDOR_FROM_DATABASE=Intracom SA -@@ -3589,6 +3636,9 @@ +@@ -3595,6 +3642,9 @@ acpi:IKE*: ID_VENDOR_FROM_DATABASE=Ikegami Tsushinki Co. Ltd. @@ -205,7 +205,7 @@ acpi:IKS*: ID_VENDOR_FROM_DATABASE=Ikos Systems Inc -@@ -3637,6 +3687,9 @@ +@@ -3643,6 +3693,9 @@ acpi:IMX*: ID_VENDOR_FROM_DATABASE=arpara Technology Co., Ltd. @@ -215,7 +215,7 @@ acpi:INA*: ID_VENDOR_FROM_DATABASE=Inventec Corporation -@@ -4165,6 +4218,9 @@ +@@ -4171,6 +4224,9 @@ acpi:LAN*: ID_VENDOR_FROM_DATABASE=Sodeman Lancom Inc @@ -225,7 +225,7 @@ acpi:LAS*: ID_VENDOR_FROM_DATABASE=LASAT Comm. A/S -@@ -4216,6 +4272,9 @@ +@@ -4222,6 +4278,9 @@ acpi:LED*: ID_VENDOR_FROM_DATABASE=Long Engineering Design Inc @@ -235,7 +235,7 @@ acpi:LEG*: ID_VENDOR_FROM_DATABASE=Legerity, Inc -@@ -4234,6 +4293,9 @@ +@@ -4240,6 +4299,9 @@ acpi:LGD*: ID_VENDOR_FROM_DATABASE=LG Display @@ -245,7 +245,7 @@ acpi:LGI*: ID_VENDOR_FROM_DATABASE=Logitech Inc -@@ -4300,6 +4362,9 @@ +@@ -4306,6 +4368,9 @@ acpi:LND*: ID_VENDOR_FROM_DATABASE=Land Computer Company Ltd @@ -255,7 +255,7 @@ acpi:LNK*: ID_VENDOR_FROM_DATABASE=Link Tech Inc -@@ -4334,7 +4399,7 @@ +@@ -4340,7 +4405,7 @@ ID_VENDOR_FROM_DATABASE=Design Technology acpi:LPL*: @@ -264,7 +264,7 @@ acpi:LSC*: ID_VENDOR_FROM_DATABASE=LifeSize Communications -@@ -4510,6 +4575,9 @@ +@@ -4516,6 +4581,9 @@ acpi:MCX*: ID_VENDOR_FROM_DATABASE=Millson Custom Solutions Inc. @@ -274,7 +274,7 @@ acpi:MDA*: ID_VENDOR_FROM_DATABASE=Media4 Inc -@@ -4756,6 +4824,9 @@ +@@ -4762,6 +4830,9 @@ acpi:MOM*: ID_VENDOR_FROM_DATABASE=Momentum Data Systems @@ -284,7 +284,7 @@ acpi:MOS*: ID_VENDOR_FROM_DATABASE=Moses Corporation -@@ -4996,6 +5067,9 @@ +@@ -5002,6 +5073,9 @@ acpi:NAL*: ID_VENDOR_FROM_DATABASE=Network Alchemy @@ -294,7 +294,7 @@ acpi:NAT*: ID_VENDOR_FROM_DATABASE=NaturalPoint Inc. -@@ -5536,6 +5610,9 @@ +@@ -5542,6 +5616,9 @@ acpi:PCX*: ID_VENDOR_FROM_DATABASE=PC Xperten @@ -304,7 +304,7 @@ acpi:PDM*: ID_VENDOR_FROM_DATABASE=Psion Dacom Plc. -@@ -5599,9 +5676,6 @@ +@@ -5605,9 +5682,6 @@ acpi:PHE*: ID_VENDOR_FROM_DATABASE=Philips Medical Systems Boeblingen GmbH @@ -314,7 +314,7 @@ acpi:PHL*: ID_VENDOR_FROM_DATABASE=Philips Consumer Electronics Company -@@ -5692,9 +5766,6 @@ +@@ -5698,9 +5772,6 @@ acpi:PNL*: ID_VENDOR_FROM_DATABASE=Panelview, Inc. @@ -324,7 +324,7 @@ acpi:PNR*: ID_VENDOR_FROM_DATABASE=Planar Systems, Inc. -@@ -6172,9 +6243,6 @@ +@@ -6178,9 +6249,6 @@ acpi:RTI*: ID_VENDOR_FROM_DATABASE=Rancho Tech Inc @@ -334,7 +334,7 @@ acpi:RTL*: ID_VENDOR_FROM_DATABASE=Realtek Semiconductor Company Ltd -@@ -6349,9 +6417,6 @@ +@@ -6355,9 +6423,6 @@ acpi:SEE*: ID_VENDOR_FROM_DATABASE=SeeColor Corporation @@ -344,7 +344,7 @@ acpi:SEI*: ID_VENDOR_FROM_DATABASE=Seitz & Associates Inc -@@ -6835,6 +6900,9 @@ +@@ -6841,6 +6906,9 @@ acpi:SVD*: ID_VENDOR_FROM_DATABASE=SVD Computer @@ -354,7 +354,7 @@ acpi:SVI*: ID_VENDOR_FROM_DATABASE=Sun Microsystems -@@ -6919,6 +6987,9 @@ +@@ -6925,6 +6993,9 @@ acpi:SZM*: ID_VENDOR_FROM_DATABASE=Shenzhen MTC Co., Ltd @@ -364,7 +364,7 @@ acpi:TAA*: ID_VENDOR_FROM_DATABASE=Tandberg -@@ -7009,6 +7080,9 @@ +@@ -7015,6 +7086,9 @@ acpi:TDG*: ID_VENDOR_FROM_DATABASE=Six15 Technologies @@ -374,7 +374,7 @@ acpi:TDM*: ID_VENDOR_FROM_DATABASE=Tandem Computer Europe Inc -@@ -7051,6 +7125,9 @@ +@@ -7057,6 +7131,9 @@ acpi:TEV*: ID_VENDOR_FROM_DATABASE=Televés, S.A. @@ -384,7 +384,7 @@ acpi:TEZ*: ID_VENDOR_FROM_DATABASE=Tech Source Inc. -@@ -7180,9 +7257,6 @@ +@@ -7186,9 +7263,6 @@ acpi:TNC*: ID_VENDOR_FROM_DATABASE=TNC Industrial Company Ltd @@ -394,7 +394,7 @@ acpi:TNM*: ID_VENDOR_FROM_DATABASE=TECNIMAGEN SA -@@ -7495,14 +7569,14 @@ +@@ -7501,14 +7575,14 @@ acpi:UNC*: ID_VENDOR_FROM_DATABASE=Unisys Corporation @@ -415,7 +415,7 @@ acpi:UNI*: ID_VENDOR_FROM_DATABASE=Uniform Industry Corp. -@@ -7537,6 +7611,9 @@ +@@ -7543,6 +7617,9 @@ acpi:USA*: ID_VENDOR_FROM_DATABASE=Utimaco Safeware AG @@ -425,7 +425,7 @@ acpi:USD*: ID_VENDOR_FROM_DATABASE=U.S. Digital Corporation -@@ -7798,9 +7875,6 @@ +@@ -7804,9 +7881,6 @@ acpi:WAL*: ID_VENDOR_FROM_DATABASE=Wave Access @@ -435,7 +435,7 @@ acpi:WAV*: ID_VENDOR_FROM_DATABASE=Wavephore -@@ -7928,7 +8002,7 @@ +@@ -7934,7 +8008,7 @@ ID_VENDOR_FROM_DATABASE=WyreStorm Technologies LLC acpi:WYS*: @@ -444,7 +444,7 @@ acpi:WYT*: ID_VENDOR_FROM_DATABASE=Wooyoung Image & Information Co.,Ltd. -@@ -7942,9 +8016,6 @@ +@@ -7948,9 +8022,6 @@ acpi:XDM*: ID_VENDOR_FROM_DATABASE=XDM Ltd. @@ -454,7 +454,7 @@ acpi:XES*: ID_VENDOR_FROM_DATABASE=Extreme Engineering Solutions, Inc. -@@ -7978,9 +8049,6 @@ +@@ -7984,9 +8055,6 @@ acpi:XNT*: ID_VENDOR_FROM_DATABASE=XN Technologies, Inc. @@ -464,7 +464,7 @@ acpi:XQU*: ID_VENDOR_FROM_DATABASE=SHANGHAI SVA-DAV ELECTRONICS CO., LTD -@@ -8047,6 +8115,9 @@ +@@ -8053,6 +8121,9 @@ acpi:ZBX*: ID_VENDOR_FROM_DATABASE=Zebax Technologies diff --git a/hwdb.d/20-pci-vendor-model.hwdb b/hwdb.d/20-pci-vendor-model.hwdb index e36c665ca2a39..dc48494c5197f 100644 --- a/hwdb.d/20-pci-vendor-model.hwdb +++ b/hwdb.d/20-pci-vendor-model.hwdb @@ -893,6 +893,12 @@ pci:v00000E11d0000F150* pci:v00000E55* ID_VENDOR_FROM_DATABASE=HaSoTec GmbH +pci:v00000E8D* + ID_VENDOR_FROM_DATABASE=MediaTek Inc. (Wrong ID) + +pci:v00000E8Dd00000801* + ID_MODEL_FROM_DATABASE=MT7621 PCIe Bridge + pci:v00000EAC* ID_VENDOR_FROM_DATABASE=SHF Communication Technologies AG @@ -12003,7 +12009,7 @@ pci:v00001002d0000745E* ID_MODEL_FROM_DATABASE=Navi 31 [Radeon Pro W7800] pci:v00001002d00007460* - ID_MODEL_FROM_DATABASE=Navi32 GL-XL [AMD Radeon PRO V710] + ID_MODEL_FROM_DATABASE=Navi 32 GL-XL [AMD Radeon PRO V710] pci:v00001002d00007461* ID_MODEL_FROM_DATABASE=Navi 32 [AMD Radeon PRO V710] @@ -12075,7 +12081,10 @@ pci:v00001002d00007550* ID_MODEL_FROM_DATABASE=Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] pci:v00001002d00007550sv0000148Csd00002435* - ID_MODEL_FROM_DATABASE=Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] (Reaper Radeon RX 9070 XT 16GB GDDR6 (RX9070XT 16G-A)) + ID_MODEL_FROM_DATABASE=Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] (Radeon RX 9070 XT 16GB) + +pci:v00001002d00007550sv00001849sd00005403* + ID_MODEL_FROM_DATABASE=Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] (Navi 48 XTX [Steel Legend Radeon RX 9070 XT]) pci:v00001002d00007550sv00001DA2sd0000E490* ID_MODEL_FROM_DATABASE=Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] (Navi 48 XTX [Sapphire Pulse Radeon RX 9070 XT]) @@ -23006,6 +23015,9 @@ pci:v00001077d00001022* pci:v00001077d00001080* ID_MODEL_FROM_DATABASE=ISP1080 SCSI Host Adapter +pci:v00001077d00001080sv00001077sd00000001* + ID_MODEL_FROM_DATABASE=ISP1080 SCSI Host Adapter (QLA1080) + pci:v00001077d00001216* ID_MODEL_FROM_DATABASE=ISP12160 Dual Channel Ultra3 SCSI Processor @@ -39993,7 +40005,7 @@ pci:v000010DEd00002C39* ID_MODEL_FROM_DATABASE=GB203GLM [RTX PRO 4000 Blackwell Generation Laptop GPU] pci:v000010DEd00002C3A* - ID_MODEL_FROM_DATABASE=GB203GL [RTX PRO 4500 Blackwell] + ID_MODEL_FROM_DATABASE=GB203GL [RTX PRO 4500 Blackwell Server Edition] pci:v000010DEd00002C58* ID_MODEL_FROM_DATABASE=GB203M / GN22-X11 [GeForce RTX 5090 Max-Q / Mobile] @@ -51846,7 +51858,7 @@ pci:v0000125Bd00009100sv0000A000sd00007000* ID_MODEL_FROM_DATABASE=AX99100 PCIe to Multi I/O Controller (Local Bus) pci:v0000125Bd00009100sv0000EA50sd00001C10* - ID_MODEL_FROM_DATABASE=AX99100 PCIe to Multi I/O Controller (RXi2-BP) + ID_MODEL_FROM_DATABASE=AX99100 PCIe to Multi I/O Controller (RXi2-BP Serial Port) pci:v0000125Bd00009105* ID_MODEL_FROM_DATABASE=AX99100 PCIe to I/O Bridge @@ -55265,6 +55277,45 @@ pci:v00001344d000051CBsv00001028sd000023A7* pci:v00001344d000051CBsv00001028sd000023A8* ID_MODEL_FROM_DATABASE=6550 ION NVMe SSD (MTFDLAL30T7THL-1BK1JABDA) +pci:v00001344d000051CC* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD + +pci:v00001344d000051CCsv00001028sd00002453* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLBQ122T8QHF-1BQ1JABDA) + +pci:v00001344d000051CCsv00001028sd00002483* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLBQ61T4QHF-1BQ1JABDA) + +pci:v00001344d000051CCsv00001028sd00002484* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLBQ30T7QHF-1BQ1JABDA) + +pci:v00001344d000051CCsv00001028sd00002485* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLBQ122T8QHF-1BQ1DFCDA) + +pci:v00001344d000051CCsv00001028sd00002486* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLBQ61T4QHF-1BQ1DFCDA) + +pci:v00001344d000051CCsv00001028sd00002487* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLBQ30T7QHF-1BQ1DFCDA) + +pci:v00001344d000051CCsv00001028sd00002489* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLAL122T8QHF-1BQ1JABDA) + +pci:v00001344d000051CCsv00001028sd0000248A* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLAL61T4QHF-1BQ1JABDA) + +pci:v00001344d000051CCsv00001028sd0000248B* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLAL30T7QHF-1BQ1JABDA) + +pci:v00001344d000051CCsv00001028sd0000248D* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLAL122T8QHF-1BQ1DFCDA) + +pci:v00001344d000051CCsv00001028sd0000248E* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLAL61T4QHF-1BQ1DFCDA) + +pci:v00001344d000051CCsv00001028sd0000248F* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLAL30T7QHF-1BQ1DFCDA) + pci:v00001344d000051CD* ID_MODEL_FROM_DATABASE=9650 PRO NVMe SSD @@ -61880,6 +61931,9 @@ pci:v000014C3d00007663* pci:v000014C3d00007902* ID_MODEL_FROM_DATABASE=MT7902 802.11ax PCIe Wireless Network Adapter [Filogic 310] +pci:v000014C3d00007906* + ID_MODEL_FROM_DATABASE=MT7916A/MT7916D normal link PCIe Wi-Fi 6(802.11ax) 160MHz 2x2 Wireless Network Adapter [Filogic 630] + pci:v000014C3d00007915* ID_MODEL_FROM_DATABASE=MT7915A/MT7915D normal link PCIe Wi-Fi 6(802.11ax) 80MHz 4x4/2x2 Wireless Network Adapter [Filogic 615] @@ -63806,6 +63860,12 @@ pci:v000014E4d00001760sv000014E4sd00009345* pci:v000014E4d00001760sv000014E4sd0000D125* ID_MODEL_FROM_DATABASE=BCM57608 25Gb/50Gb/100Gb/200Gb/400Gb Ethernet (BCM57608 2x200G PCIe Ethernet NIC) +pci:v000014E4d00001760sv0000193Dsd0000105B* + ID_MODEL_FROM_DATABASE=BCM57608 25Gb/50Gb/100Gb/200Gb/400Gb Ethernet (NIC-ETH2030F-LP-2P 2x200G PCIe Ethernet NIC) + +pci:v000014E4d00001760sv0000193Dsd0000105C* + ID_MODEL_FROM_DATABASE=BCM57608 25Gb/50Gb/100Gb/200Gb/400Gb Ethernet (NIC-ETH4030F-LP-1P 1x400G PCIe Ethernet NIC) + pci:v000014E4d00001800* ID_MODEL_FROM_DATABASE=BCM57502 NetXtreme-E Ethernet Partition @@ -64673,6 +64733,9 @@ pci:v000014E4d00005F72* pci:v000014E4d00005FA0* ID_MODEL_FROM_DATABASE=BRCM4377 Bluetooth Controller +pci:v000014E4d00006865* + ID_MODEL_FROM_DATABASE=BCM68650 [Aspen] XGSPON OLT + pci:v000014E4d00008411* ID_MODEL_FROM_DATABASE=BCM47xx PCIe Bridge @@ -74945,6 +75008,9 @@ pci:v00001A03d00002000sv000015D9sd00000832* pci:v00001A03d00002000sv000015D9sd0000086B* ID_MODEL_FROM_DATABASE=ASPEED Graphics Family (X10DRS (AST2400 BMC)) +pci:v00001A03d00002000sv000015D9sd0000086D* + ID_MODEL_FROM_DATABASE=ASPEED Graphics Family (X10SDV (AST2400 BMC)) + pci:v00001A03d00002000sv000015D9sd00001B95* ID_MODEL_FROM_DATABASE=ASPEED Graphics Family (H12SSL-i (AST2500 BMC)) @@ -78431,6 +78497,9 @@ pci:v00001CC1d000033F4* pci:v00001CC1d000033F8* ID_MODEL_FROM_DATABASE=IM2P33F8 series NVMe SSD (DRAM-less) +pci:v00001CC1d0000413D* + ID_MODEL_FROM_DATABASE=SM2P41D3Q NVMe SSD (DRAM-less) + pci:v00001CC1d000041C3* ID_MODEL_FROM_DATABASE=SM2P41C3 NVMe SSD (DRAM-less) @@ -83487,7 +83556,7 @@ pci:v00001E95* ID_VENDOR_FROM_DATABASE=Solid State Storage Technology Corporation pci:v00001E95d00001000* - ID_MODEL_FROM_DATABASE=XA1-311024 NVMe SSD M.2 + ID_MODEL_FROM_DATABASE=XA1 Series NVMe SSD M.2 (DRAM-less) pci:v00001E95d00001001* ID_MODEL_FROM_DATABASE=CA6-8D512 NVMe SSD M.2 @@ -83511,7 +83580,7 @@ pci:v00001E95d00001006* ID_MODEL_FROM_DATABASE=CA8 Series NVMe SSD M.2 pci:v00001E95d00001007* - ID_MODEL_FROM_DATABASE=CL4-8D512 NVMe SSD M.2 (DRAM-less) + ID_MODEL_FROM_DATABASE=CL4 Series NVMe SSD M.2 (DRAM-less) pci:v00001E95d00001008* ID_MODEL_FROM_DATABASE=CL5-8D512 NVMe SSD M.2 (DRAM-less) @@ -85061,6 +85130,9 @@ pci:v00001F0Fd00003504sv00001F0Fsd00000001* pci:v00001F0Fd00003504sv00001F0Fsd00000002* ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2025XT, 2x 10GbE, Base-T, PCIe 4.0 x8) +pci:v00001F0Fd00003504sv00001F0Fsd00000003* + ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2045XT, 4x 10GbE, Base-T, PCIe 4.0 x8) + pci:v00001F0Fd0000350A* ID_MODEL_FROM_DATABASE=M18305 Family Virtual Function @@ -85196,6 +85268,12 @@ pci:v00001F31d0000451B* pci:v00001F31d00004622* ID_MODEL_FROM_DATABASE=NEM-PAC NVMe SSD (DRAM-less) +pci:v00001F32* + ID_VENDOR_FROM_DATABASE=Wuhan YuXin Semiconductor Co., Ltd. + +pci:v00001F32d0000ED55* + ID_MODEL_FROM_DATABASE=U800G NVMe SSD + pci:v00001F3F* ID_VENDOR_FROM_DATABASE=3SNIC Ltd @@ -86453,6 +86531,18 @@ pci:v00001FF2d000010A1sv00001FF2sd00000C11* pci:v00001FF2d000010A2* ID_MODEL_FROM_DATABASE=NIC1160 Ethernet Controller Virtual Function Family +pci:v00001FF2d000010B1* + ID_MODEL_FROM_DATABASE=NIC 1260 Ethernet Controller Family + +pci:v00001FF2d000010B2* + ID_MODEL_FROM_DATABASE=NIC 1260 Ethernet Controller Virtual Function Family + +pci:v00001FF2d000010B3* + ID_MODEL_FROM_DATABASE=NIC 1260C Ethernet Controller Family + +pci:v00001FF2d000010B4* + ID_MODEL_FROM_DATABASE=NIC 1260C Ethernet Controller Virtual Function Family + pci:v00001FF2d000020A1* ID_MODEL_FROM_DATABASE=IOC2110 Storage Controller @@ -87248,6 +87338,18 @@ pci:v000020E1d00007103* pci:v000020E1d00007104* ID_MODEL_FROM_DATABASE=LS X710-P +pci:v000020E1d00007180* + ID_MODEL_FROM_DATABASE=LS X718 + +pci:v000020E1d00007211* + ID_MODEL_FROM_DATABASE=LS X721-E + +pci:v000020E1d00007223* + ID_MODEL_FROM_DATABASE=LS X722-M + +pci:v000020E1d00007224* + ID_MODEL_FROM_DATABASE=LS X722-P + pci:v000020E3* ID_VENDOR_FROM_DATABASE=Elix Systems SA @@ -87266,6 +87368,9 @@ pci:v000020F6d00000001* pci:v000020F9* ID_VENDOR_FROM_DATABASE=Shenzhen Silicon Dynamic Networks Co., Ltd. +pci:v00002105* + ID_VENDOR_FROM_DATABASE=Shanghai Timar Integrated Circuit Co., LTD + pci:v00002106* ID_VENDOR_FROM_DATABASE=ZCHL Technology Co., Ltd @@ -87275,6 +87380,9 @@ pci:v00002106d00000001* pci:v00002106d00000001sv00002106sd00000001* ID_MODEL_FROM_DATABASE=HL100 Accelerator Controller (HLC100 Accelerator Card) +pci:v00002108* + ID_VENDOR_FROM_DATABASE=HuiLink Technologies (Xiamen) Co., Ltd. + pci:v00002116* ID_VENDOR_FROM_DATABASE=ZyDAS Technology Corp. @@ -118043,6 +118151,60 @@ pci:v00008086d0000D157* pci:v00008086d0000D158* ID_MODEL_FROM_DATABASE=Core Processor Miscellaneous Registers +pci:v00008086d0000D323* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H SPI Controller + +pci:v00008086d0000D325* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO UART Controller #0 + +pci:v00008086d0000D326* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO UART Controller #1 + +pci:v00008086d0000D327* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO SPI Controller #0 + +pci:v00008086d0000D330* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO SPI Controller #1 + +pci:v00008086d0000D331* + ID_MODEL_FROM_DATABASE=Nova Lake-H Thunderbolt 5 USB Controller + +pci:v00008086d0000D333* + ID_MODEL_FROM_DATABASE=Nova Lake-H Thunderbolt 5 NHI #0 + +pci:v00008086d0000D347* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO SPI Controller #2 + +pci:v00008086d0000D34E* + ID_MODEL_FROM_DATABASE=Nova Lake-H Thunderbolt 5 PCI Express Root Port #0 + +pci:v00008086d0000D34F* + ID_MODEL_FROM_DATABASE=Nova Lake-H Thunderbolt 5 PCI Express Root Port #1 + +pci:v00008086d0000D350* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO I2C Controller #4 + +pci:v00008086d0000D351* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO I2C Controller #5 + +pci:v00008086d0000D352* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO UART Controller #2 + +pci:v00008086d0000D360* + ID_MODEL_FROM_DATABASE=Nova Lake-H Thunderbolt 5 PCI Express Root Port #2 + +pci:v00008086d0000D378* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO I2C Controller #0 + +pci:v00008086d0000D379* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO I2C Controller #1 + +pci:v00008086d0000D37A* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO I2C Controller #2 + +pci:v00008086d0000D37B* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO I2C Controller #3 + pci:v00008086d0000D431* ID_MODEL_FROM_DATABASE=Nova Lake-S Thunderbolt 5 USB Controller @@ -118076,6 +118238,33 @@ pci:v00008086d0000D744* pci:v00008086d0000D745* ID_MODEL_FROM_DATABASE=NVL-HX +pci:v00008086d0000D750* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D751* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D752* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D753* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D754* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D755* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D756* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D757* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D75F* + ID_MODEL_FROM_DATABASE=NVL-P + pci:v00008086d0000E202* ID_MODEL_FROM_DATABASE=Battlemage G21 [Intel Graphics] diff --git a/hwdb.d/40-imds.hwdb b/hwdb.d/40-imds.hwdb new file mode 100644 index 0000000000000..21f58aac15c5e --- /dev/null +++ b/hwdb.d/40-imds.hwdb @@ -0,0 +1,131 @@ +# This file is part of systemd + +# This provides various properties that declare if and how IMDS is available on +# the local system, i.e. we are running in a major cloud service that provides +# something resembling AWS' or Azure's Instance Metadata Service. +# +# General IMDS endpoint data: +# IMDS_VENDOR= → Indicates IMDS is available, and which vendor it is +# IMDS_TOKEN_URL= → The URL to request an API token from. If not set, no API token is requested. +# IMDS_REFRESH_HEADER_NAME= → The HTTP request header field (everything before the ":") that contains the refresh TTL (in seconds) when requesting a token. +# IMDS_DATA_URL= → The base URL to request actual IMDS data fields from +# IMDS_DATA_URL_SUFFIX= → Parameters to suffix the URLs with +# IMDS_TOKEN_HEADER_NAME= → The HTTP request header field (everything before the ":") used to pass the token +# IMDS_EXTRA_HEADER=, IMDS_EXTRA_HEADER2=, IMDS_EXTRA_HEADER3=, … +# → Additional HTTP headers to pass when requesting a data field (full header, including ":") +# IMDS_ADDRESS_IPV4= → IPv4 address of the IMDS server +# IMDS_ADDRESS_IPV6= → IPv6 address of the IMDS server +# +# Well-known IMDS keys: +# IMDS_KEY_HOSTNAME= → IMDS key for the hostname +# IMDS_KEY_REGION= → IMDS key for the region, if that concept applies +# IMDS_KEY_ZONE= → IMDS key for the zone, if that concept applies +# IMDS_KEY_IPV4_PUBLIC= → IMDS key for the primary public IPv4 address if there is any +# IMDS_KEY_IPV6_PUBLIC= → IMDS key for the primary public IPv6 address if there is any +# IMDS_KEY_SSH_KEY= → IMDS key for an SSH public key to install in the root account +# IMDS_KEY_USERDATA= → IMDS key for arbitrary userdata (if there's only one) +# IMDS_KEY_USERDATA_BASE= → IMDS key for arbitrary userdata (if there are multiple, this is the common prefix) +# IMDS_KEY_USERDATA_BASE64= → IMDS key for arbitrary userdata (if there's only one, but it is base64 encoded) + +# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html +dmi:bvnAmazonEC2:* + IMDS_VENDOR=amazon-ec2 + IMDS_TOKEN_URL=http://169.254.169.254/latest/api/token + IMDS_REFRESH_HEADER_NAME=X-aws-ec2-metadata-token-ttl-seconds + IMDS_DATA_URL=http://169.254.169.254/latest + IMDS_TOKEN_HEADER_NAME=X-aws-ec2-metadata-token + IMDS_ADDRESS_IPV4=169.254.169.254 + IMDS_ADDRESS_IPV6=fd00:ec2::254 + IMDS_KEY_HOSTNAME=/meta-data/hostname + IMDS_KEY_REGION=/meta-data/placement/region + IMDS_KEY_ZONE=/meta-data/placement/availability-zone + IMDS_KEY_IPV4_PUBLIC=/meta-data/public-ipv4 + IMDS_KEY_IPV6_PUBLIC=/meta-data/ipv6 + IMDS_KEY_SSH_KEY=/meta-data/public-keys/0/openssh-key + IMDS_KEY_USERDATA=/user-data + +# https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service#instance-metadata +dmi:*:cat7783-7084-3265-9085-8269-3286-77:* + IMDS_VENDOR=microsoft-azure + IMDS_DATA_URL=http://169.254.169.254/metadata + IMDS_DATA_URL_SUFFIX=?api-version=2025-04-07&format=text + IMDS_EXTRA_HEADER=Metadata: true + IMDS_ADDRESS_IPV4=169.254.169.254 + IMDS_KEY_HOSTNAME=/instance/compute/osProfile/computerName + IMDS_KEY_REGION=/instance/compute/location + IMDS_KEY_ZONE=/instance/compute/physicalZone + IMDS_KEY_IPV4_PUBLIC=/instance/network/interface/0/ipv4/ipAddress/0/publicIpAddress + IMDS_KEY_IPV6_PUBLIC=/instance/network/interface/0/ipv6/ipAddress/0/publicIpAddress + IMDS_KEY_SSH_KEY=/instance/compute/publicKeys/0/keyData + IMDS_KEY_USERDATA_BASE64=/instance/compute/userData + +# https://docs.cloud.google.com/compute/docs/metadata/predefined-metadata-keys +dmi:*:pnGoogleComputeEngine:* + IMDS_VENDOR=google-gcp + IMDS_DATA_URL=http://169.254.169.254/computeMetadata/v1 + IMDS_EXTRA_HEADER=Metadata-Flavor: Google + IMDS_ADDRESS_IPV4=169.254.169.254 + IMDS_KEY_HOSTNAME=/instance/hostname + IMDS_KEY_REGION=/instance/region + IMDS_KEY_ZONE=/instance/zone + IMDS_KEY_IPV4_PUBLIC=/instance/network-interfaces/0/access-configs/0/external-ip + IMDS_KEY_USERDATA_BASE=/instance/attributes + +# https://docs.hetzner.cloud/reference/cloud#description/server-metadata +dmi:bvnHetzner:* + IMDS_VENDOR=hetzner-cloud + IMDS_DATA_URL=http://169.254.169.254/hetzner/v1/metadata + IMDS_ADDRESS_IPV4=169.254.169.254 + IMDS_KEY_HOSTNAME=/hostname + IMDS_KEY_REGION=/region + IMDS_KEY_ZONE=/availability-zone + IMDS_KEY_IPV4_PUBLIC=/public-ipv4 + IMDS_KEY_SSH_KEY=/public-keys/0 + IMDS_KEY_USERDATA=/userdata + +# https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/gettingmetadata.htm#metadata-keys +dmi:*:catOracleCloud.com:* + IMDS_VENDOR=oracle-cloud-oci + IMDS_DATA_URL=http://169.254.169.254/opc/v2 + IMDS_ADDRESS_IPV4=169.254.169.254 + IMDS_ADDRESS_IPV6=fd00:c1::a9fe:a9fe + IMDS_EXTRA_HEADER=Authorization: Bearer Oracle + IMDS_KEY_HOSTNAME=/instance/hostname + IMDS_KEY_REGION=/instance/region + IMDS_KEY_ZONE=/instance/availabilityDomain + IMDS_KEY_SSH_KEY=/instance/metadata/ssh_authorized_keys + IMDS_KEY_USERDATA_BASE64=/metadata/user_data + +# https://www.scaleway.com/en/docs/instances/how-to/use-cloud-init/ +dmi:*:svnScaleway:* + IMDS_VENDOR=scaleway + IMDS_DATA_URL=http://169.254.42.42 + IMDS_ADDRESS_IPV4=169.254.42.42 + IMDS_ADDRESS_IPV6=fd00:42::42 + IMDS_KEY_USERDATA=/user_data + +# https://www.tencentcloud.com/document/product/213/4934?lang=en +dmi:*:svnTencentCloud:* + IMDS_VENDOR=tencent-cloud + IMDS_DATA_URL=http://metadata.tencentyun.com + IMDS_KEY_HOSTNAME=/meta-data/hostname + IMDS_KEY_REGION=/meta-data/placement/region + IMDS_KEY_ZONE=/meta-data/placement/zone + IMDS_KEY_IPV4_PUBLIC=/meta-data/public-ipv4 + IMDS_KEY_SSH_KEY=/meta-data/public-keys/0/openssh-key + IMDS_KEY_USERDATA=/user-data + +# https://help.aliyun.com/zh/ecs/user-guide/view-instance-metadata +dmi:*:svnAlibabaCloud:* + IMDS_VENDOR=alibaba-ecs + IMDS_TOKEN_URL=http://100.100.100.200/latest/api/token + IMDS_REFRESH_HEADER_NAME=X-aliyun-ecs-metadata-token-ttl-seconds + IMDS_DATA_URL=http://100.100.100.200/latest + IMDS_TOKEN_HEADER_NAME=X-aliyun-ecs-metadata-token + IMDS_ADDRESS_IPV4=100.100.100.200 + IMDS_KEY_HOSTNAME=/meta-data/hostname + IMDS_KEY_REGION=/meta-data/region-id + IMDS_KEY_ZONE=/meta-data/zone-id + IMDS_KEY_IPV4_PUBLIC=/meta-data/eipv4 + IMDS_KEY_SSH_KEY=/meta-data/public-keys/0/openssh-key + IMDS_KEY_USERDATA=/user-data diff --git a/hwdb.d/60-evdev.hwdb b/hwdb.d/60-evdev.hwdb index 92b43fe1b29d1..767b63f83571d 100644 --- a/hwdb.d/60-evdev.hwdb +++ b/hwdb.d/60-evdev.hwdb @@ -421,6 +421,17 @@ evdev:name:Atmel maXTouch Touch*:dmi:bvn*:bvr*:bd*:svnGOOGLE:pnSamus:* EVDEV_ABS_35=::10 EVDEV_ABS_36=::10 +######################################### +# Goodix +######################################### + +# Goodix GXTP5100 Forcepad (Lenovo ThinkBook 16 G7+ IAH, ThinkPad X9 15 Gen 1) +# The kernel hid-multitouch driver reports ABS_PRESSURE with min==max==0, +# an invalid range that causes libinput to reject the device entirely. +# Override ABS_PRESSURE (axis 0x18=24) to a valid range. +evdev:input:b0018v27C6p01E9* + EVDEV_ABS_18=0:255:0:0 + ######################################### # Granite Devices Simucube wheel bases ######################################### diff --git a/hwdb.d/60-input-id.hwdb b/hwdb.d/60-input-id.hwdb index d32bfedf59416..700e7ee264ba6 100644 --- a/hwdb.d/60-input-id.hwdb +++ b/hwdb.d/60-input-id.hwdb @@ -67,6 +67,11 @@ id-input:modalias:input:b0003v07C0p1125* ID_INPUT_MOUSE= ID_INPUT_JOYSTICK=1 +# Cooler Master ARGB GEN-2 controller +id-input:modalias:input:b0003v2516p01C9* + ID_INPUT=0 + ID_INPUT_MOUSE=0 + # GOLD WARRIOR SIM PhoenixRC 10411R id-input:modalias:input:b0003v1781p0898* ID_INPUT_ACCELEROMETER= @@ -118,3 +123,7 @@ id-input:modalias:input:b0003v26CEp01A2* # Saitek PLC Pro Flight Rudder Pedals id-input:modalias:input:b0003v06A3p0763* ID_INPUT_JOYSTICK=1 + +# PXN HB S handbrake +id-input:modalias:input:b0003v11FFpA701* + ID_INPUT_JOYSTICK=1 diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index 2af76e1ca2b7d..771b7dc43e477 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -242,6 +242,12 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnAcer*:pnNitro*AN*515-58:pvr* KEYBOARD_KEY_8a=micmute # Microphone mute button KEYBOARD_KEY_55=power +# Nitro AN517-54 +evdev:atkbd:dmi:bvn*:bvr*:bd*:svnAcer*:pnNitro*AN*517-54:pvr* + KEYBOARD_KEY_8a=micmute # Fn+F7; Microphone mute button + KEYBOARD_KEY_f5=prog1 # NitroSense button + KEYBOARD_KEY_55=power + # Nitro ANV15-51 evdev:atkbd:dmi:bvn*:bvr*:bd*:svnAcer*:pnNitro*ANV*15-51:pvr* KEYBOARD_KEY_66=micmute # Microphone mute button @@ -320,7 +326,6 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnAYANEO:pnKUN:pvr* # multi-scancode sequence. The specific preceding codes # depend on the model, but the final scancode is always the # same. -evdev:name:AT Translated Set 2 keyboard:dmi:*:svnAYA NEO:* evdev:name:AT Translated Set 2 keyboard:dmi:*:svnAYADEVICE:* evdev:name:AT Translated Set 2 keyboard:dmi:*:svnAYANEO:* KEYBOARD_KEY_66=f15 # LC (All models) @@ -713,7 +718,7 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHP*:pnHPENVYx360Convertible13*:* evdev:name:Intel HID events:dmi:bvn*:bvr*:bd*:svnHP*:pn*HP[sS][pP][eE][cC][tT][rR][eE]*x3602-in-1*:* # ENVY x360 evdev:name:Intel HID events:dmi:bvn*:bvr*:bd*:svnHP*:pnHPENVYx360Convertible*:* -evdev:name:Intel HID events:dmi:bvn*:bvr*:bd*:svnHP*:pnHPENVYx3602-in-1*:* +evdev:name:Intel HID events:dmi:bvn*:bvr*:bd*:svnHP*:pnHP[eE][nN][vV][yY]x3602-in-1*:* KEYBOARD_KEY_08=unknown # Prevents random airplane mode activation # HP Elite x2 1013 G3 @@ -1131,6 +1136,8 @@ evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO:*:pvrIdeaPadSlim5* evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO:pn81Q7*:pvrLenovoYogaS940:* # Lenovo ThinkBook 16G6IRL evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO:pn21KH*:pvrThinkBook16G6IRL:* +# Lenovo ThinkBook 14 2-in-1 G5 IAU +evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO:pn21SQ*:pvrThinkBook142-in-1G5IAU:* KEYBOARD_KEY_a0=!mute KEYBOARD_KEY_ae=!volumedown KEYBOARD_KEY_b0=!volumeup @@ -1207,6 +1214,11 @@ evdev:name:AT Raw Set 2 keyboard:dmi:*:svnLENOVO:pn83N0:* evdev:name:AT Raw Set 2 keyboard:dmi:*:svnLENOVO:pn83N1:* KEYBOARD_KEY_20=f16 # Power button long press +# Lenovo Thinkpad T14s Gen 6 (Snapdragon) +evdev:name:hid-over-i2c 04F3:000D Keyboard:dmi:bvn*:bvr*:bd*:svnLENOVO:pn21N1*:* +evdev:name:hid-over-i2c 04F3:000D Keyboard:dmi:bvn*:bvr*:bd*:svnLENOVO:pn21N2*:* + KEYBOARD_KEY_70072=unknown # Silence spurious F23 key-press report from Fn key + ########################################################### # LG ########################################################### @@ -1808,6 +1820,14 @@ evdev:input:b0003v258Ap001E* KEYBOARD_KEY_700a6=brightnessup KEYBOARD_KEY_70066=sleep +########################################################### +# Positron +########################################################### + +# Positron Proxima 15 (G1569) +evdev:atkbd:dmi:bvn*:bvr*:bd*:svn*Positron*:pnG1569*:* + KEYBOARD_KEY_6e=fn + ########################################################### # Purism ########################################################### @@ -2147,6 +2167,14 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnVIA:pnK8N800:* evdev:name:SIPODEV USB Composite Device:dmi:bvn*:bvr*:bd*:svnVIOS:pnLTH17:* KEYBOARD_KEY_70073=touchpad_toggle # Touchpad toggle +########################################################### +# Wareus +########################################################### + +# Wareus B15 (8AD5A) +evdev:atkbd:dmi:bvn*:bvr*:bd*:svnWareus*:pnB15*:* + KEYBOARD_KEY_55=fn + ########################################################### # WeiHeng ########################################################### @@ -2170,6 +2198,18 @@ evdev:name:FTSC1000:00 2808:509C Keyboard:dmi:*:svnXiaomiInc:pnMipad2:* KEYBOARD_KEY_70029=leftmeta # Esc -> LeftMeta (Windows key / Win8 tablets home) KEYBOARD_KEY_7002a=back # Backspace -> back +# Xiaomi Mi NoteBook Pro star key +evdev:atkbd:dmi:bvnTIMI*:bvr*:bd*:svnTIMI*:pnMiNoteBookPro*:* + KEYBOARD_KEY_72=macro + +########################################################### +# X+ +########################################################### + +# X+ piccolo series 81X (Intel N305, possibly more) +evdev:input:b0011v0001p0001eAB83* + KEYBOARD_KEY_9c=enter # KP_enter in the main area is wrong + ########################################################### # Zepto ########################################################### diff --git a/hwdb.d/60-sensor.hwdb b/hwdb.d/60-sensor.hwdb index e2c91594166aa..a3f243a660861 100644 --- a/hwdb.d/60-sensor.hwdb +++ b/hwdb.d/60-sensor.hwdb @@ -114,10 +114,10 @@ sensor:modalias:acpi:BMA250E:*:dmi:*:svnAcer:*:rnAigner:* # Iconia Tab 8W ######################################### sensor:modalias:acpi:MXC6655:*:dmi:*:svnAquarius:pnNS483:* # Cmp NS483 -sensor:modalias:acpi:MXC4005:*:dmi:*:svnAquarius:pnCmpNS483:* # Cmp NS483 v2 +sensor:modalias:acpi:MXC4005:*:dmi:*:svnAquarius:pnCmpNS483:* # Cmp NS483 v2 (MXC4005 accel) ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, 1, 0; 0, 0, 1 -sensor:modalias:acpi:MXC6655:*:dmi:*:svnAquarius:pnCmpNS483:* # Cmp NS483 v2 +sensor:modalias:acpi:MXC6655:*:dmi:*:svnAquarius:pnCmpNS483:* # Cmp NS483 v2 (MXC6655 accel) ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, -1, 0; 0, 0, 1 ######################################### @@ -221,8 +221,8 @@ sensor:modalias:acpi:KIOX010A:*:dmi:*:svnAMI:*:skuH8Y6:* # MaxBook Y14 # BNCF ######################################### -sensor:modalias:acpi:NSA2513:NSA2513*:dmi:*svnBNCF:pnNewBook11* # NewBook 11 2-in-1 - ACCEL_MOUNT_MATRIX=0, 1, 0; -1, 0, 0; 0, 0, -1 +sensor:modalias:acpi:NSA2513:*:dmi:*:svnBNCF:pnNewBook11:* # NewBook 11 2-in-1: Panel at -90 degrees. No ACPI in_mount_matrix. + ACCEL_MOUNT_MATRIX=0, 1, 0; -1, 0, 0; 0, 0, 1 ######################################### # BUSH @@ -515,14 +515,11 @@ sensor:modalias:acpi:KIOX000A:*:dmi:bvnAmericanMegatrendsInc.:bvr5.11:bd03/20/20 sensor:modalias:acpi:KIOX000A:*:dmi:bvnAmericanMegatrendsInc.:bvr5.11:bd05/25/2017:*:svnDefaultstring:pnDefaultstring:pvrDefaultstring:rvnAMICorporation:rnDefaultstring:rvrDefaultstring:cvnDefaultstring:ct3:cvrDefaultstring:* ACCEL_LOCATION=base -sensor:modalias:acpi:MXC6655:*:dmi:*:svnGPD:pnG1621-02:* # Pocket 3 -sensor:modalias:acpi:MXC6655:*:dmi:*:svnGPD:pnG1628-04:* # Pocket 4 - ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, 1, 0; 0, 0, 1 - -sensor:modalias:acpi:BMI0160:*:dmi:*:svnGPD:pnG1619*:* # WinMax2 +sensor:modalias:acpi:BMI0160:*:dmi:*:svnGPD:pnG1618-05:* # WIN 5 +sensor:modalias:acpi:BMI0160:*:dmi:*:svnGPD:pnG1619-04:* # Win Max 2 ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1 -sensor:modalias:acpi:MXC6655:*:dmi:*:svnGPD:pnG1688-*:* # MicroPC 2 +sensor:modalias:acpi:MXC6655:*:dmi:*:svnGPD:pnG1688-08:* # MicroPC 2 ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, -1 ######################################### diff --git a/hwdb.d/60-tpm2.hwdb b/hwdb.d/60-tpm2.hwdb new file mode 100644 index 0000000000000..935737d1d9e15 --- /dev/null +++ b/hwdb.d/60-tpm2.hwdb @@ -0,0 +1,14 @@ +# This file is part of systemd. +# +# Use "systemd-analyze identify-tpm2" to generate the modalias string for your +# hardware. Don't forget to prefix it with "tpm2:" for inclusion in a match here. +# +# Currently, the only relevant property to set here is TPM2_BROKEN_NVPCR=1, +# which should be set on TPMs where NvPCRs don't work. Specifically, because +# on some hardware the combination of TPMA_NV_ORDERLY + TPM2_NT_EXTEND cause +# NV_Extend() operations to time out. For details, see: +# https://github.com/systemd/systemd/issues/40485 + +# ST33TPHF2ESPI Firmware 73.4 +tpm2:*:mfSTM:*:fw73.4.*: + TPM2_BROKEN_NVPCR=1 diff --git a/hwdb.d/70-sound-card.hwdb b/hwdb.d/70-sound-card.hwdb index 4c53a861ecc34..03a0a5eefc28c 100644 --- a/hwdb.d/70-sound-card.hwdb +++ b/hwdb.d/70-sound-card.hwdb @@ -69,6 +69,11 @@ usb:v1038p227A* usb:v1038p22A1* usb:v1038p227E* usb:v1038p229E* +usb:v1038p22AD* +usb:v1038p22A9* +usb:v1038p22A4* +usb:v1038p22A5* +usb:v1038p22A7* usb:v1038p12E0* usb:v1038p12E5* SOUND_FORM_FACTOR=headset diff --git a/hwdb.d/acpi_id_registry.csv b/hwdb.d/acpi_id_registry.csv index d4daa95c28997..df362829c59e3 100644 --- a/hwdb.d/acpi_id_registry.csv +++ b/hwdb.d/acpi_id_registry.csv @@ -147,4 +147,6 @@ IDEMIA,IDEM,06/26/2018 "KAYA N CO., LTD.",KAYA,01/06/2026 Mesiontech,MITH,01/30/2026 "Nexthop Systems Inc.",NXHP,02/23/2026 -"Megapolis-Telecom Region LLC",MPTR,02/23/2026 \ No newline at end of file +"Megapolis-Telecom Region LLC",MPTR,02/23/2026 +"Nanjing Tianyihexin Electronics Ltd",TYHX,02/27/2026 +"Theo End (Shenzhen) Computing Technology Co., Ltd.",LECA,02/27/2026 \ No newline at end of file diff --git a/hwdb.d/ma-large.txt b/hwdb.d/ma-large.txt index 50bcdf32fb476..fbc3faa600c75 100644 --- a/hwdb.d/ma-large.txt +++ b/hwdb.d/ma-large.txt @@ -17681,12 +17681,6 @@ DC446D (base 16) Allwinner Technology Co., Ltd Dongguan 523808 CN -A8-D3-F7 (hex) Arcadyan Technology Corporation -A8D3F7 (base 16) Arcadyan Technology Corporation - No.8, Sec.2, Guangfu Rd., - Hsinchu City Hsinchu 30071 - TW - 00-0D-92 (hex) ARIMA Communications Corp. 000D92 (base 16) ARIMA Communications Corp. 16, lane 658, Ying-Tao Road @@ -22799,12 +22793,6 @@ D42751 (base 16) Infopia Co., Ltd Regensburg Bayern 93059 DE -F4-C6-D7 (hex) blackned GmbH -F4C6D7 (base 16) blackned GmbH - Am Hartholz 21 - Alling Bavaria 82239 - DE - 4C-CA-53 (hex) Skyera, Inc. 4CCA53 (base 16) Skyera, Inc. 1704 Automation Pkwy @@ -46985,6 +46973,24 @@ FC8FA4 (base 16) NXP Semiconductors Taiwan Ltd. Northridge CA 91329 US +B0-2B-64 (hex) Cisco Systems, Inc +B02B64 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +10-E6-76 (hex) Cisco Systems, Inc +10E676 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +FC-50-0C (hex) Sitehop Ltd +FC500C (base 16) Sitehop Ltd + 9 South Street + Sheffield South Yorkshire S2 5QX + GB + 00-26-89 (hex) General Dynamics Land Systems Inc. 002689 (base 16) General Dynamics Land Systems Inc. 38500 Mound Road @@ -46997,11 +47003,167 @@ FC8FA4 (base 16) NXP Semiconductors Taiwan Ltd. Cupertino CA 95014 US -FC-50-0C (hex) Sitehop Ltd -FC500C (base 16) Sitehop Ltd - 9 South Street - Sheffield South Yorkshire S2 5QX - GB +58-2A-BD (hex) Espressif Inc. +582ABD (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +C8-C8-73 (hex) CHIPSEN INC. +C8C873 (base 16) CHIPSEN INC. + 501, Gwangmyeong M-cluster 17, Deogan-ro 104beon-gil + Gwangmyeong-si Gyeonggi-do 14353 + KR + +AC-45-B0 (hex) Shenzhen Jidao Technology Co Ltd +AC45B0 (base 16) Shenzhen Jidao Technology Co Ltd + Room 605, Building 1, Saiba Technology Building, No. 16, North Keji Er Road + Shenzhen Guangdong 518057 + CN + +3C-B9-22 (hex) HUAWEI TECHNOLOGIES CO.,LTD +3CB922 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +70-70-D5 (hex) HUAWEI TECHNOLOGIES CO.,LTD +7070D5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +60-53-55 (hex) HUAWEI TECHNOLOGIES CO.,LTD +605355 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +D4-9F-F9 (hex) Earda Technologies co Ltd +D49FF9 (base 16) Earda Technologies co Ltd + Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District + Guangzhou Guangdong 511455 + CN + +10-C1-97 (hex) Xiaomi Communications Co Ltd +10C197 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +90-0E-84 (hex) eero inc. +900E84 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +F4-C6-D7 (hex) blackned GmbH +F4C6D7 (base 16) blackned GmbH + Zugspitzstrasse 1 + Bavaria Heimertingen 87751 + DE + +48-AA-BB (hex) Sagemcom Broadband SAS +48AABB (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + +E0-D3-8E (hex) Chipsea Technologies (Shenzhen) Crop. +E0D38E (base 16) Chipsea Technologies (Shenzhen) Crop. + Room 301, Building 1, Shenzhen Bay Innovation and Technology Center, Keyuan Avenue, High-tech Zone Community, Yuehai Subdistrict, Nanshan District, Shenzhen + Shenzhen 518000 + CN + +CC-C8-37 (hex) Quectel Wireless Solutions Co.,Ltd. +CCC837 (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + +D4-4A-85 (hex) Silicon Laboratories +D44A85 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +60-95-78 (hex) Samsung Electronics Co.,Ltd +609578 (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR + +78-0A-57 (hex) Shanghai Lightningsemi Technology Co.,Ltd. +780A57 (base 16) Shanghai Lightningsemi Technology Co.,Ltd. + Floor 5, Building 6,No. 9,Lane 1670,XiuYan road,ISPACE Kangqiao Intelligent Manufacturing Industrial Park,Pudong district + SHANGHAI SHANGHAI 201315 + CN + +D0-96-EA (hex) vivo Mobile Communication Co., Ltd. +D096EA (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + +5C-BA-75 (hex) Quectel Wireless Solutions Co., Ltd. +5CBA75 (base 16) Quectel Wireless Solutions Co., Ltd. + Building 5, Shanghai Business Park Phase III (Area B), No.1016 Tianlin Road, Minhang District + Shanghai 200233 + CN + +74-13-6A (hex) Motorola Mobility LLC, a Lenovo Company +74136A (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +38-A3-E0 (hex) 1Finity Inc +38A3E0 (base 16) 1Finity Inc + 4-1-1 Kamikodanaka, Nakahara-ku, Kawasaki-shi, Kanagawa211-8588, Japan + Kawasaki Kanagawa 211-8588 + JP + +98-3B-8A (hex) Sekisui Jushi CAP-AI System Co.,Ltd. +983B8A (base 16) Sekisui Jushi CAP-AI System Co.,Ltd. + Mandai Mita Building 2F,3-2-3 Mita,Minato-ku + Tokyo 108-0073 + JP + +08-D0-1E (hex) Juniper Networks +08D01E (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US + +A8-D3-F7 (hex) Arcadyan Corporation +A8D3F7 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd., + Hsinchu City Hsinchu 30071 + TW + +B8-38-65 (hex) Hewlett Packard Enterprise +B83865 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +68-9E-67 (hex) SHENZHEN FOCUSCOM TECHNOLOGIES CO., LTD +689E67 (base 16) SHENZHEN FOCUSCOM TECHNOLOGIES CO., LTD + Room 1205, Skyworth Digital Building, Songbai Road, Baoan District, Shenzhen, China + Shenzhen Guangdong 518108 + CN + +8C-53-87 (hex) Huzhou Luxshare Precision Industry Co.LTD +8C5387 (base 16) Huzhou Luxshare Precision Industry Co.LTD + 399 Shengxun Road, Zhili Town, Wuxing District,Huzhou City, Zhejiang Province + Huzhou Zhejiang 313008 + CN + +54-13-8F (hex) GEOIDE Crypto&Com +54138F (base 16) GEOIDE Crypto&Com + 18 Rue Alain Savary + BESANCON 25000 + FR 00-01-30 (hex) Extreme Networks Headquarters 000130 (base 16) Extreme Networks Headquarters @@ -64259,18 +64421,6 @@ C0FFD4 (base 16) NETGEAR San Jose CA 95134 US -00-26-4D (hex) Arcadyan Technology Corporation -00264D (base 16) Arcadyan Technology Corporation - 4F., No. 9 , Park Avenue II , - Hsinchu Taiwan 300 - TW - -84-9C-A6 (hex) Arcadyan Technology Corporation -849CA6 (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - 00-24-B2 (hex) NETGEAR 0024B2 (base 16) NETGEAR 350 East Plumeria Drive @@ -77855,12 +78005,6 @@ CC0080 (base 16) BETTINI SRL Oslo NO-0216 NO -00-13-AE (hex) Radiance Technologies, Inc. -0013AE (base 16) Radiance Technologies, Inc. - 350 Wynn Dr. - Huntsville Alabama 35805 - US - 00-13-44 (hex) Fargo Electronics Inc. 001344 (base 16) Fargo Electronics Inc. 6533 Flying Cloud Drive @@ -91868,12 +92012,6 @@ B02EBA (base 16) Earda Technologies co Ltd Sunnyvale CA 94089 US -EC-96-BF (hex) Kontron eSystems GmbH -EC96BF (base 16) Kontron eSystems GmbH - Bahnhofstraße 100 - Wendlingen 73240 - DE - 40-54-93 (hex) zte corporation 405493 (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China @@ -92237,12 +92375,6 @@ ACA704 (base 16) Espressif Inc. Piscataway NJ 08554 US -00-24-AE (hex) IDEMIA FRANCE SAS -0024AE (base 16) IDEMIA FRANCE SAS - 2 Place Samuel de Champlain - Courbevoie 92400 - FR - 00-1F-33 (hex) NETGEAR 001F33 (base 16) NETGEAR 3553 N. First Street @@ -93230,11 +93362,11 @@ D0C6BE (base 16) HPRO-Video Northridge CA 91329 US -F8-1E-49 (hex) Apple, Inc. -F81E49 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +84-AE-DE (hex) Xiaomi Communications Co Ltd +84AEDE (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN BC-74-EA (hex) Apple, Inc. BC74EA (base 16) Apple, Inc. @@ -93260,6 +93392,168 @@ BC74EA (base 16) Apple, Inc. Cupertino CA 95014 US +B8-0B-9A (hex) HUAWEI TECHNOLOGIES CO.,LTD +B80B9A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +F8-1E-49 (hex) Apple, Inc. +F81E49 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +C8-26-91 (hex) Arista Networks, Inc. +C82691 (base 16) Arista Networks, Inc. + 5453 Great America Parkway + Santa Clara 95054 + US + +00-13-AE (hex) Radiance Technologies, Inc. +0013AE (base 16) Radiance Technologies, Inc. + 310 Bob Heath Dr. + Huntsville 35806 + US + +68-C8-C0 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED +68C8C0 (base 16) GSD VIET NAM TECHNOLOGY COMPANY LIMITED + PART OF FACTORY 2, LOT C2.10, D1 STREET, DONG AN 2 INDUSTRIAL PARK, BINHDUONG WARD + HO CHI MINH CITY HO CHI MINH 820000 + VN + +68-EE-8F (hex) Espressif Inc. +68EE8F (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +FC-E4-21 (hex) zhejiang Dusun Electron Co.,Ltd +FCE421 (base 16) zhejiang Dusun Electron Co.,Ltd + NO.640 FengQing str., + DeQing ZheJiang 313000 + CN + +20-41-BC (hex) ANY Electronics Co., Ltd +2041BC (base 16) ANY Electronics Co., Ltd + 9, Sanbon-ro 86beon-gil + Gunpo-si Gyeonggi-do 15847 + KR + +CC-0C-9C (hex) CIG SHANGHAI CO LTD +CC0C9C (base 16) CIG SHANGHAI CO LTD + 5th Floor, Building 8 No 2388 Chenhang Road + SHANGHAI 201114 + CN + +D4-66-63 (hex) Shenzhen Detran Technology Co.,Ltd. +D46663 (base 16) Shenzhen Detran Technology Co.,Ltd. + 201, F5 Building, TCL International E City, Zhongshanyuan Rd. Nanshan District + Shenzhen Guangdong 518052 + CN + +A4-D7-D6 (hex) Shenzhen Linkoh Network Technology Co;Ltd +A4D7D6 (base 16) Shenzhen Linkoh Network Technology Co;Ltd + Yangguang Industrial Park, Hangcheng, Bao'an + Shenzhen Guangdong 518000 + CN + +5C-A9-31 (hex) 38436 +5CA931 (base 16) 38436 + Flat/RM 1202, 12/F, AT Tower + North Point Hong Kong 180 + HK + +00-24-AE (hex) IDEMIA PUBLIC SECURITY FRANCE +0024AE (base 16) IDEMIA PUBLIC SECURITY FRANCE + 2 Place Samuel de Champlain + Courbevoie 92400 + FR + +B4-B6-50 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +B4B650 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 + CN + +DC-73-FC (hex) Mellanox Technologies, Inc. +DC73FC (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +CC-30-89 (hex) Mellanox Technologies, Inc. +CC3089 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +84-9C-A6 (hex) Arcadyan Corporation +849CA6 (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +00-26-4D (hex) Arcadyan Corporation +00264D (base 16) Arcadyan Corporation + 4F., No. 9 , Park Avenue II , + Hsinchu Taiwan 300 + TW + +EC-B5-AF (hex) RayService a.s. +ECB5AF (base 16) RayService a.s. + Huštěnovská 2022 + Staré Město Czech Republic 686 03 + CZ + +20-B3-7F (hex) IEEE Registration Authority +20B37F (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +18-F5-8B (hex) GlobalReach Technology EMEA Ltd +18F58B (base 16) GlobalReach Technology EMEA Ltd + 51 Eastcheap + London EC3M 1DT + GB + +6C-E2-0C (hex) Hangzhou SDIC Microelectronics Inc. +6CE20C (base 16) Hangzhou SDIC Microelectronics Inc. + 5/F, Bldg 4 Tuosen Technology Park 351 Changhe Road, Binjiang District Hangzhou, Zhejiang, P.R.China + Hangzhou Zhejiang 310052 + CN + +44-90-BA (hex) CHINA DRAGON TECHNOLOGY LIMITED +4490BA (base 16) CHINA DRAGON TECHNOLOGY LIMITED + B4 Building,No.3 First industrial Zone,Nanpu Road,Lao Community,Xinqian Street,Baoan District,Shenzhen,City + ShenZhen 518100 + CN + +44-4A-4C (hex) vivo Mobile Communication Co., Ltd. +444A4C (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + +00-76-B6 (hex) Ford Motor Company +0076B6 (base 16) Ford Motor Company + 20300 Rotunda Drive + Dearborn MI 48124 + US + +EC-96-BF (hex) Kontron eSystems GmbH +EC96BF (base 16) Kontron eSystems GmbH + Bahnhofstr. 96 + Wendlingen 73240 + DE + +2C-B4-71 (hex) Tuya Smart Inc. +2CB471 (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US + 6C-87-20 (hex) New H3C Technologies Co., Ltd 6C8720 (base 16) New H3C Technologies Co., Ltd 466 Changhe Road, Binjiang District @@ -102572,12 +102866,6 @@ AC8D34 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -74-31-70 (hex) Arcadyan Technology Corporation -743170 (base 16) Arcadyan Technology Corporation - 4F. , No. 9 , Park Avenue II, - Hsinchu 300 - TW - 40-11-75 (hex) IEEE Registration Authority 401175 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -110738,24 +111026,6 @@ C4473F (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -5C-DC-96 (hex) Arcadyan Technology Corporation -5CDC96 (base 16) Arcadyan Technology Corporation - No.8, Sec.2, Guangfu Rd., - Hsinchu City 30071, 12345 - TW - -00-1A-2A (hex) Arcadyan Technology Corporation -001A2A (base 16) Arcadyan Technology Corporation - 4F., No. 9 , Park Avenue II, - Hsinchu 300 - TW - -88-25-2C (hex) Arcadyan Technology Corporation -88252C (base 16) Arcadyan Technology Corporation - 4F., NO.9, Park Avenue II , - Hsinchu 300 - TW - 00-E0-63 (hex) Cabletron Systems, Inc. 00E063 (base 16) Cabletron Systems, Inc. 35 INDUSTRIAL WAY @@ -110798,18 +111068,6 @@ D40129 (base 16) Broadcom Tel-aviv 12345 IL -1C-C6-3C (hex) Arcadyan Technology Corporation -1CC63C (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - -18-83-BF (hex) Arcadyan Technology Corporation -1883BF (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - 68-ED-43 (hex) BlackBerry RTS 68ED43 (base 16) BlackBerry RTS 451 Phillip Street @@ -127262,12 +127520,6 @@ B4B5AF (base 16) Minsung Electronics Kanagawa 215-0034 JP -00-07-8B (hex) Wegener Communications, Inc. -00078B (base 16) Wegener Communications, Inc. - 11350 Technology Circle - Duluth GA 30097 - US - 00-07-83 (hex) SynCom Network, Inc. 000783 (base 16) SynCom Network, Inc. 4F, No. 31, Hsintai Road, Chupei City, @@ -139664,6 +139916,162 @@ CCBE61 (base 16) Apple, Inc. Cupertino CA 95014 US +3C-7F-6E (hex) Xiaomi Communications Co Ltd +3C7F6E (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +90-64-9B (hex) Espressif Inc. +90649B (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +B8-57-D6 (hex) Cisco Systems, Inc +B857D6 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +00-07-8B (hex) Wegener Communications, Inc. +00078B (base 16) Wegener Communications, Inc. + 930 Interstate Ridge Drive, Ste. A, + Gainesville GA 30501 + US + +E4-02-74 (hex) FW Murphy Production Controls +E40274 (base 16) FW Murphy Production Controls + 4646 S Harvard Ave + Tulsa OK 74135 + US + +14-0A-02 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +140A02 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district + shenzhen guangdong 518000 + CN + +38-E1-58 (hex) Flaircomm Microelectronics,Inc. +38E158 (base 16) Flaircomm Microelectronics,Inc. + 7F,Guomai Building,Guomai Science and Technology Park,116 Jiangbin East Avenue,Mawei District,Fuzhou City + Fuzhou FUJIAN 350015 + CN + +58-21-9D (hex) Shanghai Timar Integrated Circuit Co., LTD +58219D (base 16) Shanghai Timar Integrated Circuit Co., LTD + Room 1208, No. 999 West Zhongshan Road, Changning District, Shanghai, China + shanghai shanghai 200030 + CN + +D8-85-5E (hex) zte corporation +D8855E (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +C8-85-41 (hex) Espressif Inc. +C88541 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +00-7F-1D (hex) Fantasia Trading LLC +007F1D (base 16) Fantasia Trading LLC + 5350 Ontario Mills Pkwy, Suite 100 + Ontario CA 91764 + US + +E4-0A-75 (hex) Silicon Laboratories +E40A75 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +40-79-55 (hex) Datacolor +407955 (base 16) Datacolor + 2 Shengpu Road, Suzhou Industrial Park, Export Processing Zone B, Suzhou, Jiangsu, P.R. China + Suzhou 215000 + CN + +EC-6F-F9 (hex) Pioseed Technology(Chengdu)Co.,Ltd. +EC6FF9 (base 16) Pioseed Technology(Chengdu)Co.,Ltd. + Unit 1,Building 2,177 Tianquan Road,Chengdu High-tech Zone(self-numbered) + Chengdu Sichuan 610097 + CN + +B0-7A-A4 (hex) Guangzhou Punp Electronics Manufacturing Co., Ltd. +B07AA4 (base 16) Guangzhou Punp Electronics Manufacturing Co., Ltd. + No. 20 Qianfeng South Road, Panyu District + Guangzhou Guangdong 511450 + CN + +18-83-BF (hex) Arcadyan Corporation +1883BF (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +1C-C6-3C (hex) Arcadyan Corporation +1CC63C (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +88-25-2C (hex) Arcadyan Corporation +88252C (base 16) Arcadyan Corporation + 4F., NO.9, Park Avenue II , + Hsinchu 300 + TW + +00-1A-2A (hex) Arcadyan Corporation +001A2A (base 16) Arcadyan Corporation + 4F., No. 9 , Park Avenue II, + Hsinchu 300 + TW + +5C-DC-96 (hex) Arcadyan Corporation +5CDC96 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd., + Hsinchu City 30071, 12345 + TW + +74-31-70 (hex) Arcadyan Corporation +743170 (base 16) Arcadyan Corporation + 4F. , No. 9 , Park Avenue II, + Hsinchu 300 + TW + +F8-E0-00 (hex) FUJI ELECTRIC CO., LTD. +F8E000 (base 16) FUJI ELECTRIC CO., LTD. + 1-27, Fuji-cho + Yokkaichi 510-0013 + JP + +50-61-88 (hex) PLANET Technology Corporation +506188 (base 16) PLANET Technology Corporation + 11F., No. 96, Minquan Road, Xindian Dist., + New Taipei City TAIWAN 23141 + TW + +D8-5C-11 (hex) Optiview USA +D85C11 (base 16) Optiview USA + 5211 Fairmont Street + Jacksonville FL 32207 + US + +18-DC-12 (hex) Silicon Laboratories +18DC12 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +D0-82-EB (hex) Tuya Smart Inc. +D082EB (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US + B0-0C-9D (hex) Quectel Wireless Solutions Co.,Ltd. B00C9D (base 16) Quectel Wireless Solutions Co.,Ltd. 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District @@ -156752,36 +157160,12 @@ D0D04B (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -4C-09-D4 (hex) Arcadyan Technology Corporation -4C09D4 (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - C8-FF-28 (hex) Liteon Technology Corporation C8FF28 (base 16) Liteon Technology Corporation 4F, 90, Chien 1 Road New Taipei City Taiwan 23585 TW -9C-80-DF (hex) Arcadyan Technology Corporation -9C80DF (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - -00-23-08 (hex) Arcadyan Technology Corporation -002308 (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - -88-03-55 (hex) Arcadyan Technology Corporation -880355 (base 16) Arcadyan Technology Corporation - 4F., No.9 , Park Avenue II - Hsinchu 300 - TW - 34-BB-1F (hex) BlackBerry RTS 34BB1F (base 16) BlackBerry RTS 451 Phillip Street @@ -169556,12 +169940,6 @@ A07332 (base 16) Cashmaster International Limited Pune Maharashtra 411 038 IN -00-17-1E (hex) Theo Benning GmbH & Co. KG -00171E (base 16) Theo Benning GmbH & Co. KG - Muensterstraße 135-137 - Bocholt NRW 46397 - DE - 00-17-12 (hex) ISCO International 001712 (base 16) ISCO International 1001 Cambridge Drive @@ -186743,6 +187121,30 @@ F8CB15 (base 16) Apple, Inc. REDMOND WA 98052 US +74-25-54 (hex) NVIDIA Corporation +742554 (base 16) NVIDIA Corporation + 2701 San Tomas Expressway + Santa Clara CA 95050 + US + +78-45-DC (hex) HUAWEI TECHNOLOGIES CO.,LTD +7845DC (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +10-49-0E (hex) HUAWEI TECHNOLOGIES CO.,LTD +10490E (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +50-37-CD (hex) Quectel Wireless Solutions Co., Ltd. +5037CD (base 16) Quectel Wireless Solutions Co., Ltd. + Building 5, Shanghai Business Park Phase III (Area B), No.1016 Tianlin Road, Minhang District + Shanghai 200233 + CN + D4-CE-40 (hex) Apple, Inc. D4CE40 (base 16) Apple, Inc. 1 Infinite Loop @@ -186755,6 +187157,180 @@ D4CE40 (base 16) Apple, Inc. Cupertino CA 95014 US +F0-D0-18 (hex) Hewlett Packard Enterprise +F0D018 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +00-17-1E (hex) Benning Elektrotechnik und Elektronik GmbH & Co. KG +00171E (base 16) Benning Elektrotechnik und Elektronik GmbH & Co. KG + Muensterstraße 135-137 + Bocholt NRW 46397 + DE + +44-78-31 (hex) HUAWEI TECHNOLOGIES CO.,LTD +447831 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +D8-62-CA (hex) Cisco Systems, Inc +D862CA (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +F8-1B-2E (hex) G.Tech Technology Ltd. +F81B2E (base 16) G.Tech Technology Ltd. + No.8,Jinyuan 1st Road,Tangjiawan Town, High-tech Zone + Zhuhai Guangdong 519085 + CN + +E4-FB-1E (hex) Microsoft Corporation +E4FB1E (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +54-E6-FD (hex) Sony Interactive Entertainment Inc. +54E6FD (base 16) Sony Interactive Entertainment Inc. + 1-7-1 Konan + Minato-ku Tokyo 108-0075 + JP + +6C-BF-2F (hex) eero inc. +6CBF2F (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +1C-E4-DD (hex) Technicolor (China) Technology Co., Ltd. +1CE4DD (base 16) Technicolor (China) Technology Co., Ltd. + No.A2181,2F,Zhongguancun Dongsheng Science and Technology Park, Jia No.18, Xueqing Rd., Haidian District + Beijing 100083 + CN + +F4-1A-F7 (hex) zte corporation +F41AF7 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +D4-50-39 (hex) Sagemcom Broadband SAS +D45039 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + +38-B1-4E (hex) IEEE Registration Authority +38B14E (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +9C-CE-22 (hex) PROMED Soest GmbH +9CCE22 (base 16) PROMED Soest GmbH + Wasserfuhr 5 + Soest 59494 + DE + +68-48-B4 (hex) AltoBeam Inc. +6848B4 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN + +EC-72-F7 (hex) DJI BAIWANG TECHNOLOGY CO LTD +EC72F7 (base 16) DJI BAIWANG TECHNOLOGY CO LTD + Room 101, Building 12, Baiwangxin Industrial Park, 1002 Songbai Road, Sunshine Community, Xili Street + Shenzhen Guangdong 518057 + CN + +38-6D-ED (hex) Juniper Networks +386DED (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US + +B8-D5-AD (hex) Nokia +B8D5AD (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +00-23-08 (hex) Arcadyan Corporation +002308 (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +9C-80-DF (hex) Arcadyan Corporation +9C80DF (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +30-08-4D (hex) Trumpf Hüttinger +30084D (base 16) Trumpf Hüttinger + Bötzingerstraße 80 + Freiburg 79111 + DE + +88-03-55 (hex) Arcadyan Corporation +880355 (base 16) Arcadyan Corporation + 4F., No.9 , Park Avenue II + Hsinchu 300 + TW + +4C-09-D4 (hex) Arcadyan Corporation +4C09D4 (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +94-54-A0 (hex) Fosilicon CO., Ltd +9454A0 (base 16) Fosilicon CO., Ltd + Room 502A, Building A, Phoenix Wisdom Valley, No. 50, Tiezi Road, Xixiang, Bao'an, Shenzhen + Shenzhen Guangdong 518102 + CN + +E0-83-0D (hex) NOTTA PTE. LTD. +E0830D (base 16) NOTTA PTE. LTD. + 9 RAFFLES PLACE #26-01 REPUBLIC PLAZA + SINGAPORE 048619 + SG + +2C-AB-EE (hex) EM Microelectronic +2CABEE (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +28-87-5F (hex) Annapurna labs +28875F (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +10-3A-5D (hex) Emerson +103A5D (base 16) Emerson + 6021 Innovation Blvd + Shakopee MN 55379 + US + +30-1C-22 (hex) Hewlett Packard Enterprise +301C22 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +FC-CA-10 (hex) MERCUSYS TECHNOLOGIES CO., LTD. +FCCA10 (base 16) MERCUSYS TECHNOLOGIES CO., LTD. + 3F,Zone B,Building R1,High-Tech Industrial Village,No.023 High-Tech South 4 Road,Nanshan,Shenzhen + Shenzhen Guangdong 518057 + CN + C8-5C-E2 (hex) IEEE Registration Authority C85CE2 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -193262,12 +193838,6 @@ F885F9 (base 16) Calix Inc. Dongguan Guangdong 523808 CN -04-C2-9B (hex) Aura Home, Inc. -04C29B (base 16) Aura Home, Inc. - 50 Eldridge Street, Suite 5D - New York NY 10002 - US - 1C-87-E3 (hex) TECNO MOBILE LIMITED 1C87E3 (base 16) TECNO MOBILE LIMITED ROOMS 05-15, 13A/F., SOUTH TOWER, WORLD FINANCE CENTRE, HARBOUR CITY, 17 CANTON ROAD, TSIM SHA TSUI, KOWLOON, HONG KONG @@ -204260,30 +204830,6 @@ BC620E (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -00-1D-19 (hex) Arcadyan Technology Corporation -001D19 (base 16) Arcadyan Technology Corporation - 4F., No. 9 , Park Avenue II, - Hsinchu 300 - TW - -00-12-BF (hex) Arcadyan Technology Corporation -0012BF (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II - Hsinchu 300 - TW - -50-7E-5D (hex) Arcadyan Technology Corporation -507E5D (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - -7C-4F-B5 (hex) Arcadyan Technology Corporation -7C4FB5 (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - 00-20-D4 (hex) Cabletron Systems, Inc. 0020D4 (base 16) Cabletron Systems, Inc. 35 INDUSTRIAL WAY @@ -231905,12 +232451,6 @@ AC1C26 (base 16) Hangzhou Ezviz Software Co.,Ltd. Piscataway NJ 08554 US -68-1D-4C (hex) Kontron eSystems GmbH -681D4C (base 16) Kontron eSystems GmbH - Bahnhofstraße 100 - Wendlingen 73240 - DE - B8-52-13 (hex) zte corporation B85213 (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China @@ -233207,24 +233747,6 @@ A41894 (base 16) IQSIGHT B.V. Eindhoven 5651 GW NL -E8-8F-8E (hex) Hoags Technologies India Private Limited -E88F8E (base 16) Hoags Technologies India Private Limited - M-138, 9TH A MAIN, JEEVAN BHEEMA NAGAR, - Bangalore KA 560075 - IN - -18-A0-84 (hex) Apple, Inc. -18A084 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -58-2A-93 (hex) Apple, Inc. -582A93 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - 64-C9-05 (hex) Apple, Inc. 64C905 (base 16) Apple, Inc. 1 Infinite Loop @@ -233249,14 +233771,212 @@ E88F8E (base 16) Hoags Technologies India Private Limited Cupertino CA 95014 US -00-0C-DE (hex) ABB AG. -000CDE (base 16) ABB AG. - Eppelheimer Straße 82 - Heidelberg Baden-Württemberg 69123 - DE +E8-8F-8E (hex) Hoags Technologies India Private Limited +E88F8E (base 16) Hoags Technologies India Private Limited + M-138, 9TH A MAIN, JEEVAN BHEEMA NAGAR, + Bangalore KA 560075 + IN + +18-A0-84 (hex) Apple, Inc. +18A084 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +58-2A-93 (hex) Apple, Inc. +582A93 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +50-63-82 (hex) HUAWEI TECHNOLOGIES CO.,LTD +506382 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN E8-FC-5F (hex) Ruckus Wireless E8FC5F (base 16) Ruckus Wireless 350 West Java Drive Sunnyvale CA 94089 US + +E8-68-B1 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +E868B1 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +B0-F0-79 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +B0F079 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +E8-23-FB (hex) Redder +E823FB (base 16) Redder + Via B. Ferracina, 2 + Camisano Vicentino VI 36043 + IT + +BC-68-C3 (hex) HUAWEI TECHNOLOGIES CO.,LTD +BC68C3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +A4-93-FE (hex) HUAWEI TECHNOLOGIES CO.,LTD +A493FE (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +9C-CC-01 (hex) Espressif Inc. +9CCC01 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +40-BA-09 (hex) Dell Inc. +40BA09 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US + +00-0C-DE (hex) ABB AG +000CDE (base 16) ABB AG + Eppelheimer Straße 82 + Heidelberg Baden-Württemberg 69123 + DE + +64-CA-80 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +64CA80 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 + CN + +00-1D-19 (hex) Arcadyan Corporation +001D19 (base 16) Arcadyan Corporation + 4F., No. 9 , Park Avenue II, + Hsinchu 300 + TW + +44-B1-76 (hex) Espressif Inc. +44B176 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +44-9A-52 (hex) zte corporation +449A52 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +C0-6B-C7 (hex) Gallagher Group Limited +C06BC7 (base 16) Gallagher Group Limited + 181 Kahikatea Drive + Hamilton Waikato 3204 + NZ + +BC-C4-36 (hex) Nokia +BCC436 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +DC-B8-7D (hex) Hewlett Packard Enterprise +DCB87D (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +24-7E-7F (hex) D-Fend Solutions A.D Ltd +247E7F (base 16) D-Fend Solutions A.D Ltd + 13 Zarhin st + Raanana Sharon 4366241 + IL + +7C-4F-B5 (hex) Arcadyan Corporation +7C4FB5 (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +50-7E-5D (hex) Arcadyan Corporation +507E5D (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +00-12-BF (hex) Arcadyan Corporation +0012BF (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II + Hsinchu 300 + TW + +98-2B-A6 (hex) Motorola Mobility LLC, a Lenovo Company +982BA6 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +5C-82-17 (hex) DSE srl +5C8217 (base 16) DSE srl + Via La Valle 51 + San Mauro Torinese TO 10099 + IT + +AC-E6-06 (hex) Honor Device Co., Ltd. +ACE606 (base 16) Honor Device Co., Ltd. + Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District + Shenzhen Guangdong 518040 + CN + +CC-FA-95 (hex) Honor Device Co., Ltd. +CCFA95 (base 16) Honor Device Co., Ltd. + Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District + Shenzhen Guangdong 518040 + CN + +64-AC-E0 (hex) Samsung Electronics Co.,Ltd +64ACE0 (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR + +CC-1E-AB (hex) LEDATEL sp. z o.o. i Wspólnicy sp.k +CC1EAB (base 16) LEDATEL sp. z o.o. i Wspólnicy sp.k + Terespolska 144 + Nowy Konik 9522033813 05-074 + PL + +7C-5C-8D (hex) EM Microelectronic +7C5C8D (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +68-1D-4C (hex) Kontron eSystems GmbH +681D4C (base 16) Kontron eSystems GmbH + Bahnhofstr. 96 + Wendlingen 73240 + DE + +04-C2-9B (hex) Aura Home, Inc. +04C29B (base 16) Aura Home, Inc. + 148 Lafayette Street, Floor 5 + New York NY 10013 + US + +B8-97-34 (hex) Silicon Laboratories +B89734 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +30-A7-71 (hex) Jiang Su Fulian Communication Technology Co.,Ltd +30A771 (base 16) Jiang Su Fulian Communication Technology Co.,Ltd + Yongan Community, the south of Lanling Road, Danyang Development Distinct + zhenjiang jiangsu 212300 + CN diff --git a/hwdb.d/ma-medium.txt b/hwdb.d/ma-medium.txt index 0f268976eed05..72b11878d4d5b 100644 --- a/hwdb.d/ma-medium.txt +++ b/hwdb.d/ma-medium.txt @@ -7109,6 +7109,18 @@ F0-12-04 (hex) MetaX Shanghai 200000 CN +2C-7A-F4 (hex) Kegao Intelligent Garden Technology(Guangdong) Co.,Ltd. +400000-4FFFFF (base 16) Kegao Intelligent Garden Technology(Guangdong) Co.,Ltd. + 8/F Building D,No.39, East Keji Avenue, Shishan Town Nanhai District + Foshan Guangdong 528225 + CN + +2C-7A-F4 (hex) Shenzhen Yitoa Digital Technology Co., Ltd. +300000-3FFFFF (base 16) Shenzhen Yitoa Digital Technology Co., Ltd. + 7th floor, Building 1, Jiancang Technology Park, Bao'an, Shenzhen, China + Shenzhen GuangDong 518000 + CN + 2C-7A-F4 (hex) ShangYu Auto Technology Co.,Ltd 600000-6FFFFF (base 16) ShangYu Auto Technology Co.,Ltd No. 69 Yuanda Road, Anting Town, Jiading District, Shanghai @@ -7121,18 +7133,6 @@ F0-12-04 (hex) MetaX Xi'An Shaanxi 710000 CN -2C-7A-F4 (hex) Shenzhen Yitoa Digital Technology Co., Ltd. -300000-3FFFFF (base 16) Shenzhen Yitoa Digital Technology Co., Ltd. - 7th floor, Building 1, Jiancang Technology Park, Bao'an, Shenzhen, China - Shenzhen GuangDong 518000 - CN - -2C-7A-F4 (hex) Kegao Intelligent Garden Technology(Guangdong) Co.,Ltd. -400000-4FFFFF (base 16) Kegao Intelligent Garden Technology(Guangdong) Co.,Ltd. - 8/F Building D,No.39, East Keji Avenue, Shishan Town Nanhai District - Foshan Guangdong 528225 - CN - FC-A2-DF (hex) TiGHT AV C00000-CFFFFF (base 16) TiGHT AV Uggledalsvägen 23 @@ -7265,12 +7265,6 @@ A00000-AFFFFF (base 16) ShenZhen Chainway Information Technology Co., Ltd. ShenZhen GuangDong 518102 CN -48-08-EB (hex) Silicon Dynamic Networks -D00000-DFFFFF (base 16) Silicon Dynamic Networks - Floor 2, Building 14, Section C, St. Moritz Garden, Yulong Road, Longhua New District - Shenzhen Guangdong 518131 - CN - E0-23-3B (hex) IOFAC 500000-5FFFFF (base 16) IOFAC Hyundaitera Tower 1628, 8, Ori-ro 651beon-gil @@ -7283,18 +7277,18 @@ E0-23-3B (hex) IOFAC Guangzhou Guangdong 510000 CN +48-08-EB (hex) Silicon Dynamic Networks +D00000-DFFFFF (base 16) Silicon Dynamic Networks + Floor 2, Building 14, Section C, St. Moritz Garden, Yulong Road, Longhua New District + Shenzhen Guangdong 518131 + CN + 50-FA-CB (hex) The Scotts Company C00000-CFFFFF (base 16) The Scotts Company 14111 Scottslawn Marysville OH 43041 US -50-FA-CB (hex) VeriFone Systems(China),Inc -800000-8FFFFF (base 16) VeriFone Systems(China),Inc - 1701 of Building D,Area III of Innovation Park,No.20 of Gaoxin Avenue,Minhou County - Fuzhou Fujian 350000 - CN - 9C-E4-50 (hex) XTX Markets Technologies Limited C00000-CFFFFF (base 16) XTX Markets Technologies Limited R7, 14-18 Handyside Street @@ -7307,14 +7301,26 @@ C00000-CFFFFF (base 16) XTX Markets Technologies Limited Shenzhen Guangdong 518100 CN -8C-AE-49 (hex) Shanghai Kanghai Information System CO.,LTD. -E00000-EFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +50-FA-CB (hex) VeriFone Systems(China),Inc +800000-8FFFFF (base 16) VeriFone Systems(China),Inc + 1701 of Building D,Area III of Innovation Park,No.20 of Gaoxin Avenue,Minhou County + Fuzhou Fujian 350000 + CN + +F4-97-9D (hex) Smart Access Designs, LLC +800000-8FFFFF (base 16) Smart Access Designs, LLC + 58 Mackenzie Willow Ter + Cheshire CT 06410 + US + +F8-2B-E6 (hex) Shanghai Kanghai Information System CO.,LTD. +B00000-BFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -6C-93-08 (hex) Shanghai Kanghai Information System CO.,LTD. -500000-5FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +04-58-5D (hex) Shanghai Kanghai Information System CO.,LTD. +E00000-EFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN @@ -7337,24 +7343,18 @@ E00000-EFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. ShenZhen GuangDong 518000 CN -F8-2B-E6 (hex) Shanghai Kanghai Information System CO.,LTD. -B00000-BFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +8C-AE-49 (hex) Shanghai Kanghai Information System CO.,LTD. +E00000-EFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -04-58-5D (hex) Shanghai Kanghai Information System CO.,LTD. -E00000-EFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +6C-93-08 (hex) Shanghai Kanghai Information System CO.,LTD. +500000-5FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -F4-97-9D (hex) Smart Access Designs, LLC -800000-8FFFFF (base 16) Smart Access Designs, LLC - 58 Mackenzie Willow Ter - Cheshire CT 06410 - US - 9C-E4-50 (hex) Strato Automation Inc. 400000-4FFFFF (base 16) Strato Automation Inc. 1550-B Rue de Coulomb @@ -7397,6 +7397,18 @@ C00000-CFFFFF (base 16) Rayve Innovation Corp Shawnee KS 66214 US +E8-F6-D7 (hex) CowManager +700000-7FFFFF (base 16) CowManager + Gerverscop 9 + Harmelen UT 3481LT + NL + +74-33-36 (hex) ACTECK TECHNOLOGY Co., Ltd +D00000-DFFFFF (base 16) ACTECK TECHNOLOGY Co., Ltd + 4F-1, No. 13, Sec.2 Beitou Rd., Beitou Dist. + Taipei City Taiwan 112028 + TW + E8-F6-D7 (hex) Emergent Solutions Inc. E00000-EFFFFF (base 16) Emergent Solutions Inc. 3600 Steeles Ave. E, Markham, ON @@ -7427,18 +7439,6 @@ A00000-AFFFFF (base 16) Shenzhen Jooan Technology Co., Ltd Shenzhen Guangdong 518122 CN -E8-F6-D7 (hex) CowManager -700000-7FFFFF (base 16) CowManager - Gerverscop 9 - Harmelen UT 3481LT - NL - -74-33-36 (hex) ACTECK TECHNOLOGY Co., Ltd -D00000-DFFFFF (base 16) ACTECK TECHNOLOGY Co., Ltd - 4F-1, No. 13, Sec.2 Beitou Rd., Beitou Dist. - Taipei City Taiwan 112028 - TW - 0C-BF-B4 (hex) Acula Technology Corp 000000-0FFFFF (base 16) Acula Technology Corp 11 Alley 21 Lane 20 Dashing Rd.,Luchu Dist Taoyuan City 33862, Taiwan @@ -7457,18 +7457,18 @@ D00000-DFFFFF (base 16) ACTECK TECHNOLOGY Co., Ltd Warren NH 03279 US -5C-5C-75 (hex) Bkeen International Corporated -400000-4FFFFF (base 16) Bkeen International Corporated - No.11 xingyung street chungli dist taoyuan city - Taoyuan 320 - TW - 5C-5C-75 (hex) Spectrum FiftyNine BV 900000-9FFFFF (base 16) Spectrum FiftyNine BV Middelweg 8a Molenhoek Limb 6584ah NL +5C-5C-75 (hex) Bkeen International Corporated +400000-4FFFFF (base 16) Bkeen International Corporated + No.11 xingyung street chungli dist taoyuan city + Taoyuan 320 + TW + 5C-5C-75 (hex) Deuta America E00000-EFFFFF (base 16) Deuta America 5547 A1A S, Suite 111 @@ -7505,12 +7505,6 @@ D00000-DFFFFF (base 16) Atlas Tech Inc Winnipeg Manitoba R3S0A1 CA -B4-AB-F3 (hex) Stravik Technologies LLC -800000-8FFFFF (base 16) Stravik Technologies LLC - 447 Sutter St Ste 405 - San Francisco CA 94108 - US - B4-AB-F3 (hex) Shenzhen Unicair Communication Technology Co., Ltd. 200000-2FFFFF (base 16) Shenzhen Unicair Communication Technology Co., Ltd. 8-9/F, Block1, Wutong Island, Shunchang Rd., Xixiang, Bao'an District, @@ -7523,17 +7517,11 @@ B00000-BFFFFF (base 16) Vissonic Electronics Limited Guangzhou 510000 CN -60-15-9F (hex) MaiaSpace -E00000-EFFFFF (base 16) MaiaSpace - BATIMENT A37 ARIANEGROUP, FORET DE VERNON - Vernon 27200 - FR - -60-15-9F (hex) SHENZHEN DAERXIN TECHNOLOGY CO.,LTD -600000-6FFFFF (base 16) SHENZHEN DAERXIN TECHNOLOGY CO.,LTD - 1st Building West 3 /f, Daerxun Technolgy Parks, No.29 pingxin North Road,Pinghu,Longgang - shenzhen guangdong 518111 - CN +B4-AB-F3 (hex) Stravik Technologies LLC +800000-8FFFFF (base 16) Stravik Technologies LLC + 447 Sutter St Ste 405 + San Francisco CA 94108 + US 80-77-86 (hex) Wintec Co., Ltd 200000-2FFFFF (base 16) Wintec Co., Ltd @@ -7547,10 +7535,16 @@ E00000-EFFFFF (base 16) MaiaSpace New York NY 10010 US -08-3C-03 (hex) Dongguan Development Security Intelligent Tech Co., Ltd -C00000-CFFFFF (base 16) Dongguan Development Security Intelligent Tech Co., Ltd - Room 202, No. 17, Sanzhong Xinglong Road, - Qingxi Town Dongguan City 523000 +60-15-9F (hex) MaiaSpace +E00000-EFFFFF (base 16) MaiaSpace + BATIMENT A37 ARIANEGROUP, FORET DE VERNON + Vernon 27200 + FR + +60-15-9F (hex) SHENZHEN DAERXIN TECHNOLOGY CO.,LTD +600000-6FFFFF (base 16) SHENZHEN DAERXIN TECHNOLOGY CO.,LTD + 1st Building West 3 /f, Daerxun Technolgy Parks, No.29 pingxin North Road,Pinghu,Longgang + shenzhen guangdong 518111 CN 80-77-86 (hex) Huizhou Jiemeisi Technology Co.,Ltd. @@ -7565,6 +7559,12 @@ A00000-AFFFFF (base 16) Huizhou Jiemeisi Technology Co.,Ltd. North Kuta Bali 80361 ID +08-3C-03 (hex) Dongguan Development Security Intelligent Tech Co., Ltd +C00000-CFFFFF (base 16) Dongguan Development Security Intelligent Tech Co., Ltd + Room 202, No. 17, Sanzhong Xinglong Road, + Qingxi Town Dongguan City 523000 + CN + 34-D7-F5 (hex) Hefei Panyuan Intelligent Technology Co., Ltd A00000-AFFFFF (base 16) Hefei Panyuan Intelligent Technology Co., Ltd No. 116 Shilian South Road, High-tech District, @@ -7601,6 +7601,12 @@ A00000-AFFFFF (base 16) Hefei Panyuan Intelligent Technology Co., Ltd Largo FL 33773 US +18-C3-E4 (hex) Trusted Technology Solutions, Inc. +400000-4FFFFF (base 16) Trusted Technology Solutions, Inc. + 346 River Street + Lemont IL 60439 + US + E8-6C-C7 (hex) ebblo Western Europe 000000-0FFFFF (base 16) ebblo Western Europe Rheinstrasse 36 @@ -7613,11 +7619,47 @@ E8-6C-C7 (hex) ebblo Western Europe Canoas RS 92120130 BR -18-C3-E4 (hex) Trusted Technology Solutions, Inc. -400000-4FFFFF (base 16) Trusted Technology Solutions, Inc. - 346 River Street - Lemont IL 60439 - US +C4-82-72 (hex) Mantenimiento y paileria +600000-6FFFFF (base 16) Mantenimiento y paileria + Avenida 16 de Septiembre 21 + Cuautitlán Estado de México 54831 + MX + +C4-82-72 (hex) Digisine Energytech Co., Ltd. +200000-2FFFFF (base 16) Digisine Energytech Co., Ltd. + 2F, No. 196, Sec. 2, Zhongxing Rd., Xindian Dist., + New Taipei City 231 + TW + +C4-82-72 (hex) Satways Ltd +900000-9FFFFF (base 16) Satways Ltd + 15 Megalou Konstantinou Street + Irakleio, Attica 14122 + GR + +38-B1-4E (hex) Guangzhou Sunrise Technology Co., Ltd. +C00000-CFFFFF (base 16) Guangzhou Sunrise Technology Co., Ltd. + 503, C2,No.182 Science Avenue,Science City,High-Tech Industrial Development Zone Guangzhou, Guangdong , CN. + Guangzhou Guangdong 510000 + CN + +38-B1-4E (hex) Universal Robots A/S +600000-6FFFFF (base 16) Universal Robots A/S + Energivej 51 + Odense S Odense 5260 + DK + +38-B1-4E (hex) DCL COMMUNICATION PTE. LTD. +900000-9FFFFF (base 16) DCL COMMUNICATION PTE. LTD. + 10 Ubi Crescent #04-18 + Singapore 408564 + SG + +20-B3-7F (hex) EGSTON Power Electronics GmbH +C00000-CFFFFF (base 16) EGSTON Power Electronics GmbH + Grafenbergerstraße 37 + Eggenburg 3730 + AT B8-4C-87 (hex) Shenzhen Link-all Technology Co., Ltd 300000-3FFFFF (base 16) Shenzhen Link-all Technology Co., Ltd @@ -14252,12 +14294,6 @@ C00000-CFFFFF (base 16) Faaftech Goiânia Goias 74093020 BR -B0-CC-CE (hex) MICROTEST -E00000-EFFFFF (base 16) MICROTEST - 14 F.-6, No. 79, Sec. 1, Xintai 5th Rd., Xizhi Dist. - New Taipei 221432 - TW - B0-CC-CE (hex) Shenzhen Xtooltech Intelligent Co.,Ltd. 400000-4FFFFF (base 16) Shenzhen Xtooltech Intelligent Co.,Ltd. 17&18/F, A2 Building, Creative City, Liuxian Avenue, Nanshan District, Shenzhen, China @@ -14276,6 +14312,12 @@ A00000-AFFFFF (base 16) Skylight San Francisco CA 94111 US +B0-CC-CE (hex) MICROTEST +E00000-EFFFFF (base 16) MICROTEST + 14 F.-6, No. 79, Sec. 1, Xintai 5th Rd., Xizhi Dist. + New Taipei 221432 + TW + 78-78-35 (hex) EHTech (Beijing)Co., Ltd. 200000-2FFFFF (base 16) EHTech (Beijing)Co., Ltd. 2nd Floor, Building 6 (Block D), No.5 Shengfang Road, Daxing District @@ -14318,12 +14360,6 @@ FC-E4-98 (hex) NTCSOFT Osaka-shi Osaka 530-0047 JP -00-6A-5E (hex) CYBERTEL BRIDGE -C00000-CFFFFF (base 16) CYBERTEL BRIDGE - 9th floor, Hansin IT Tower, 272, Digital-ro,Guro-gu - Seoul 08389 - KR - F4-97-9D (hex) Kaiware (Shenzhen) Technologies Co.,Ltd B00000-BFFFFF (base 16) Kaiware (Shenzhen) Technologies Co.,Ltd B716, Key Laboratory Platform Building, Shenzhen Virtual University Park, No. 1 Yuexing 2nd Road, High-tech Park Community, Yuehai Street, Nanshan District, Shenzhen, Guangdong 518057, China @@ -14336,12 +14372,24 @@ A00000-AFFFFF (base 16) Annapurna labs Mail box 15123 Haifa 3508409 IL +00-6A-5E (hex) CYBERTEL BRIDGE +C00000-CFFFFF (base 16) CYBERTEL BRIDGE + 9th floor, Hansin IT Tower, 272, Digital-ro,Guro-gu + Seoul 08389 + KR + 00-6A-5E (hex) Beijing Lingji Innovations technology Co,LTD. D00000-DFFFFF (base 16) Beijing Lingji Innovations technology Co,LTD. Room 106, 1st Floor, A-1 Building, Zhongguancun Dongsheng Science and Technology Park, No. 66 Xixiaokou Road, Haidian District, Beijing Beijing Beijing 100190 CN +F4-97-9D (hex) Teenage Engineering AB +E00000-EFFFFF (base 16) Teenage Engineering AB + Textilgatan 31 + Stockholm n/a 12030 + SE + F4-97-9D (hex) Warner Technology Corp 500000-5FFFFF (base 16) Warner Technology Corp 421 Shepherds Way @@ -14354,11 +14402,17 @@ A00000-AFFFFF (base 16) MARKT Co., Ltd Seongnam-si Gyeonggi-do 13558 KR -F4-97-9D (hex) Teenage Engineering AB -E00000-EFFFFF (base 16) Teenage Engineering AB - Textilgatan 31 - Stockholm n/a 12030 - SE +48-08-EB (hex) Hangzhou Jianan Technology Co.,Ltd +500000-5FFFFF (base 16) Hangzhou Jianan Technology Co.,Ltd + Room-4606, Building 3, Sijiqing Street, Shangcheng District + Hangzhou Zhejiang Province 310000 + CN + +48-08-EB (hex) ZHEJIANG AIKE INTELLIGENTTECHNOLOGY CO.LTD +C00000-CFFFFF (base 16) ZHEJIANG AIKE INTELLIGENTTECHNOLOGY CO.LTD + No. 18, Chunjiang Road, Ningwei Street, Xiaoshan District, Hangzhou City, Zhejiang + Hangzhou Zhejiang 311200 + CN E0-23-3B (hex) Ugreen Group Limited E00000-EFFFFF (base 16) Ugreen Group Limited @@ -14378,18 +14432,6 @@ D00000-DFFFFF (base 16) Magosys Systems LTD Rehovot 7638517 IL -48-08-EB (hex) Hangzhou Jianan Technology Co.,Ltd -500000-5FFFFF (base 16) Hangzhou Jianan Technology Co.,Ltd - Room-4606, Building 3, Sijiqing Street, Shangcheng District - Hangzhou Zhejiang Province 310000 - CN - -48-08-EB (hex) ZHEJIANG AIKE INTELLIGENTTECHNOLOGY CO.LTD -C00000-CFFFFF (base 16) ZHEJIANG AIKE INTELLIGENTTECHNOLOGY CO.LTD - No. 18, Chunjiang Road, Ningwei Street, Xiaoshan District, Hangzhou City, Zhejiang - Hangzhou Zhejiang 311200 - CN - 48-08-EB (hex) Eruminc Co.,Ltd. B00000-BFFFFF (base 16) Eruminc Co.,Ltd. 59-47, Seouldaehak-ro @@ -14402,10 +14444,10 @@ B00000-BFFFFF (base 16) Eruminc Co.,Ltd. Beijing 100027 CN -50-FA-CB (hex) Shenzhen Hill Technology Co., LTD. -500000-5FFFFF (base 16) Shenzhen Hill Technology Co., LTD. - Room 203, No.118 Xingye 1st Road, Rentian Community, Fuhai Street, Bao’an District - Shenzhen Guangdong 518103 +48-08-EB (hex) Shenzhen Electron Technology Co., LTD. +700000-7FFFFF (base 16) Shenzhen Electron Technology Co., LTD. + Building 2, Yingfeng Industrial Zone, Tantou Community, Songgang Street, Bao'an District + Shenzhen Guangzhou 51800 CN 50-FA-CB (hex) Huaihua Jiannan Electronic Technology Co.,Ltd. @@ -14414,16 +14456,10 @@ B00000-BFFFFF (base 16) Eruminc Co.,Ltd. Huaihua 418000 CN -48-08-EB (hex) Shenzhen Electron Technology Co., LTD. -700000-7FFFFF (base 16) Shenzhen Electron Technology Co., LTD. - Building 2, Yingfeng Industrial Zone, Tantou Community, Songgang Street, Bao'an District - Shenzhen Guangzhou 51800 - CN - -9C-E4-50 (hex) Shenzhen GW Technology Co., LTD -600000-6FFFFF (base 16) Shenzhen GW Technology Co., LTD - 2-1501C, Building T2, Haigu Technology Building, Luozu Community, Shiyan Street, Bao'an District, Shenzhen City, Guangdong Province - Shenzhen Guangdong 518101 +50-FA-CB (hex) Shenzhen Hill Technology Co., LTD. +500000-5FFFFF (base 16) Shenzhen Hill Technology Co., LTD. + Room 203, No.118 Xingye 1st Road, Rentian Community, Fuhai Street, Bao’an District + Shenzhen Guangdong 518103 CN 50-FA-CB (hex) Kyocera AVX Components (Timisoara) SRL @@ -14438,22 +14474,10 @@ E00000-EFFFFF (base 16) Advant sp. z o.o. Gdańsk pomorskie 80-398 PL -9C-E4-50 (hex) AIO SYSTEMS -100000-1FFFFF (base 16) AIO SYSTEMS - 158 Jan smuts drive,Walter streetRosebank quarter - Johannesburg 2196 - ZA - -24-A1-0D (hex) REVUPTECH PRIVATE LIMITED -C00000-CFFFFF (base 16) REVUPTECH PRIVATE LIMITED - G 232, G.B. NAGAR SECTOR 63 NOIDA - NOIDA UTTAR PRADESH 201301 - IN - -9C-E4-50 (hex) Shenzhen Lixun Technology Co., Ltd. -200000-2FFFFF (base 16) Shenzhen Lixun Technology Co., Ltd. - Room 209, Building D, Xinda Creative Park, Qianjin 2nd Road and Baotian 2nd Road, Bao'an District - Shenzhen 518102 +9C-E4-50 (hex) Shenzhen GW Technology Co., LTD +600000-6FFFFF (base 16) Shenzhen GW Technology Co., LTD + 2-1501C, Building T2, Haigu Technology Building, Luozu Community, Shiyan Street, Bao'an District, Shenzhen City, Guangdong Province + Shenzhen Guangdong 518101 CN F4-20-55 (hex) Shanghai Kanghai Information System CO.,LTD. @@ -14474,20 +14498,38 @@ B0-47-5E (hex) Shanghai Kanghai Information System CO.,LTD. ShenZhen GuangDong 518000 CN -FC-E4-98 (hex) Shanghai Kanghai Information System CO.,LTD. -300000-3FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +38-A8-CD (hex) Shanghai Kanghai Information System CO.,LTD. +700000-7FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN +9C-E4-50 (hex) AIO SYSTEMS +100000-1FFFFF (base 16) AIO SYSTEMS + 158 Jan smuts drive,Walter streetRosebank quarter + Johannesburg 2196 + ZA + +9C-E4-50 (hex) Shenzhen Lixun Technology Co., Ltd. +200000-2FFFFF (base 16) Shenzhen Lixun Technology Co., Ltd. + Room 209, Building D, Xinda Creative Park, Qianjin 2nd Road and Baotian 2nd Road, Bao'an District + Shenzhen 518102 + CN + +24-A1-0D (hex) REVUPTECH PRIVATE LIMITED +C00000-CFFFFF (base 16) REVUPTECH PRIVATE LIMITED + G 232, G.B. NAGAR SECTOR 63 NOIDA + NOIDA UTTAR PRADESH 201301 + IN + 9C-E4-50 (hex) Marelli AL&S ALIT-TZ 300000-3FFFFF (base 16) Marelli AL&S ALIT-TZ Via dell'industria 17 Tolmezzo Italy/Udine 33028 IT -38-A8-CD (hex) Shanghai Kanghai Information System CO.,LTD. -700000-7FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +FC-E4-98 (hex) Shanghai Kanghai Information System CO.,LTD. +300000-3FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN @@ -14534,11 +14576,11 @@ E8-F6-D7 (hex) Xiphos Systems Corp. Montreal QC H2W 1Y5 CA -74-33-36 (hex) Lyno Dynamics LLC -900000-9FFFFF (base 16) Lyno Dynamics LLC - 2232 dell range blvd - Cheyenne WY 82009 - US +E8-F6-D7 (hex) ZIEHL-ABEGG SE +300000-3FFFFF (base 16) ZIEHL-ABEGG SE + Heinz-Ziehl-Strasse 1 + Kuenzelsau 74653 + DE 74-33-36 (hex) Elide Interfaces Inc 400000-4FFFFF (base 16) Elide Interfaces Inc @@ -14546,18 +14588,18 @@ E8-F6-D7 (hex) Xiphos Systems Corp. Brooklyn NY 11211 US +74-33-36 (hex) Lyno Dynamics LLC +900000-9FFFFF (base 16) Lyno Dynamics LLC + 2232 dell range blvd + Cheyenne WY 82009 + US + E8-F6-D7 (hex) emicrotec 500000-5FFFFF (base 16) emicrotec Münzgrabenstraße 168/102 Graz Styria 8010 AT -E8-F6-D7 (hex) ZIEHL-ABEGG SE -300000-3FFFFF (base 16) ZIEHL-ABEGG SE - Heinz-Ziehl-Strasse 1 - Kuenzelsau 74653 - DE - 0C-BF-B4 (hex) Nanchang si colordisplay Technology Co.,Ltd D00000-DFFFFF (base 16) Nanchang si colordisplay Technology Co.,Ltd No.679,Aixihu North Road, High-tech Zone @@ -14576,18 +14618,18 @@ A00000-AFFFFF (base 16) IRTEYA LLC Hengelo Overijssel 7554PA NL -58-76-07 (hex) Hubcom Techno System LLP -D00000-DFFFFF (base 16) Hubcom Techno System LLP - Level 4 Ceejay House, Dr. Annie Besant Road, Worli, Mumbai City - mumbai Maharashtra 400018 - IN - 58-76-07 (hex) Shade Innovations 600000-6FFFFF (base 16) Shade Innovations 9715 B Burnet Rd. Suite 400 Austin TX 78758 US +58-76-07 (hex) Hubcom Techno System LLP +D00000-DFFFFF (base 16) Hubcom Techno System LLP + Level 4 Ceejay House, Dr. Annie Besant Road, Worli, Mumbai City + mumbai Maharashtra 400018 + IN + 5C-5C-75 (hex) hassoun Gulf Industrial Company 800000-8FFFFF (base 16) hassoun Gulf Industrial Company Building NO:9273Al Shihabi Street3rd Industrial CityJeddah- KSA @@ -14600,23 +14642,17 @@ D00000-DFFFFF (base 16) Hubcom Techno System LLP villepreux 78450 FR -C0-9B-F4 (hex) AUMOVIO Components Malaysia Sdn.Bhd. -E00000-EFFFFF (base 16) AUMOVIO Components Malaysia Sdn.Bhd. - 2455, MK.1, Tingkat Perusahaan 2A, - Prai Industrial Estate, Prai, Penang 13600 - MY - 58-AD-08 (hex) Suzhou Huichuan United Power System Co.,Ltd D00000-DFFFFF (base 16) Suzhou Huichuan United Power System Co.,Ltd Suzhou Huichuan United Power System Co., Ltd Suzhou Jiangsu 215000 CN -B4-AB-F3 (hex) Shenyang Tianwei Technology Co., Ltd -400000-4FFFFF (base 16) Shenyang Tianwei Technology Co., Ltd - 666-1 Nanjing South Street Hunnan District - Shenyang City Liaoning Province 110000 - CN +C0-9B-F4 (hex) AUMOVIO Components Malaysia Sdn.Bhd. +E00000-EFFFFF (base 16) AUMOVIO Components Malaysia Sdn.Bhd. + 2455, MK.1, Tingkat Perusahaan 2A, + Prai Industrial Estate, Prai, Penang 13600 + MY 58-AD-08 (hex) MileOne Technologies Inc E00000-EFFFFF (base 16) MileOne Technologies Inc @@ -14624,6 +14660,12 @@ E00000-EFFFFF (base 16) MileOne Technologies Inc Dover DE 19901 US +B4-AB-F3 (hex) Shenyang Tianwei Technology Co., Ltd +400000-4FFFFF (base 16) Shenyang Tianwei Technology Co., Ltd + 666-1 Nanjing South Street Hunnan District + Shenyang City Liaoning Province 110000 + CN + B4-AB-F3 (hex) RANG DONG LIGHT SOURCE & VACUUM FLASK J.S.C E00000-EFFFFF (base 16) RANG DONG LIGHT SOURCE & VACUUM FLASK J.S.C 87 - 89 Ha Dinh Str, Khuong Dinh Ward, Hanoi , Vietnam @@ -14636,18 +14678,18 @@ B4-AB-F3 (hex) Rugged Video LLC Cedarburg WI 53012 US -08-3C-03 (hex) Luxshare Precision Industry Co., Ltd. -A00000-AFFFFF (base 16) Luxshare Precision Industry Co., Ltd. - 2/F, Block A, Sanyang New Industrial Zone, West Haoyi, Shajing Street, - Baoan District Shenzhen 314117 - CN - 08-3C-03 (hex) Beijing New Matrix Information Technology CO., Ltd 400000-4FFFFF (base 16) Beijing New Matrix Information Technology CO., Ltd Building 2,No.1 Courtyard,Taibai West Road, Fengtai Districtr Beijing Beijing 100071 CN +08-3C-03 (hex) Luxshare Precision Industry Co., Ltd. +A00000-AFFFFF (base 16) Luxshare Precision Industry Co., Ltd. + 2/F, Block A, Sanyang New Industrial Zone, West Haoyi, Shajing Street, + Baoan District Shenzhen 314117 + CN + 34-D7-F5 (hex) ShenZhen C&D Electronics CO.Ltd. 100000-1FFFFF (base 16) ShenZhen C&D Electronics CO.Ltd. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District @@ -14672,18 +14714,6 @@ D00000-DFFFFF (base 16) JEL Corporation FUKUYAMA HIROSHIMA 7200831 JP -18-C3-E4 (hex) CASE Deutschland GmbH -D00000-DFFFFF (base 16) CASE Deutschland GmbH - Gruener Ring 126 - Braunschweig Niedersachsen 38108 - DE - -6C-47-80 (hex) HD HYUNDAI ENERGY SOLUTIONS CO., LTD. -100000-1FFFFF (base 16) HD HYUNDAI ENERGY SOLUTIONS CO., LTD. - 477, Bundangsuseo-ro - Bundang-gu, Seongnam-si Gyeonggi-do 13553 - KR - 6C-47-80 (hex) Prolink Surveillance Technology Co.Ltd A00000-AFFFFF (base 16) Prolink Surveillance Technology Co.Ltd 10F.-7, No.18, Ln. 609, Sec. 5, Chongxin Rd., Sanchong Dist. @@ -14696,6 +14726,18 @@ A00000-AFFFFF (base 16) Prolink Surveillance Technology Co.Ltd Warsaw 02-653 PL +18-C3-E4 (hex) CASE Deutschland GmbH +D00000-DFFFFF (base 16) CASE Deutschland GmbH + Gruener Ring 126 + Braunschweig Niedersachsen 38108 + DE + +6C-47-80 (hex) HD HYUNDAI ENERGY SOLUTIONS CO., LTD. +100000-1FFFFF (base 16) HD HYUNDAI ENERGY SOLUTIONS CO., LTD. + 477, Bundangsuseo-ro + Bundang-gu, Seongnam-si Gyeonggi-do 13553 + KR + 18-C3-E4 (hex) Clicks Technology Ltd 600000-6FFFFF (base 16) Clicks Technology Ltd 2 Stone Buildings @@ -14708,6 +14750,66 @@ E00000-EFFFFF (base 16) Alban Giacomo S.p.a. Romano d'Ezzelino Vicenza 36060 IT +C4-82-72 (hex) MyPlace Australia Pty Ltd +B00000-BFFFFF (base 16) MyPlace Australia Pty Ltd + 115 Vulcan Rd + Canning Vale WA 6155 + AU + +38-B1-4E (hex) Noitom Robotics Technology (Beijing) Co.,Ltd. +400000-4FFFFF (base 16) Noitom Robotics Technology (Beijing) Co.,Ltd. + 601–604, 6th Floor, Building 2, NO.16, Xiaoyuehe Dongpan Road, Haidian District + Beijing Beijing 100085 + CN + +38-B1-4E (hex) Shenzhen Mondo Technology Co,.Ltd +100000-1FFFFF (base 16) Shenzhen Mondo Technology Co,.Ltd + East Wing, 4th Floor, Building 1Gemdale Vison Software Technology ParkNanshan District, Shenzhen CityGuangdong Province, P.R. China + Shenzhen Guangdong 518057 + CN + +38-B1-4E (hex) Shenzhen Tongchuang Mechatronics co,LtD. +000000-0FFFFF (base 16) Shenzhen Tongchuang Mechatronics co,LtD. + 1026# Songbai Road, + Shenzhen Guangdong 51800 + CN + +38-B1-4E (hex) QRONOZ CO., Ltd. +300000-3FFFFF (base 16) QRONOZ CO., Ltd. + Rm. 2, 9 F., No. 6, Ln. 180, Sec. 6,Minquan E. Rd., Neihu Dist., + Taipei City 114708 + TW + +20-B3-7F (hex) Kitchen Armor +600000-6FFFFF (base 16) Kitchen Armor + 17500 Cartwright Rd + Irvine CA 92614 + US + +20-B3-7F (hex) OTP CO.,LTD. +400000-4FFFFF (base 16) OTP CO.,LTD. + 817 the SOHO, 2-7-4, AOMI, KOTO-KU,TOKYO JAPAN + TOKYO TOKYO 135-0064 + JP + +20-B3-7F (hex) Shenzhen HantangFengyun Technology Co.,Ltd +500000-5FFFFF (base 16) Shenzhen HantangFengyun Technology Co.,Ltd + 741, HUAMEIJU Building 2., 82 of Haiyu Community., Xin'an Street, Bao'an District, Shenzhen + Shenzhen 518000 + CN + +20-B3-7F (hex) Annapurna labs +900000-9FFFFF (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +20-B3-7F (hex) ShenZhen C&D Electronics CO.Ltd. +A00000-AFFFFF (base 16) ShenZhen C&D Electronics CO.Ltd. + 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District + ShenZhen GuangDong 518000 + CN + B8-4C-87 (hex) Altronix , Corp A00000-AFFFFF (base 16) Altronix , Corp 140 58th St. Bldg A, Ste 2N @@ -21773,24 +21875,30 @@ E00000-EFFFFF (base 16) Orion Power Systems, Inc. Yenimahalle ANKARA 06374 TR -04-58-5D (hex) Rexon Technology -C00000-CFFFFF (base 16) Rexon Technology - No. 261, Renhua Rd., Dali Dist. - Taichung City 412037 - TW - 04-58-5D (hex) Research Laboratory of Design Automation, Ltd. 100000-1FFFFF (base 16) Research Laboratory of Design Automation, Ltd. 8 Birzhevoy Spusk Taganrog 347900 RU +04-58-5D (hex) Rexon Technology +C00000-CFFFFF (base 16) Rexon Technology + No. 261, Renhua Rd., Dali Dist. + Taichung City 412037 + TW + D4-A0-FB (hex) Skyfri Corp 700000-7FFFFF (base 16) Skyfri Corp 800 North State Street Suite 403 City of Dover DE 19901 US +D4-A0-FB (hex) Snap-on Tools +C00000-CFFFFF (base 16) Snap-on Tools + 19220 San Jose Ave. + City of Industry CA 91748 + US + B0-CC-CE (hex) Watermark Systems (India) Private Limited B00000-BFFFFF (base 16) Watermark Systems (India) Private Limited 1010, Maker Chambers 5, Nariman Point, Mumbai @@ -21803,11 +21911,11 @@ B0-CC-CE (hex) Gateview Technologies JACKSONVILLE FL 32226 US -D4-A0-FB (hex) Snap-on Tools -C00000-CFFFFF (base 16) Snap-on Tools - 19220 San Jose Ave. - City of Industry CA 91748 - US +B0-CC-CE (hex) Xiaomi EV Technology Co., Ltd. +D00000-DFFFFF (base 16) Xiaomi EV Technology Co., Ltd. + Room 618, Floor 6, Building 5, Yard 15, Kechuang Tenth Street, Beijing Economic and Technological Development Zone, Beijing + Beijing Beijing 100176 + CN B0-CC-CE (hex) Beijing Viazijing Technology Co., Ltd. 500000-5FFFFF (base 16) Beijing Viazijing Technology Co., Ltd. @@ -21815,11 +21923,11 @@ B0-CC-CE (hex) Beijing Viazijing Technology Co., Ltd. Beijing 100085 CN -B0-CC-CE (hex) Xiaomi EV Technology Co., Ltd. -D00000-DFFFFF (base 16) Xiaomi EV Technology Co., Ltd. - Room 618, Floor 6, Building 5, Yard 15, Kechuang Tenth Street, Beijing Economic and Technological Development Zone, Beijing - Beijing Beijing 100176 - CN +F8-2B-E6 (hex) MaiaEdge, Inc. +C00000-CFFFFF (base 16) MaiaEdge, Inc. + 77 S. Bedford Street + Burlington MA 01803 + US 78-78-35 (hex) Ambient Life Inc. 700000-7FFFFF (base 16) Ambient Life Inc. @@ -21827,18 +21935,18 @@ D00000-DFFFFF (base 16) Xiaomi EV Technology Co., Ltd. Newton MA 02460 US -F8-2B-E6 (hex) MaiaEdge, Inc. -C00000-CFFFFF (base 16) MaiaEdge, Inc. - 77 S. Bedford Street - Burlington MA 01803 - US - FC-E4-98 (hex) Siretta Ltd C00000-CFFFFF (base 16) Siretta Ltd Basingstoke Rd, Spencers Wood, Reading Reading RG7 1PW GB +FC-E4-98 (hex) SM Instruments +500000-5FFFFF (base 16) SM Instruments + 20, Yuseong-daero 1184beon-gil + Daejeon Yuseong-gu 34109 + KR + 78-78-35 (hex) BLOOM VIEW LIMITED 900000-9FFFFF (base 16) BLOOM VIEW LIMITED Room 1502 ,Easey Commercial Building @@ -21857,18 +21965,18 @@ E00000-EFFFFF (base 16) Aplex Technology Inc. Zhonghe District New Taipei City 235 - TW -FC-E4-98 (hex) SM Instruments -500000-5FFFFF (base 16) SM Instruments - 20, Yuseong-daero 1184beon-gil - Daejeon Yuseong-gu 34109 - KR - 34-B5-F3 (hex) WEAD GmbH 300000-3FFFFF (base 16) WEAD GmbH Retzfeld 7 Sankt Georgen an der Gusen 4222 AT +34-B5-F3 (hex) Digicom +D00000-DFFFFF (base 16) Digicom + The 4th floor, Building No.4, Xiangshan South Road 105#, Shijingshan, Beijing, China + BEIJING 100144 + CN + 34-B5-F3 (hex) Shanghai Sigen New Energy Technology Co., Ltd 800000-8FFFFF (base 16) Shanghai Sigen New Energy Technology Co., Ltd Room 514 The 5th Floor, No.175 Weizhan Road China (Shanghai) Plilot Free Trade Zone @@ -21887,12 +21995,6 @@ C00000-CFFFFF (base 16) Shenzhen Mifasuolla Smart Co.,Ltd Shenzhen Guangdong 518052 CN -34-B5-F3 (hex) Digicom -D00000-DFFFFF (base 16) Digicom - The 4th floor, Building No.4, Xiangshan South Road 105#, Shijingshan, Beijing, China - BEIJING 100144 - CN - 34-B5-F3 (hex) Viettel Manufacturing Corporation One Member Limited Liability Company E00000-EFFFFF (base 16) Viettel Manufacturing Corporation One Member Limited Liability Company An Binh Hamlet, An Khanh Commune @@ -21905,18 +22007,18 @@ E00000-EFFFFF (base 16) Viettel Manufacturing Corporation One Member Limite Mughalsarai Uttar Pradesh(UP) 232101 IN -00-6A-5E (hex) Shenzhen yeahmoo Technology Co., Ltd. -300000-3FFFFF (base 16) Shenzhen yeahmoo Technology Co., Ltd. - Room 103, 1st Floor, Building 4, Yunli Intelligent Park, No. 3 Changfa Middle Road,Yangmei Community, Bantian Street, Longgang District, - Shenzhen Guangdong Province 518100 - CN - 04-58-5D (hex) HDS Otomasyon Güvenlik ve Yazılım Teknolojileri Sanayi Ticaret Limited Şirketi D00000-DFFFFF (base 16) HDS Otomasyon Güvenlik ve Yazılım Teknolojileri Sanayi Ticaret Limited Şirketi Merkez Mahallesi Sadabad Cad. Kapı No:20 İstanbul Kağıthane 34406 TR +00-6A-5E (hex) Shenzhen yeahmoo Technology Co., Ltd. +300000-3FFFFF (base 16) Shenzhen yeahmoo Technology Co., Ltd. + Room 103, 1st Floor, Building 4, Yunli Intelligent Park, No. 3 Changfa Middle Road,Yangmei Community, Bantian Street, Longgang District, + Shenzhen Guangdong Province 518100 + CN + F4-97-9D (hex) LUXSHARE - ICT(NGHE AN) LIMITED 700000-7FFFFF (base 16) LUXSHARE - ICT(NGHE AN) LIMITED No. 18, Road No. 03, VSIP Nghe An Industrial Park, Hung Nguyen Commune, Nghe An Province, Vietnam @@ -21941,11 +22043,23 @@ F4-97-9D (hex) MERRY ELECTRONICS CO., LTD. TAICHUNG 408213 TW -48-08-EB (hex) Quanta Storage Inc. -400000-4FFFFF (base 16) Quanta Storage Inc. - 3F. No.188, Wenhua 2nd Rd - Taoyuan City Guishan District 33383 - TW +48-08-EB (hex) Tianjin Jinmu Intelligent Control Technology Co., Ltd +100000-1FFFFF (base 16) Tianjin Jinmu Intelligent Control Technology Co., Ltd + Room 3271, Building 1, Collaborative Development Center, West Ring North Road, Beijing-Tianjin Zhongguancun Science Park, BaoDi District, Tianjin, China. + Tianjin 301800 + CN + +48-08-EB (hex) Yeacode (Xiamen) Inkjet Inc. +A00000-AFFFFF (base 16) Yeacode (Xiamen) Inkjet Inc. + 2F, No.8826, Lianting Road, Xiang An District + Xiamen FUJIAN 361100 + CN + +48-08-EB (hex) Quanta Storage Inc. +400000-4FFFFF (base 16) Quanta Storage Inc. + 3F. No.188, Wenhua 2nd Rd + Taoyuan City Guishan District 33383 + TW E0-23-3B (hex) Rehear Audiology Company LTD. 700000-7FFFFF (base 16) Rehear Audiology Company LTD. @@ -21959,42 +22073,12 @@ E0-23-3B (hex) PluralFusion INC Richmond VA 23223 US -48-08-EB (hex) Tianjin Jinmu Intelligent Control Technology Co., Ltd -100000-1FFFFF (base 16) Tianjin Jinmu Intelligent Control Technology Co., Ltd - Room 3271, Building 1, Collaborative Development Center, West Ring North Road, Beijing-Tianjin Zhongguancun Science Park, BaoDi District, Tianjin, China. - Tianjin 301800 - CN - 48-08-EB (hex) Technological Application And Production One Member Liability Company (Tecapro Company) 300000-3FFFFF (base 16) Technological Application And Production One Member Liability Company (Tecapro Company) 18A Cong Hoa Street, Ward Bay Hien Hochiminh Hochiminh 70000 VN -48-08-EB (hex) Yeacode (Xiamen) Inkjet Inc. -A00000-AFFFFF (base 16) Yeacode (Xiamen) Inkjet Inc. - 2F, No.8826, Lianting Road, Xiang An District - Xiamen FUJIAN 361100 - CN - -50-FA-CB (hex) Darveen Technology Limited -400000-4FFFFF (base 16) Darveen Technology Limited - 3/F, 2nd Building Hui Sheng Da industrial park, Qingcui road, Longhua district, Shenzhen - Shenzhen Guangdong 518000 - CN - -50-FA-CB (hex) Shenzhen Evertones Quantum Technology Co., Ltd. -100000-1FFFFF (base 16) Shenzhen Evertones Quantum Technology Co., Ltd. - Room 3907, Tower B, Digital Innovation Center, Beizhan Community, Minzhi Sub-district, Longhua District - Shenzhen Guangdong 518131 - CN - -C4-FF-BC (hex) Shanghai Kanghai Information System CO.,LTD. -600000-6FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. - 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District - ShenZhen GuangDong 518000 - CN - 64-62-66 (hex) Shanghai Kanghai Information System CO.,LTD. 700000-7FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District @@ -22025,16 +22109,10 @@ D00000-DFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. ShenZhen GuangDong 518000 CN -9C-E4-50 (hex) Shenzhen HQVT TECHNOLOGY Co.,LTD -900000-9FFFFF (base 16) Shenzhen HQVT TECHNOLOGY Co.,LTD - 3/F,Building 8 ,Taihua Wutong Island,Xixiang,Bao'an District - China Guang Dong 518000 - CN - -9C-E4-50 (hex) Shenzhen Coslight Technology Co.,Ltd. -B00000-BFFFFF (base 16) Shenzhen Coslight Technology Co.,Ltd. - Room 101, Factory Building, No. 2 Guangtian Road, Luotian Community, Yanluo Sub-district, Bao'an District, Shenzhen - Shenzhen 518000 +50-FA-CB (hex) Darveen Technology Limited +400000-4FFFFF (base 16) Darveen Technology Limited + 3/F, 2nd Building Hui Sheng Da industrial park, Qingcui road, Longhua district, Shenzhen + Shenzhen Guangdong 518000 CN 48-5E-0E (hex) Shanghai Kanghai Information System CO.,LTD. @@ -22049,16 +22127,16 @@ EC-74-CD (hex) Shanghai Kanghai Information System CO.,LTD. ShenZhen GuangDong 518000 CN -9C-E4-50 (hex) BEIJING TRANSTREAMS TECHNOLOGY CO.,LTD -700000-7FFFFF (base 16) BEIJING TRANSTREAMS TECHNOLOGY CO.,LTD - Room 1401, 14th Floor, Building 8, No. 8 Kegu 1st Street, Beijing Economic and Technological Development Zone - Beijing Beijing 100176 +C4-FF-BC (hex) Shanghai Kanghai Information System CO.,LTD. +600000-6FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. + 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District + ShenZhen GuangDong 518000 CN -9C-E4-50 (hex) Shenzhen Kuki Electric Co., Ltd. -E00000-EFFFFF (base 16) Shenzhen Kuki Electric Co., Ltd. - No.6 Shichang Road,Xinqiao Street Baoan District,Shenzhen,Guangdong - Shenzhen Guangdong 518125 +50-FA-CB (hex) Shenzhen Evertones Quantum Technology Co., Ltd. +100000-1FFFFF (base 16) Shenzhen Evertones Quantum Technology Co., Ltd. + Room 3907, Tower B, Digital Innovation Center, Beizhan Community, Minzhi Sub-district, Longhua District + Shenzhen Guangdong 518131 CN 24-A1-0D (hex) Shenzhen Star Instrument Co., Ltd. @@ -22076,12 +22154,42 @@ B00000-BFFFFF (base 16) Private Hangzhou Binjiang Distric 310000 CN +9C-E4-50 (hex) BEIJING TRANSTREAMS TECHNOLOGY CO.,LTD +700000-7FFFFF (base 16) BEIJING TRANSTREAMS TECHNOLOGY CO.,LTD + Room 1401, 14th Floor, Building 8, No. 8 Kegu 1st Street, Beijing Economic and Technological Development Zone + Beijing Beijing 100176 + CN + +9C-E4-50 (hex) Shenzhen Kuki Electric Co., Ltd. +E00000-EFFFFF (base 16) Shenzhen Kuki Electric Co., Ltd. + No.6 Shichang Road,Xinqiao Street Baoan District,Shenzhen,Guangdong + Shenzhen Guangdong 518125 + CN + +9C-E4-50 (hex) Shenzhen HQVT TECHNOLOGY Co.,LTD +900000-9FFFFF (base 16) Shenzhen HQVT TECHNOLOGY Co.,LTD + 3/F,Building 8 ,Taihua Wutong Island,Xixiang,Bao'an District + China Guang Dong 518000 + CN + +9C-E4-50 (hex) Shenzhen Coslight Technology Co.,Ltd. +B00000-BFFFFF (base 16) Shenzhen Coslight Technology Co.,Ltd. + Room 101, Factory Building, No. 2 Guangtian Road, Luotian Community, Yanluo Sub-district, Bao'an District, Shenzhen + Shenzhen 518000 + CN + 24-A1-0D (hex) Sony Honda Mobility Inc. 200000-2FFFFF (base 16) Sony Honda Mobility Inc. Midtown-East 9th floor, 9-7-2 Akasaka Minato-ku Tokyo 107-0052 JP +F0-40-AF (hex) Shenzhen BitFantasy Technology Co., Ltd +B00000-BFFFFF (base 16) Shenzhen BitFantasy Technology Co., Ltd + Room 507, Building C3, East Industrial Zone, No.12 Wenchang Street, Xiangshan Street Community, Shahe Subdistrict, Nanshan District, Shenzhen, Guangdong, China + Shenzhen 518000 + CN + F0-40-AF (hex) Actia Nordic AB 200000-2FFFFF (base 16) Actia Nordic AB Datalinjen 3A @@ -22112,29 +22220,35 @@ C00000-CFFFFF (base 16) clover Co,.Ltd Uiwang-si Gyeonggi-do 16072 KR -F0-40-AF (hex) Shenzhen BitFantasy Technology Co., Ltd -B00000-BFFFFF (base 16) Shenzhen BitFantasy Technology Co., Ltd - Room 507, Building C3, East Industrial Zone, No.12 Wenchang Street, Xiangshan Street Community, Shahe Subdistrict, Nanshan District, Shenzhen, Guangdong, China - Shenzhen 518000 - CN - E8-F6-D7 (hex) Massive Beams GmbH 600000-6FFFFF (base 16) Massive Beams GmbH Bismarckstr. 10-12 Berlin 10625 DE +74-33-36 (hex) Ramon Space +E00000-EFFFFF (base 16) Ramon Space + HAHARASH 4 + HOD HASHARON 4524078 + IL + 74-33-36 (hex) Moultrie Mobile 800000-8FFFFF (base 16) Moultrie Mobile 5724 Highway 280 East Birmingham AL 35242 US -74-33-36 (hex) Ramon Space -E00000-EFFFFF (base 16) Ramon Space - HAHARASH 4 - HOD HASHARON 4524078 - IL +74-33-36 (hex) Zoller + Fröhlich GmbH +200000-2FFFFF (base 16) Zoller + Fröhlich GmbH + Simoniusstraße 22 + Wangen im Allgäu 88239 + DE + +0C-BF-B4 (hex) ShenZhen XunDun Technology CO.LTD +300000-3FFFFF (base 16) ShenZhen XunDun Technology CO.LTD + 2/F, Building 11, Mabian Industrial Zone (Dezhi High-tech Park), Area 72, Xingdong Community, Xin 'an Street, Bao 'an District, Shenzhen + ShenZhen 518101 + CN 0C-BF-B4 (hex) Shenzhen EN Plus Tech Co.,Ltd. 400000-4FFFFF (base 16) Shenzhen EN Plus Tech Co.,Ltd. @@ -22154,18 +22268,6 @@ E00000-EFFFFF (base 16) Ramon Space Nuremberg Bayern 90441 DE -74-33-36 (hex) Zoller + Fröhlich GmbH -200000-2FFFFF (base 16) Zoller + Fröhlich GmbH - Simoniusstraße 22 - Wangen im Allgäu 88239 - DE - -0C-BF-B4 (hex) ShenZhen XunDun Technology CO.LTD -300000-3FFFFF (base 16) ShenZhen XunDun Technology CO.LTD - 2/F, Building 11, Mabian Industrial Zone (Dezhi High-tech Park), Area 72, Xingdong Community, Xin 'an Street, Bao 'an District, Shenzhen - ShenZhen 518101 - CN - 0C-BF-B4 (hex) ICWiser 500000-5FFFFF (base 16) ICWiser 5th Floor, Building 1, Liandong U Valley, No. 97, Xingguan Road, Industrial Park, Jiading District, @@ -22184,12 +22286,6 @@ C00000-CFFFFF (base 16) EV4 Limited Lokeren 9160 BE -58-76-07 (hex) HARDWARIO a.s. -000000-0FFFFF (base 16) HARDWARIO a.s. - U Jezu 525/4 - Liberec 460 01 - CZ - 20-2B-DA (hex) ZhuoYu Technology E00000-EFFFFF (base 16) ZhuoYu Technology No. 60 Xingke Road, Xili Street @@ -22226,12 +22322,30 @@ B00000-BFFFFF (base 16) Rwaytech Archamps Haute-Savoie 74160 FR +58-76-07 (hex) HARDWARIO a.s. +000000-0FFFFF (base 16) HARDWARIO a.s. + U Jezu 525/4 + Liberec 460 01 + CZ + +5C-5C-75 (hex) Shenzhen Jooan Technology Co., Ltd +D00000-DFFFFF (base 16) Shenzhen Jooan Technology Co., Ltd + Area B, Floor 101-2, Floor 3, Floor 5 and Floor 6 of area B, Building No. 8, Guixiang Community Plaza Road, Guanlan Street, Longhua District, Shenzhen. + Shenzhen Guangdong 518000 + CN + 5C-5C-75 (hex) YingKeSong Pen Industry Technology R&D Center Shenzhen Co Ltd 500000-5FFFFF (base 16) YingKeSong Pen Industry Technology R&D Center Shenzhen Co Ltd R1605 Building 1 HengDaDuHui Plaza, BanTian LongGang Shenzhen Guangdong 518129 CN +5C-5C-75 (hex) UOI TECHNOLOGY CORPORATION +700000-7FFFFF (base 16) UOI TECHNOLOGY CORPORATION + 1F., No. 50, Ln. 148, Lide St. + Zhonghe Dist. New Taipei City 23512 + TW + 5C-5C-75 (hex) Elite Link 000000-0FFFFF (base 16) Elite Link No.1226, F12,Chouyin Building-A, Rd188, Shangcheng Avenue, Financial Services District @@ -22244,24 +22358,6 @@ A00000-AFFFFF (base 16) InoxSmart by Unison Hardware sacramento CA 95829 US -5C-5C-75 (hex) Shenzhen Jooan Technology Co., Ltd -D00000-DFFFFF (base 16) Shenzhen Jooan Technology Co., Ltd - Area B, Floor 101-2, Floor 3, Floor 5 and Floor 6 of area B, Building No. 8, Guixiang Community Plaza Road, Guanlan Street, Longhua District, Shenzhen. - Shenzhen Guangdong 518000 - CN - -5C-5C-75 (hex) UOI TECHNOLOGY CORPORATION -700000-7FFFFF (base 16) UOI TECHNOLOGY CORPORATION - 1F., No. 50, Ln. 148, Lide St. - Zhonghe Dist. New Taipei City 23512 - TW - -58-AD-08 (hex) Fujian Ruihe Technology Co., Ltd. -300000-3FFFFF (base 16) Fujian Ruihe Technology Co., Ltd. - No. 3, Building 6, 3rd Floor, Military Housing Administration Building, 528 Xihong Road, Gulou District, Fuzhou City - Fuzhou Fujian 350000 - CN - 58-AD-08 (hex) Jiangsu Delianda Intelligent Technology Co., Ltd. 700000-7FFFFF (base 16) Jiangsu Delianda Intelligent Technology Co., Ltd. Intelligent Terminal Innovation Park (D), High-tech Zone @@ -22274,6 +22370,18 @@ B00000-BFFFFF (base 16) AUMOVIO Brazil Industry Ltda. Guarulhos São Paulo 07042-020 BR +58-AD-08 (hex) Fujian Ruihe Technology Co., Ltd. +300000-3FFFFF (base 16) Fujian Ruihe Technology Co., Ltd. + No. 3, Building 6, 3rd Floor, Military Housing Administration Building, 528 Xihong Road, Gulou District, Fuzhou City + Fuzhou Fujian 350000 + CN + +B4-AB-F3 (hex) VOOMAX TECHNOLOGY LIMITED +000000-0FFFFF (base 16) VOOMAX TECHNOLOGY LIMITED + 11/F CENTRALTOWER 28 QUEEN'S RD CENTRAL,CENTRAL + HONG KONG 999077 + HK + B4-AB-F3 (hex) SNSYS 600000-6FFFFF (base 16) SNSYS 7f 830, Dongtansunhwan-daero @@ -22286,18 +22394,6 @@ B4-AB-F3 (hex) NubiCubi Karlsruhe Baden Wurttenberg 76187 DE -B4-AB-F3 (hex) VOOMAX TECHNOLOGY LIMITED -000000-0FFFFF (base 16) VOOMAX TECHNOLOGY LIMITED - 11/F CENTRALTOWER 28 QUEEN'S RD CENTRAL,CENTRAL - HONG KONG 999077 - HK - -60-15-9F (hex) HUIZHOU BOHUI CONNECTION TECHNOLOGY CO., LTD -800000-8FFFFF (base 16) HUIZHOU BOHUI CONNECTION TECHNOLOGY CO., LTD - 中国广东省惠州市惠城区东江高新区东兴大道111号泓淋工业园 - 惠州 广东省 516000 - CN - 60-15-9F (hex) QingDao Hiincom Electronics Co., Ltd A00000-AFFFFF (base 16) QingDao Hiincom Electronics Co., Ltd No.1 JinYe Road @@ -22316,11 +22412,11 @@ C00000-CFFFFF (base 16) Terrestar Solutions Inc Montreal Quebec H2V 2L1 CA -80-77-86 (hex) Realtime Biometrics India (P) limited -400000-4FFFFF (base 16) Realtime Biometrics India (P) limited - C-83, Ganesh Nagar, Pandav Nagar, New Delhi, Delhi, 110092 - Delhi Delhi 110092 - IN +60-15-9F (hex) HUIZHOU BOHUI CONNECTION TECHNOLOGY CO., LTD +800000-8FFFFF (base 16) HUIZHOU BOHUI CONNECTION TECHNOLOGY CO., LTD + 中国广东省惠州市惠城区东江高新区东兴大道111号泓淋工业园 + 惠州 广东省 516000 + CN 60-15-9F (hex) Shenzhen NTS Technology Co.,Ltd 900000-9FFFFF (base 16) Shenzhen NTS Technology Co.,Ltd @@ -22328,15 +22424,33 @@ C00000-CFFFFF (base 16) Terrestar Solutions Inc Shenzhen Guangdong 518100 CN +80-77-86 (hex) Realtime Biometrics India (P) limited +400000-4FFFFF (base 16) Realtime Biometrics India (P) limited + C-83, Ganesh Nagar, Pandav Nagar, New Delhi, Delhi, 110092 + Delhi Delhi 110092 + IN + 80-77-86 (hex) Daisy Audio Inc. 000000-0FFFFF (base 16) Daisy Audio Inc. 500 N Central Ave. Suite 600 Glendale CA 91203 US +80-77-86 (hex) YSTen Technology Co., Ltd. +800000-8FFFFF (base 16) YSTen Technology Co., Ltd. + Room 101, Building D (Cygnus), Wuxi Software Park, No. 111 Linghu Avenue, Xinwu District, + Wuxi City Jiangsu Province 214028 + CN + 08-3C-03 (hex) Private B00000-BFFFFF (base 16) Private +08-3C-03 (hex) Wildtech +200000-2FFFFF (base 16) Wildtech + 23 Leinster Road + Christchurch 8014 + NZ + 08-3C-03 (hex) SNM Technology 100000-1FFFFF (base 16) SNM Technology 664, Sosa-ro @@ -22349,18 +22463,6 @@ B00000-BFFFFF (base 16) Private Austin TX 78750 US -80-77-86 (hex) YSTen Technology Co., Ltd. -800000-8FFFFF (base 16) YSTen Technology Co., Ltd. - Room 101, Building D (Cygnus), Wuxi Software Park, No. 111 Linghu Avenue, Xinwu District, - Wuxi City Jiangsu Province 214028 - CN - -08-3C-03 (hex) Wildtech -200000-2FFFFF (base 16) Wildtech - 23 Leinster Road - Christchurch 8014 - NZ - 08-3C-03 (hex) Federal Signal SSG 000000-0FFFFF (base 16) Federal Signal SSG 2645 Federal Signal Drive @@ -22403,17 +22505,29 @@ D00000-DFFFFF (base 16) Schenker Storen AG Ellisville MO 63021 US +18-C3-E4 (hex) iX-tech GmbH +500000-5FFFFF (base 16) iX-tech GmbH + Römerstadt 2 + Saarbrücken Saarland 66121 + DE + 18-C3-E4 (hex) 38220 900000-9FFFFF (base 16) 38220 C/ Mariano Barbacid, 5. 3ª planta Rivas Vaciamadrid Madrid 28521 ES -18-C3-E4 (hex) iX-tech GmbH -500000-5FFFFF (base 16) iX-tech GmbH - Römerstadt 2 - Saarbrücken Saarland 66121 - DE +C4-82-72 (hex) Mode Sensors AS +700000-7FFFFF (base 16) Mode Sensors AS + Sluppenveien 6 + Trondheim 7037 + NO + +18-C3-E4 (hex) Fime SAS +A00000-AFFFFF (base 16) Fime SAS + 8 rue du Commodore JH HALLET + CAEN 14000 + FR 18-C3-E4 (hex) Sodalec 000000-0FFFFF (base 16) Sodalec @@ -22421,12 +22535,69 @@ D00000-DFFFFF (base 16) Schenker Storen AG Pacé 35740 FR -18-C3-E4 (hex) Fime SAS -A00000-AFFFFF (base 16) Fime SAS - 8 rue du Commodore JH HALLET - CAEN 14000 +C4-82-72 (hex) Schunk SE & Co. KG +500000-5FFFFF (base 16) Schunk SE & Co. KG + Bahnhofstraße 106-134 + Lauffen am Neckar 74348 + DE + +C4-82-72 (hex) E2-CAD +C00000-CFFFFF (base 16) E2-CAD + 13-17 Allée Rosa Luxemburg + Eragny sur oise 95610 FR +C4-82-72 (hex) Private +100000-1FFFFF (base 16) Private + +C4-82-72 (hex) Tolt Technologies LLC +A00000-AFFFFF (base 16) Tolt Technologies LLC + 19520 Mountain View Road NE + Duvall WA 98019-8822 + US + +38-B1-4E (hex) Huizhou GYXX Technology Co., Ltd +B00000-BFFFFF (base 16) Huizhou GYXX Technology Co., Ltd + Room 01, 4th Floor, Building 2,No. 13, Dahuixi Section, Huizhou Avenue,Shuikou Subdistrict Office, Huicheng District,Huizhou, Guangdong, China + Huizhou 516005 + CN + +C4-82-72 (hex) Smart Radar System, Inc +E00000-EFFFFF (base 16) Smart Radar System, Inc + 7F, Innovalley A, 253 Pangyo-ro Bundang-gu + Seongnam-si Gyeonggi-do Korea 13486 + KR + +38-B1-4E (hex) NACE +700000-7FFFFF (base 16) NACE + 1085 Andrew dr + West Chester PA 19380 + US + +20-B3-7F (hex) B810 SPA +B00000-BFFFFF (base 16) B810 SPA + Via Enzo Lazzaretti, 2/1 + REGGIO EMILIA Reggio Emilia 42122 + IT + +20-B3-7F (hex) Luxedo +700000-7FFFFF (base 16) Luxedo + 1232 Topside Rd. + Louisville TN 37777 + US + +20-B3-7F (hex) Chelsio Communications Inc +000000-0FFFFF (base 16) Chelsio Communications Inc + 735, N Pastoria Av + SUNNYVALE CA 94085 + US + +20-B3-7F (hex) Xunmu Information Technology (Shanghai) Co., Ltd. +D00000-DFFFFF (base 16) Xunmu Information Technology (Shanghai) Co., Ltd. + 15F,New Bund Oriental Plaza 1,No.512,Haiyang West Road, Pudong New Area, Shanghai + Shanghai 200135 + CN + D0-14-11 (hex) P.B. Elettronica srl 100000-1FFFFF (base 16) P.B. Elettronica srl Via Santorelli, 8 @@ -26186,12 +26357,6 @@ B00000-BFFFFF (base 16) JNL Technologies Inc Ixonia WI 53036 US -C4-FF-BC (hex) iMageTech CO.,LTD. -400000-4FFFFF (base 16) iMageTech CO.,LTD. - 5F., No.16, Lane 15, Sec. 6, Mincyuan E. Rd., Neihu District, - TAIPEI 114 - TW - 9C-43-1E (hex) SuZhou Jinruiyang Information Technology CO.,LTD C00000-CFFFFF (base 16) SuZhou Jinruiyang Information Technology CO.,LTD NO.1003 Room A1 Buliding Tengfei Business Park in Suzhou Industrial Park. @@ -29264,23 +29429,17 @@ FC-A2-DF (hex) SpacemiT zhuhai guangdong 519000 CN -D4-A0-FB (hex) Shenzhen Dijiean Technology Co., Ltd -400000-4FFFFF (base 16) Shenzhen Dijiean Technology Co., Ltd - Floor 6,Building B,Tongxie Industrial Zone,No.80 Shilong Road,Shiyan Street,Baoan District - Shenzhen City Guangdong 518000 - CN - 04-58-5D (hex) Wetatronics Limited 000000-0FFFFF (base 16) Wetatronics Limited 45 Bath StreetParnell Auckland Auckland 1052 NZ -D4-A0-FB (hex) M2MD Technologies, Inc. -000000-0FFFFF (base 16) M2MD Technologies, Inc. - 525 Chestnut Rose Ln - Atlanta GA 30327 - US +04-58-5D (hex) JRK VISION +800000-8FFFFF (base 16) JRK VISION + A-1107, 135, Gasan digital 2-ro, Geumcheon-gu + SEOUL 08504 + KR D4-A0-FB (hex) Corelase Oy 500000-5FFFFF (base 16) Corelase Oy @@ -29288,16 +29447,16 @@ D4-A0-FB (hex) Corelase Oy Tampere Pirkanmaa 33720 FI -04-58-5D (hex) JRK VISION -800000-8FFFFF (base 16) JRK VISION - A-1107, 135, Gasan digital 2-ro, Geumcheon-gu - SEOUL 08504 - KR +D4-A0-FB (hex) Shenzhen Dijiean Technology Co., Ltd +400000-4FFFFF (base 16) Shenzhen Dijiean Technology Co., Ltd + Floor 6,Building B,Tongxie Industrial Zone,No.80 Shilong Road,Shiyan Street,Baoan District + Shenzhen City Guangdong 518000 + CN -D4-A0-FB (hex) Spatial Hover Inc -B00000-BFFFFF (base 16) Spatial Hover Inc - 10415 A Westpark Dr. - Houston TX 77042 +D4-A0-FB (hex) M2MD Technologies, Inc. +000000-0FFFFF (base 16) M2MD Technologies, Inc. + 525 Chestnut Rose Ln + Atlanta GA 30327 US D4-A0-FB (hex) NEXXUS NETWORKS INDIA PRIVATE LIMITED @@ -29306,6 +29465,12 @@ D4-A0-FB (hex) NEXXUS NETWORKS INDIA PRIVATE LIMITED GAUTAM BUDDHA NAGAR UTTAR PRADESH 201301 IN +D4-A0-FB (hex) Spatial Hover Inc +B00000-BFFFFF (base 16) Spatial Hover Inc + 10415 A Westpark Dr. + Houston TX 77042 + US + D4-A0-FB (hex) Huizhou Jiemeisi Technology Co.,Ltd. 600000-6FFFFF (base 16) Huizhou Jiemeisi Technology Co.,Ltd. NO.63, HUMEI STREET, DASHULING, XIAOJINKOU HUICHENG @@ -29336,12 +29501,6 @@ B0-CC-CE (hex) Agrisys A/S Seongnam 13590 KR -FC-E4-98 (hex) QuEL, Inc. -100000-1FFFFF (base 16) QuEL, Inc. - ON Build. 5F 4-7-14 Myojincho - Hachioji Tokyo 192-0046 - JP - FC-E4-98 (hex) Infinity Electronics Ltd D00000-DFFFFF (base 16) Infinity Electronics Ltd 167-169 Great Portland Street @@ -29360,6 +29519,12 @@ FC-E4-98 (hex) AVCON Information Technology Co.,Ltd. Shanghai Shanghai 021-55666588 CN +FC-E4-98 (hex) QuEL, Inc. +100000-1FFFFF (base 16) QuEL, Inc. + ON Build. 5F 4-7-14 Myojincho + Hachioji Tokyo 192-0046 + JP + 34-B5-F3 (hex) Hyatta Digital Technology Co., Ltd. 700000-7FFFFF (base 16) Hyatta Digital Technology Co., Ltd. 1405, Building A, Huizhi R&D Center, No. 287 Guangshen Road, Xixiang Street, Bao'an District @@ -29396,11 +29561,11 @@ C00000-CFFFFF (base 16) Lab241 Co.,Ltd. Seoul 08511 KR -F4-97-9D (hex) Huitec printer solution co., -D00000-DFFFFF (base 16) Huitec printer solution co., - 2f#104 Minchuan Rd. Hisdean district - New Taipei Taiwan 23141 - TW +E0-23-3B (hex) Quality Pay Systems S.L. +000000-0FFFFF (base 16) Quality Pay Systems S.L. + 21 Forja Avenue, Cañada de la Fuente Industrial Park + Martos Jaen 23600 + ES F4-97-9D (hex) Beijing Jiaxin Technology Co., Ltd 900000-9FFFFF (base 16) Beijing Jiaxin Technology Co., Ltd @@ -29414,59 +29579,47 @@ F4-97-9D (hex) Equinox Power Burnaby BC V5J 0H1 CA +F4-97-9D (hex) Huitec printer solution co., +D00000-DFFFFF (base 16) Huitec printer solution co., + 2f#104 Minchuan Rd. Hisdean district + New Taipei Taiwan 23141 + TW + +E0-23-3B (hex) 356 Productions +300000-3FFFFF (base 16) 356 Productions + 1881 West Traverse Pkwy + LEHI UT 84043 + US + E0-23-3B (hex) Chengdu ChengFeng Technology co,. Ltd. C00000-CFFFFF (base 16) Chengdu ChengFeng Technology co,. Ltd. High-tech Zone TianfuSoftwarePark,B6-103,CHENGDU, 610000 CHENGDU SICHUAN 610000 CN -E0-23-3B (hex) Quality Pay Systems S.L. -000000-0FFFFF (base 16) Quality Pay Systems S.L. - 21 Forja Avenue, Cañada de la Fuente Industrial Park - Martos Jaen 23600 - ES - E0-23-3B (hex) Kiwimoore(Shanghai) Semiconductor Co.,Ltd 600000-6FFFFF (base 16) Kiwimoore(Shanghai) Semiconductor Co.,Ltd 9F, Block B, No. 800 Naxian Road, Pudong New District Shanghai 201210 CN -E0-23-3B (hex) 356 Productions -300000-3FFFFF (base 16) 356 Productions - 1881 West Traverse Pkwy - LEHI UT 84043 - US - E0-23-3B (hex) Elvys s.r.o 100000-1FFFFF (base 16) Elvys s.r.o Polska 9 Kosice 04011 SK -00-6A-5E (hex) DICOM CORPORATION -600000-6FFFFF (base 16) DICOM CORPORATION - 15TH FL, CENTER BUILDING, NO 1 NGUYEN HUY TUONG STR, THANH XUAN WARD - Hanoi 100000 - VN - 50-FA-CB (hex) Bosch Security Systems 900000-9FFFFF (base 16) Bosch Security Systems Estrada Nacional 109/IC 1 Ovar Aveiro 3880-728 PT -0C-7F-ED (hex) Tango Networks Inc -200000-2FFFFF (base 16) Tango Networks Inc - 2601 Network Blvd, Suite 410 - Frisco TX TX 75034 - US - -50-FA-CB (hex) 1208815047 -600000-6FFFFF (base 16) 1208815047 - 5F, 547, SAMSEONG-RO, GANGNAM-GU,SEOUL, SOUTH KOREA - seoul 06156 - KR +00-6A-5E (hex) DICOM CORPORATION +600000-6FFFFF (base 16) DICOM CORPORATION + 15TH FL, CENTER BUILDING, NO 1 NGUYEN HUY TUONG STR, THANH XUAN WARD + Hanoi 100000 + VN 50-FA-CB (hex) Combined Public Communications, LLC B00000-BFFFFF (base 16) Combined Public Communications, LLC @@ -29474,6 +29627,12 @@ B00000-BFFFFF (base 16) Combined Public Communications, LLC Cold Spring KY 41076 US +0C-7F-ED (hex) Tango Networks Inc +200000-2FFFFF (base 16) Tango Networks Inc + 2601 Network Blvd, Suite 410 + Frisco TX TX 75034 + US + 50-FA-CB (hex) todoc 000000-0FFFFF (base 16) todoc 501ho, 242 Digital-ro @@ -29486,12 +29645,30 @@ D00000-DFFFFF (base 16) Vortex Infotech Private Limited Mumbai Maharashtra 400104 IN +54-A4-93 (hex) Shanghai Kanghai Information System CO.,LTD. +400000-4FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. + 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District + ShenZhen GuangDong 518000 + CN + 04-EE-E8 (hex) Shanghai Kanghai Information System CO.,LTD. 700000-7FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN +50-FA-CB (hex) 1208815047 +600000-6FFFFF (base 16) 1208815047 + 5F, 547, SAMSEONG-RO, GANGNAM-GU,SEOUL, SOUTH KOREA + seoul 06156 + KR + +24-A1-0D (hex) Amina Distribution AS +D00000-DFFFFF (base 16) Amina Distribution AS + Strandsvingen 14A + Stavanger 4032 + NO + 88-A6-EF (hex) Shanghai Kanghai Information System CO.,LTD. C00000-CFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District @@ -29504,42 +29681,12 @@ B00000-BFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. ShenZhen GuangDong 518000 CN -54-A4-93 (hex) Shanghai Kanghai Information System CO.,LTD. -400000-4FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. - 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District - ShenZhen GuangDong 518000 - CN - -24-A1-0D (hex) Amina Distribution AS -D00000-DFFFFF (base 16) Amina Distribution AS - Strandsvingen 14A - Stavanger 4032 - NO - 24-A1-0D (hex) Gönnheimer Elektronic GmbH E00000-EFFFFF (base 16) Gönnheimer Elektronic GmbH Dr. Julius Leber Str. 2 Neustadt Rheinland Pfalz 67433 DE -F0-40-AF (hex) Proemion GmbH -D00000-DFFFFF (base 16) Proemion GmbH - Donaustraße 14 - Fulda Hessen 36043 - DE - -F0-40-AF (hex) Unionbell Technologies Limited -700000-7FFFFF (base 16) Unionbell Technologies Limited - Crown Court Estate, NO 11 DR Nwachukwu Nwanesi Street - Durumi Abuja 900103 - NG - -F0-40-AF (hex) Shanghai Kanghai Information System CO.,LTD. -E00000-EFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. - 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District - ShenZhen GuangDong 518000 - CN - F0-40-AF (hex) Colorlight Cloud Tech Ltd 000000-0FFFFF (base 16) Colorlight Cloud Tech Ltd 38F, Building A, Building 8, Shenzhen International Innovation Valley, Vanke Cloud City, Nanshan District, Shenzhen @@ -29558,29 +29705,41 @@ F0-40-AF (hex) Nuro.ai Mountain View CA 94070 US -E8-F6-D7 (hex) Mono Technologies Inc. -000000-0FFFFF (base 16) Mono Technologies Inc. - 600 N Broad Street, Suite 5 # 924 - Middletown DE 19709 - US - -E8-F6-D7 (hex) Ivostud GmbH -A00000-AFFFFF (base 16) Ivostud GmbH - Schützenstraße 6-8 - Breckerfeld 58339 +F0-40-AF (hex) Proemion GmbH +D00000-DFFFFF (base 16) Proemion GmbH + Donaustraße 14 + Fulda Hessen 36043 DE +F0-40-AF (hex) Shanghai Kanghai Information System CO.,LTD. +E00000-EFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. + 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District + ShenZhen GuangDong 518000 + CN + +F0-40-AF (hex) Unionbell Technologies Limited +700000-7FFFFF (base 16) Unionbell Technologies Limited + Crown Court Estate, NO 11 DR Nwachukwu Nwanesi Street + Durumi Abuja 900103 + NG + E8-F6-D7 (hex) ZhuoPuCheng (Shenzhen) Technology.Co.,Ltd. D00000-DFFFFF (base 16) ZhuoPuCheng (Shenzhen) Technology.Co.,Ltd. Building T3, Gaoxin Industrial Village, No. 011, Gaoxin Nanqi Dao Shenzhen Guangdong 518057 CN -74-33-36 (hex) SECLAB FR -500000-5FFFFF (base 16) SECLAB FR - 40 av Theroigne de Mericourt - MONTPELLIER 34000 - FR +E8-F6-D7 (hex) Mono Technologies Inc. +000000-0FFFFF (base 16) Mono Technologies Inc. + 600 N Broad Street, Suite 5 # 924 + Middletown DE 19709 + US + +E8-F6-D7 (hex) Ivostud GmbH +A00000-AFFFFF (base 16) Ivostud GmbH + Schützenstraße 6-8 + Breckerfeld 58339 + DE 74-33-36 (hex) Shenzhen Handheld-Wireless Technology Co., Ltd. C00000-CFFFFF (base 16) Shenzhen Handheld-Wireless Technology Co., Ltd. @@ -29588,24 +29747,30 @@ C00000-CFFFFF (base 16) Shenzhen Handheld-Wireless Technology Co., Ltd. Shenzhen GuangDong 518000 CN -74-33-36 (hex) Huzhou Luxshare Precision Industry Co.LTD -000000-0FFFFF (base 16) Huzhou Luxshare Precision Industry Co.LTD - 399 Shengxun Road, Zhili Town, Wuxing District,Huzhou City, Zhejiang Province - Huzhou Zhejiang 313008 - CN - 74-33-36 (hex) Annapurna labs B00000-BFFFFF (base 16) Annapurna labs Matam Scientific Industries Center, Building 8.2 Mail box 15123 Haifa 3508409 IL +74-33-36 (hex) SECLAB FR +500000-5FFFFF (base 16) SECLAB FR + 40 av Theroigne de Mericourt + MONTPELLIER 34000 + FR + 74-33-36 (hex) Venture International Pte Ltd 700000-7FFFFF (base 16) Venture International Pte Ltd 5006, Ang Mo Kio Ave 5, #05-01/12, Techplace II Singapore 569873 SG +74-33-36 (hex) Huzhou Luxshare Precision Industry Co.LTD +000000-0FFFFF (base 16) Huzhou Luxshare Precision Industry Co.LTD + 399 Shengxun Road, Zhili Town, Wuxing District,Huzhou City, Zhejiang Province + Huzhou Zhejiang 313008 + CN + C0-D3-91 (hex) SAMSARA NETWORKS INC E00000-EFFFFF (base 16) SAMSARA NETWORKS INC 1 De Haro St @@ -29624,18 +29789,18 @@ B00000-BFFFFF (base 16) Arvind Limited Pune Maharastra 411060 IN -58-76-07 (hex) Olte Climate sp. z o.o. -800000-8FFFFF (base 16) Olte Climate sp. z o.o. - ul. Rzeczna 8/5NIP: 6772533194 - Krakow malopolska 30-021 - PL - 20-2B-DA (hex) Shenzhen FeiCheng Technology Co.,Ltd 600000-6FFFFF (base 16) Shenzhen FeiCheng Technology Co.,Ltd Room 402, Building B, Huafeng Internet Creative Park, No. 107 Gongye Road, Gonge Community, Xixiang Street, Bao'an District, Shenzhen Shenzhen 518000 CN +58-76-07 (hex) Olte Climate sp. z o.o. +800000-8FFFFF (base 16) Olte Climate sp. z o.o. + ul. Rzeczna 8/5NIP: 6772533194 + Krakow malopolska 30-021 + PL + 20-2B-DA (hex) Industrial Connections & Solutions LLC A00000-AFFFFF (base 16) Industrial Connections & Solutions LLC 6801 Industrial Dr @@ -29654,6 +29819,18 @@ A00000-AFFFFF (base 16) Industrial Connections & Solutions LLC shanghai 200031 CN +58-AD-08 (hex) Wuxi Qinghexiaobei Technology Co., Ltd. +900000-9FFFFF (base 16) Wuxi Qinghexiaobei Technology Co., Ltd. + 801C,Building E, Yingchuang Power, NO.1 Shangdi East Road, Haidian District, Beijing + Beijing Beijing 100085 + CN + +58-AD-08 (hex) Also, Inc. +C00000-CFFFFF (base 16) Also, Inc. + 630 Hansen Way + Palo Alto CA 94306 + US + 5C-5C-75 (hex) Anhui Haima Cloud Technology Co.,Ltd B00000-BFFFFF (base 16) Anhui Haima Cloud Technology Co.,Ltd Wangjiang West Road 900# @@ -29666,17 +29843,11 @@ B00000-BFFFFF (base 16) Anhui Haima Cloud Technology Co.,Ltd Beijing Beijing 100176 CN -58-AD-08 (hex) Also, Inc. -C00000-CFFFFF (base 16) Also, Inc. - 630 Hansen Way - Palo Alto CA 94306 - US - -58-AD-08 (hex) Wuxi Qinghexiaobei Technology Co., Ltd. -900000-9FFFFF (base 16) Wuxi Qinghexiaobei Technology Co., Ltd. - 801C,Building E, Yingchuang Power, NO.1 Shangdi East Road, Haidian District, Beijing - Beijing Beijing 100085 - CN +60-15-9F (hex) Klaric GmbH & Co. KG +100000-1FFFFF (base 16) Klaric GmbH & Co. KG + Kesselstr. 17 + Stuttgart 70327 + DE 58-AD-08 (hex) Shenzhen Yumutek co.,ltd 400000-4FFFFF (base 16) Shenzhen Yumutek co.,ltd @@ -29684,23 +29855,17 @@ C00000-CFFFFF (base 16) Also, Inc. Shenzhen Guangdong 518000 CN -60-15-9F (hex) FF Videosistemas SL -D00000-DFFFFF (base 16) FF Videosistemas SL - Calle Vizcaya, 2 - Las Rozas de Madrid Madrid 28231 - ES - B4-AB-F3 (hex) Annapurna labs C00000-CFFFFF (base 16) Annapurna labs Matam Scientific Industries Center, Building 8.2 Mail box 15123 Haifa 3508409 IL -60-15-9F (hex) Klaric GmbH & Co. KG -100000-1FFFFF (base 16) Klaric GmbH & Co. KG - Kesselstr. 17 - Stuttgart 70327 - DE +60-15-9F (hex) FF Videosistemas SL +D00000-DFFFFF (base 16) FF Videosistemas SL + Calle Vizcaya, 2 + Las Rozas de Madrid Madrid 28231 + ES 60-15-9F (hex) Lens Technology(Xiangtan) Co.,Ltd B00000-BFFFFF (base 16) Lens Technology(Xiangtan) Co.,Ltd @@ -29708,12 +29873,6 @@ B00000-BFFFFF (base 16) Lens Technology(Xiangtan) Co.,Ltd Xiangtan Hunan 411100 CN -80-77-86 (hex) SMW-Autoblok Spannsysteme -900000-9FFFFF (base 16) SMW-Autoblok Spannsysteme - Wiesentalstr. 28 - Meckenbeuren 88074 - DE - 80-77-86 (hex) Applied Energy Technologies Pvt Ltd D00000-DFFFFF (base 16) Applied Energy Technologies Pvt Ltd Plot No:288A,3rd Floor, Udyog Vihar Phase-4, Sector-18,Gurugram Haryana-122015 @@ -29726,23 +29885,35 @@ D00000-DFFFFF (base 16) Applied Energy Technologies Pvt Ltd Pune Maharashtra 411023 IN +80-77-86 (hex) SMW-Autoblok Spannsysteme +900000-9FFFFF (base 16) SMW-Autoblok Spannsysteme + Wiesentalstr. 28 + Meckenbeuren 88074 + DE + 80-77-86 (hex) Mach B00000-BFFFFF (base 16) Mach 2002 Bethel Rd Ste 105 Finksburg MD 21048 US +08-3C-03 (hex) Jiaxing UPhoton Optoelectronics Technology Co., Ltd. +600000-6FFFFF (base 16) Jiaxing UPhoton Optoelectronics Technology Co., Ltd. + Room 102, 1st Floor, Building 10, No. 1156 Gaoqiao Avenue, Gaoqiao Street (Development Zone), Tongxiang City + Jiaxing Zhejiang 314500 + CN + 08-3C-03 (hex) Yinglian Technology Co.,Ltd D00000-DFFFFF (base 16) Yinglian Technology Co.,Ltd Room 802, 8th Floor, Building 5, Wenzhou Runchen Technology Co., Ltd., No. 333 Jiankang Road, Wanquan Town, Pingyang County Wenzhou Zhejiang 325000 CN -08-3C-03 (hex) Jiaxing UPhoton Optoelectronics Technology Co., Ltd. -600000-6FFFFF (base 16) Jiaxing UPhoton Optoelectronics Technology Co., Ltd. - Room 102, 1st Floor, Building 10, No. 1156 Gaoqiao Avenue, Gaoqiao Street (Development Zone), Tongxiang City - Jiaxing Zhejiang 314500 - CN +08-3C-03 (hex) GS Industrie-Elektronik GmbH +800000-8FFFFF (base 16) GS Industrie-Elektronik GmbH + Porschestrasse 11 + Leverkusen 51381 + DE 08-3C-03 (hex) LEADTEK BIOMED INC. 500000-5FFFFF (base 16) LEADTEK BIOMED INC. @@ -29756,12 +29927,6 @@ E00000-EFFFFF (base 16) Prozone jalandhar Punjab 144001 IN -08-3C-03 (hex) GS Industrie-Elektronik GmbH -800000-8FFFFF (base 16) GS Industrie-Elektronik GmbH - Porschestrasse 11 - Leverkusen 51381 - DE - 34-D7-F5 (hex) DREAMTECH 600000-6FFFFF (base 16) DREAMTECH 10F, U-Space 2 A Tower, 670 Daewangpangyo-ro, Bundang-Gu, Seongnam-Si, Gyeonggi-Do, Republic of Korea @@ -29774,8 +29939,11 @@ E00000-EFFFFF (base 16) Prozone ShenZhen Guangdong 518004 CN -6C-47-80 (hex) Private -900000-9FFFFF (base 16) Private +6C-47-80 (hex) KEI SYSTEM Co., Ltd. +000000-0FFFFF (base 16) KEI SYSTEM Co., Ltd. + 2-2-23 Kishinosato, Nishinari Ward + Osaka City Osaka 557-0041 + JP 34-D7-F5 (hex) Catapult Sports Inc E00000-EFFFFF (base 16) Catapult Sports Inc @@ -29783,18 +29951,15 @@ E00000-EFFFFF (base 16) Catapult Sports Inc Boston MA 02109 US -6C-47-80 (hex) KEI SYSTEM Co., Ltd. -000000-0FFFFF (base 16) KEI SYSTEM Co., Ltd. - 2-2-23 Kishinosato, Nishinari Ward - Osaka City Osaka 557-0041 - JP - 34-D7-F5 (hex) Cassel Messtechnik GmbH 300000-3FFFFF (base 16) Cassel Messtechnik GmbH In der Dehne 10 Dransfeld 37127 DE +6C-47-80 (hex) Private +900000-9FFFFF (base 16) Private + 18-C3-E4 (hex) HuiTong intelligence Company 100000-1FFFFF (base 16) HuiTong intelligence Company 8F., No. 51, Ln. 258, Rueiguang Rd., Neihu Dist., Taipei City 114, Taiwan (R.O.C.) @@ -29813,14 +29978,53 @@ E00000-EFFFFF (base 16) SHENZHEN MEGMEET ELECTRICAL CO., LTD ShenZhen 518051 CN +6C-47-80 (hex) Private +800000-8FFFFF (base 16) Private + 18-C3-E4 (hex) Bit Part LLC C00000-CFFFFF (base 16) Bit Part LLC 224 W 35th St, Ste 500 PMB 497 New York NY 10001 US -6C-47-80 (hex) Private -800000-8FFFFF (base 16) Private +C4-82-72 (hex) Gabriel Tecnologia +000000-0FFFFF (base 16) Gabriel Tecnologia + Rua Doutor Virgilio de Carvalho Pinto, 142 + São Paulo SP 05415-020 + BR + +C4-82-72 (hex) Posital B.V. +D00000-DFFFFF (base 16) Posital B.V. + ECI 13 + Roermond 6041MA + NL + +38-B1-4E (hex) Private +D00000-DFFFFF (base 16) Private + +C4-FF-BC (hex) HyperNet CO., LTD +400000-4FFFFF (base 16) HyperNet CO., LTD + 5F., No.16, Lane 15, Sec. 6, Mincyuan E. Rd., Neihu District, + TAIPEI 114 + TW + +20-B3-7F (hex) QT medical inc +300000-3FFFFF (base 16) QT medical inc + 1370 Valley Vista Dr Ste 266 + Diamond Bar CA 91765 + US + +38-B1-4E (hex) Knit Sound Company +E00000-EFFFFF (base 16) Knit Sound Company + 496 Ada Dr SE Ste 201 + Ada MI 49301 + US + +20-B3-7F (hex) Xconnect LLP +800000-8FFFFF (base 16) Xconnect LLP + Kurmangazy st 77 + Almaty Almaty 050022 + KZ C8-5C-E2 (hex) Fela Management AG 000000-0FFFFF (base 16) Fela Management AG @@ -31055,12 +31259,6 @@ E00000-EFFFFF (base 16) Suzhou Sidi Information Technology Co., Ltd. Suzhou 215000 CN -F4-A4-54 (hex) TRI WORKS -200000-2FFFFF (base 16) TRI WORKS - #402 Goto building 4F 2-2-2 Daimyo Chuo-ku - Fukuoka-shi 810-0041 - JP - F4-A4-54 (hex) Chongqing Hengxun Liansheng Industrial Co.,Ltd 300000-3FFFFF (base 16) Chongqing Hengxun Liansheng Industrial Co.,Ltd Shop 42, Area C, Chongqing Yixiang City, No. 12 Jiangnan Avenue, Nan'an District @@ -36773,11 +36971,11 @@ C00000-CFFFFF (base 16) Reonel Oy ji nan shi shandong 250031 CN -FC-A2-DF (hex) PDI COMMUNICATION SYSTEMS INC. -200000-2FFFFF (base 16) PDI COMMUNICATION SYSTEMS INC. - 40 GREENWOOD LN - SPRINGBORO OH 45066 - US +FC-A2-DF (hex) BPL MEDICAL TECHNOLOGIES PRIVATE LIMITED +A00000-AFFFFF (base 16) BPL MEDICAL TECHNOLOGIES PRIVATE LIMITED + 11KM BANNERGHATTA MAIN ROAD ARAKERE BANGALORE + BANGALORE KARNATAKA 560076 + IN FC-A2-DF (hex) Annapurna labs 500000-5FFFFF (base 16) Annapurna labs @@ -36785,11 +36983,11 @@ FC-A2-DF (hex) Annapurna labs Mail box 15123 Haifa 3508409 IL -FC-A2-DF (hex) BPL MEDICAL TECHNOLOGIES PRIVATE LIMITED -A00000-AFFFFF (base 16) BPL MEDICAL TECHNOLOGIES PRIVATE LIMITED - 11KM BANNERGHATTA MAIN ROAD ARAKERE BANGALORE - BANGALORE KARNATAKA 560076 - IN +FC-A2-DF (hex) PDI COMMUNICATION SYSTEMS INC. +200000-2FFFFF (base 16) PDI COMMUNICATION SYSTEMS INC. + 40 GREENWOOD LN + SPRINGBORO OH 45066 + US FC-A2-DF (hex) MBio Diagnostics, Inc. D00000-DFFFFF (base 16) MBio Diagnostics, Inc. @@ -36797,12 +36995,6 @@ D00000-DFFFFF (base 16) MBio Diagnostics, Inc. Loveland 80538 US -04-58-5D (hex) Foxconn Brasil Industria e Comercio Ltda -200000-2FFFFF (base 16) Foxconn Brasil Industria e Comercio Ltda - Av. Marginal da Rodovia dos Bandeirantes, 800 - Distrito Industrial - Jundiaí Sao Paulo 13213-008 - BR - 04-58-5D (hex) Dron Edge India Private Limited 900000-9FFFFF (base 16) Dron Edge India Private Limited A 93 SECTOR 65 NOIDA 201301 @@ -36821,6 +37013,12 @@ D00000-DFFFFF (base 16) MBio Diagnostics, Inc. Flensburg Schleswig-Holstein 24939 DE +04-58-5D (hex) Foxconn Brasil Industria e Comercio Ltda +200000-2FFFFF (base 16) Foxconn Brasil Industria e Comercio Ltda + Av. Marginal da Rodovia dos Bandeirantes, 800 - Distrito Industrial + Jundiaí Sao Paulo 13213-008 + BR + 04-58-5D (hex) Sercomm Japan Corporation 500000-5FFFFF (base 16) Sercomm Japan Corporation 8F, 3-1, YuanQu St., NanKang, Taipei 115, Taiwan @@ -36833,18 +37031,18 @@ A00000-AFFFFF (base 16) IMPULSE CCTV NETWORKS INDIA PVT. LTD. GREATER NOIDA WEST UTTAR PRADESH 201306 IN -D4-A0-FB (hex) Beijing Lingji Innovations technology Co,LTD. -200000-2FFFFF (base 16) Beijing Lingji Innovations technology Co,LTD. - Room 106, 1st Floor, A-1 Building, Zhongguancun Dongsheng Science and Technology Park, No. 66 Xixiaokou Road, Haidian District, Beijing - Beijing Beijing 100190 - CN - D4-A0-FB (hex) FASTWEL ELECTRONICS INDIA PRIVATE LIMITED D00000-DFFFFF (base 16) FASTWEL ELECTRONICS INDIA PRIVATE LIMITED DORASWANIPALYA , NO 3, ARKER MICOLAYOUT, ARKERE , BENGALURE(BANGLORE) URBAN BENGALURU KARNATAKA 560076 IN +D4-A0-FB (hex) Beijing Lingji Innovations technology Co,LTD. +200000-2FFFFF (base 16) Beijing Lingji Innovations technology Co,LTD. + Room 106, 1st Floor, A-1 Building, Zhongguancun Dongsheng Science and Technology Park, No. 66 Xixiaokou Road, Haidian District, Beijing + Beijing Beijing 100190 + CN + D4-A0-FB (hex) GTEK GLOBAL CO.,LTD E00000-EFFFFF (base 16) GTEK GLOBAL CO.,LTD No3/2/13 Ta Thanh Oai, Thanh Tri district @@ -36857,30 +37055,12 @@ A00000-AFFFFF (base 16) Shenzhen Dangs Science and Technology CO.,Ltd. Shenzhen Guangdong 518063 CN -78-78-35 (hex) Suzhou Chena Information Technology Co., Ltd. -D00000-DFFFFF (base 16) Suzhou Chena Information Technology Co., Ltd. - 3rd Floor, Building B6, No. 8 Yanghua Road, Suzhou Industrial Park - Suzhou Free Trade Zone Jiangsu Province 215000 - CN - B0-CC-CE (hex) Taiv Inc 900000-9FFFFF (base 16) Taiv Inc 400-321 McDermot Ave Winnipeg Manitoba R3A 0A3 CA -78-78-35 (hex) NEOARK Corporation -E00000-EFFFFF (base 16) NEOARK Corporation - Nakano-machi2073-1 - Hachioji Tokyo 1920015 - JP - -78-78-35 (hex) SHENZHEN CHUANGWEI ELECTRONIC APPLIANCE TECH CO., LTD. -300000-3FFFFF (base 16) SHENZHEN CHUANGWEI ELECTRONIC APPLIANCE TECH CO., LTD. - 6F Floor, Overseas Factory, Skyworth Technology Industrial Park, Tangtou Community, Shiyan Street, Bao'an District - Shenzhen Guangdong 518000 - CN - 78-78-35 (hex) ENQT GmbH 100000-1FFFFF (base 16) ENQT GmbH Spaldingstrasse 210 @@ -36893,12 +37073,30 @@ C00000-CFFFFF (base 16) DBG Communications Technology Co.,Ltd. Huizhou Gangdong 516083 CN +78-78-35 (hex) Suzhou Chena Information Technology Co., Ltd. +D00000-DFFFFF (base 16) Suzhou Chena Information Technology Co., Ltd. + 3rd Floor, Building B6, No. 8 Yanghua Road, Suzhou Industrial Park + Suzhou Free Trade Zone Jiangsu Province 215000 + CN + 78-78-35 (hex) Shanghai Intchains Technology Co., Ltd. B00000-BFFFFF (base 16) Shanghai Intchains Technology Co., Ltd. Building 1&2, No.333 Haiyang No.1 Road Lingang Science and Technology Park Pudon shanghai shanghai 200120 CN +78-78-35 (hex) NEOARK Corporation +E00000-EFFFFF (base 16) NEOARK Corporation + Nakano-machi2073-1 + Hachioji Tokyo 1920015 + JP + +78-78-35 (hex) SHENZHEN CHUANGWEI ELECTRONIC APPLIANCE TECH CO., LTD. +300000-3FFFFF (base 16) SHENZHEN CHUANGWEI ELECTRONIC APPLIANCE TECH CO., LTD. + 6F Floor, Overseas Factory, Skyworth Technology Industrial Park, Tangtou Community, Shiyan Street, Bao'an District + Shenzhen Guangdong 518000 + CN + FC-E4-98 (hex) Videonetics Technology Private Limited 600000-6FFFFF (base 16) Videonetics Technology Private Limited Videonetics Technology Private LimitedPlot No. AI/154/1, Action Area - 1A, 4th Floor, Utility Building @@ -36906,28 +37104,10 @@ FC-E4-98 (hex) Videonetics Technology Private Limited IN FC-E4-98 (hex) E Haute Intelligent Technology Co., Ltd -700000-7FFFFF (base 16) E Haute Intelligent Technology Co., Ltd - Room 01-05, 40/F, Building C, Longhua Digital Innovation Center, Longhua District - Shenzhen Guangdong 518000 - CN - -00-6A-5E (hex) TRULY ELECTRONICS MFG.,LTD -000000-0FFFFF (base 16) TRULY ELECTRONICS MFG.,LTD - Truly industry city,shanwei guangdong,P.R.C - shanwei guangdong 516600 - CN - -00-6A-5E (hex) BroadMaster Biotech Corp -100000-1FFFFF (base 16) BroadMaster Biotech Corp - No. 91 Xiyuan RD. Zhongli City - Taoyuan Select State 32057 - TW - -00-6A-5E (hex) Annapurna labs -900000-9FFFFF (base 16) Annapurna labs - Matam Scientific Industries Center, Building 8.2 - Mail box 15123 Haifa 3508409 - IL +700000-7FFFFF (base 16) E Haute Intelligent Technology Co., Ltd + Room 01-05, 40/F, Building C, Longhua Digital Innovation Center, Longhua District + Shenzhen Guangdong 518000 + CN 34-B5-F3 (hex) LAUMAS Elettronica s.r.l. 400000-4FFFFF (base 16) LAUMAS Elettronica s.r.l. @@ -36935,23 +37115,29 @@ FC-E4-98 (hex) E Haute Intelligent Technology Co., Ltd Montechiarugolo 43022 IT +00-6A-5E (hex) BroadMaster Biotech Corp +100000-1FFFFF (base 16) BroadMaster Biotech Corp + No. 91 Xiyuan RD. Zhongli City + Taoyuan Select State 32057 + TW + 34-B5-F3 (hex) Satco Europe GmbH 100000-1FFFFF (base 16) Satco Europe GmbH Waidhauserstr. 3 Vohenstrauß 92546 DE -E0-23-3B (hex) The KIE -400000-4FFFFF (base 16) The KIE - 6F, 619, 42, Changeop-ro, Sujeong-gu, Seongnam-si - Gyeonggi-do 13449 - KR +00-6A-5E (hex) Annapurna labs +900000-9FFFFF (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL -E0-23-3B (hex) HANET TECHNOLOGY -900000-9FFFFF (base 16) HANET TECHNOLOGY - 13th Floor, G-Group Tower Building, No. 5 Nguyen Thi Due, Yen Hoa Ward - HANOI 70000 - VN +00-6A-5E (hex) TRULY ELECTRONICS MFG.,LTD +000000-0FFFFF (base 16) TRULY ELECTRONICS MFG.,LTD + Truly industry city,shanwei guangdong,P.R.C + shanwei guangdong 516600 + CN 48-08-EB (hex) Guangdong Three Link Technology Co., Ltd 200000-2FFFFF (base 16) Guangdong Three Link Technology Co., Ltd @@ -36965,6 +37151,18 @@ E0-23-3B (hex) HANET TECHNOLOGY Velka Lomnica Presov 05952 SK +E0-23-3B (hex) HANET TECHNOLOGY +900000-9FFFFF (base 16) HANET TECHNOLOGY + 13th Floor, G-Group Tower Building, No. 5 Nguyen Thi Due, Yen Hoa Ward + HANOI 70000 + VN + +E0-23-3B (hex) The KIE +400000-4FFFFF (base 16) The KIE + 6F, 619, 42, Changeop-ro, Sujeong-gu, Seongnam-si + Gyeonggi-do 13449 + KR + 50-FA-CB (hex) ZENOPIX TEKNOLOJI SAN VE TIC LTD STI 700000-7FFFFF (base 16) ZENOPIX TEKNOLOJI SAN VE TIC LTD STI AKADEMI MAH. GURBULUT SK. S.U.TEKNOLOJI GELISTIRME BOLGESI KONYA TEKNOKENT NO:67 SELCUKLU @@ -36995,32 +37193,32 @@ A00000-AFFFFF (base 16) AUO DISPLAY PLUS CORPORATION Shenzhen Guangdong 518000 CN -78-13-05 (hex) Shanghai Kanghai Information System CO.,LTD. -900000-9FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +C4-98-94 (hex) Shanghai Kanghai Information System CO.,LTD. +700000-7FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -58-47-CA (hex) Shanghai Kanghai Information System CO.,LTD. -600000-6FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +34-46-63 (hex) Shanghai Kanghai Information System CO.,LTD. +900000-9FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -C4-98-94 (hex) Shanghai Kanghai Information System CO.,LTD. -700000-7FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +04-A1-6F (hex) Shanghai Kanghai Information System CO.,LTD. +000000-0FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -34-46-63 (hex) Shanghai Kanghai Information System CO.,LTD. +78-13-05 (hex) Shanghai Kanghai Information System CO.,LTD. 900000-9FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -04-A1-6F (hex) Shanghai Kanghai Information System CO.,LTD. -000000-0FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +58-47-CA (hex) Shanghai Kanghai Information System CO.,LTD. +600000-6FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN @@ -37049,18 +37247,18 @@ B00000-BFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. Taichung 40768 TW -24-A1-0D (hex) Luxvisions lnnovation TechnologyCorp.Limited -800000-8FFFFF (base 16) Luxvisions lnnovation TechnologyCorp.Limited - No. 69, Yongsheng Road, Huangpu District, Guangzhou - Guangzhou Guangdong Province 510000 - CN - 24-A1-0D (hex) Dongguan Taijie Electronics Technology Co.,Ltd 400000-4FFFFF (base 16) Dongguan Taijie Electronics Technology Co.,Ltd 5F, 6# Building, Sanjia Industrial Park, Dongkeng Town Dongguan Guangdong 523000 CN +24-A1-0D (hex) Luxvisions lnnovation TechnologyCorp.Limited +800000-8FFFFF (base 16) Luxvisions lnnovation TechnologyCorp.Limited + No. 69, Yongsheng Road, Huangpu District, Guangzhou + Guangzhou Guangdong Province 510000 + CN + F0-40-AF (hex) SIEMENS AG A00000-AFFFFF (base 16) SIEMENS AG Oestl. Rheinbrueckenstr.50 @@ -37127,18 +37325,18 @@ B00000-BFFFFF (base 16) 대한전력전자 Colne Lancashire BB8 8LJ GB -20-2B-DA (hex) Chongqing Ruishixing Technology Co., Ltd -700000-7FFFFF (base 16) Chongqing Ruishixing Technology Co., Ltd - No. 1, 5th Floor, Unit 2, Building 1, Jinqian Port Industrial Park, No. 808, Haier Road, Tieshanping Street, - Jiangbei District Chongqing 400000 - CN - 0C-BF-B4 (hex) Shenzhen PengBrain Technology Co.,Ltd E00000-EFFFFF (base 16) Shenzhen PengBrain Technology Co.,Ltd B1014, Building 2, Chuangwei Innovation Valley, No. 8, Tangtou 1st Road, Tangtou Community, Shiyan Street, Bao'an District, Shenzhen Guangdong 518000 CN +20-2B-DA (hex) Chongqing Ruishixing Technology Co., Ltd +700000-7FFFFF (base 16) Chongqing Ruishixing Technology Co., Ltd + No. 1, 5th Floor, Unit 2, Building 1, Jinqian Port Industrial Park, No. 808, Haier Road, Tieshanping Street, + Jiangbei District Chongqing 400000 + CN + 20-2B-DA (hex) BRUSH ELECTRICAL MACHINES LTD 800000-8FFFFF (base 16) BRUSH ELECTRICAL MACHINES LTD Powerhouse, Excelsior Rd @@ -37157,11 +37355,11 @@ E00000-EFFFFF (base 16) Shenzhen PengBrain Technology Co.,Ltd Schenkon LU 6214 CH -20-2B-DA (hex) Plato System Development B.V. -500000-5FFFFF (base 16) Plato System Development B.V. - Amerikalaan 59 - Maastricht-Airport 6199 AE - NL +20-2B-DA (hex) Transit Solutions, LLC. +D00000-DFFFFF (base 16) Transit Solutions, LLC. + 114 West Grandview Avenue + Zelienople PA 16063 + US 20-2B-DA (hex) Teletek Electronics JSC 400000-4FFFFF (base 16) Teletek Electronics JSC @@ -37169,11 +37367,17 @@ E00000-EFFFFF (base 16) Shenzhen PengBrain Technology Co.,Ltd Sofia Sofia 1220 BG -20-2B-DA (hex) Transit Solutions, LLC. -D00000-DFFFFF (base 16) Transit Solutions, LLC. - 114 West Grandview Avenue - Zelienople PA 16063 - US +20-2B-DA (hex) Plato System Development B.V. +500000-5FFFFF (base 16) Plato System Development B.V. + Amerikalaan 59 + Maastricht-Airport 6199 AE + NL + +58-76-07 (hex) BOE Technology Group Co., Ltd. +C00000-CFFFFF (base 16) BOE Technology Group Co., Ltd. + No.12 Xihuanzhong RD, BDA + Beijing Beijing 100176 + CN 5C-5C-75 (hex) youyeetoo 200000-2FFFFF (base 16) youyeetoo @@ -37181,11 +37385,11 @@ D00000-DFFFFF (base 16) Transit Solutions, LLC. Shenzhen Guangdong 518100 CN -58-76-07 (hex) BOE Technology Group Co., Ltd. -C00000-CFFFFF (base 16) BOE Technology Group Co., Ltd. - No.12 Xihuanzhong RD, BDA - Beijing Beijing 100176 - CN +5C-5C-75 (hex) TECTOY S.A +100000-1FFFFF (base 16) TECTOY S.A + Avenida Ministro Mário Andreazza, nº 4120, CEP 69075 - 830 - Manaus / AM – Brasil, CNPJ: 22.770.366/0001-82 + Manaus Manaus 69075 - 830 + BR 58-76-07 (hex) INP Technologies Ltd A00000-AFFFFF (base 16) INP Technologies Ltd @@ -37205,12 +37409,6 @@ C00000-CFFFFF (base 16) Siemens Sensors & Communication Ltd. Dalian Liaoning 116023 CN -5C-5C-75 (hex) TECTOY S.A -100000-1FFFFF (base 16) TECTOY S.A - Avenida Ministro Mário Andreazza, nº 4120, CEP 69075 - 830 - Manaus / AM – Brasil, CNPJ: 22.770.366/0001-82 - Manaus Manaus 69075 - 830 - BR - 5C-5C-75 (hex) Ebet Systems 600000-6FFFFF (base 16) Ebet Systems 150 George St @@ -37229,17 +37427,23 @@ A00000-AFFFFF (base 16) Gateview Technologies Hsinchu County 302 TW +7C-BC-84 (hex) AUMOVIO France S.A.S. +400000-4FFFFF (base 16) AUMOVIO France S.A.S. + 1 AVENUE PAUL OURLIAC + TOULOUSE 31100 + FR + 58-AD-08 (hex) NEOiD 000000-0FFFFF (base 16) NEOiD Rua Germano Torres, 166 - CJ8, Carmo Belo Horizonte MG 30310-040 BR -7C-BC-84 (hex) AUMOVIO France S.A.S. -400000-4FFFFF (base 16) AUMOVIO France S.A.S. - 1 AVENUE PAUL OURLIAC - TOULOUSE 31100 - FR +B4-AB-F3 (hex) FrontGrade Technologies +700000-7FFFFF (base 16) FrontGrade Technologies + 2815 Newby Road SW + Huntsville AL 35805 + US 58-AD-08 (hex) Vanguard Protex Global 500000-5FFFFF (base 16) Vanguard Protex Global @@ -37247,17 +37451,29 @@ A00000-AFFFFF (base 16) Gateview Technologies Oldsmar FL 34677 US +60-15-9F (hex) MICRO-TEX PTE.LTD. +400000-4FFFFF (base 16) MICRO-TEX PTE.LTD. + 131B LORONG 1 TOA PAYOH, #10-544 + TOA PAYOH CREST 312131 + SG + B4-AB-F3 (hex) Shenzhen Quanzhixin Information Technology Co.,Ltd 500000-5FFFFF (base 16) Shenzhen Quanzhixin Information Technology Co.,Ltd Jinhuanyu Building,Xixiang street,Bao'an District Shenzhen Guangdong 518100 CN -B4-AB-F3 (hex) FrontGrade Technologies -700000-7FFFFF (base 16) FrontGrade Technologies - 2815 Newby Road SW - Huntsville AL 35805 - US +60-15-9F (hex) Voxai Technology Co.,Ltd. +200000-2FFFFF (base 16) Voxai Technology Co.,Ltd. + 1211 Dongfangkejidasha + Shenzhen Guangdong 518040 + CN + +60-15-9F (hex) yst +000000-0FFFFF (base 16) yst + Um al ramuol,Al fattan skytower + Dubai 123200 + AE 60-15-9F (hex) Hubei HanRui Jing Automotive Intelligent System Co.,Ltd 300000-3FFFFF (base 16) Hubei HanRui Jing Automotive Intelligent System Co.,Ltd @@ -37271,29 +37487,23 @@ B4-AB-F3 (hex) FrontGrade Technologies Beijing Beijing 101499 CN -60-15-9F (hex) Voxai Technology Co.,Ltd. -200000-2FFFFF (base 16) Voxai Technology Co.,Ltd. - 1211 Dongfangkejidasha - Shenzhen Guangdong 518040 +80-77-86 (hex) Demeas +300000-3FFFFF (base 16) Demeas + 2106, 21st Floor, Building D, Tsinghua Science Park, Keji 2nd Road, Hi-Tech Zone + xi'an shaanxi 710000 CN -60-15-9F (hex) yst -000000-0FFFFF (base 16) yst - Um al ramuol,Al fattan skytower - Dubai 123200 - AE - 60-15-9F (hex) Critical Loop 700000-7FFFFF (base 16) Critical Loop 4150 Donald Douglas Drive Long Beach CA 90808 US -60-15-9F (hex) MICRO-TEX PTE.LTD. -400000-4FFFFF (base 16) MICRO-TEX PTE.LTD. - 131B LORONG 1 TOA PAYOH, #10-544 - TOA PAYOH CREST 312131 - SG +80-77-86 (hex) Arc networks pvt ltd +E00000-EFFFFF (base 16) Arc networks pvt ltd + shop 10, Bhoomi Acres, phase 2, Wagbil road, hiranandani estate, thane west + Thane Maharashtra 400615 + IN 80-77-86 (hex) Cornerstone Technology (Shenzhen) Limited C00000-CFFFFF (base 16) Cornerstone Technology (Shenzhen) Limited @@ -37307,24 +37517,18 @@ C00000-CFFFFF (base 16) Cornerstone Technology (Shenzhen) Limited AHMEDABAD Gujarat 380054 IN -80-77-86 (hex) Demeas -300000-3FFFFF (base 16) Demeas - 2106, 21st Floor, Building D, Tsinghua Science Park, Keji 2nd Road, Hi-Tech Zone - xi'an shaanxi 710000 - CN - -80-77-86 (hex) Arc networks pvt ltd -E00000-EFFFFF (base 16) Arc networks pvt ltd - shop 10, Bhoomi Acres, phase 2, Wagbil road, hiranandani estate, thane west - Thane Maharashtra 400615 - IN - 08-3C-03 (hex) INNOS TECHNOLOGIES INC. 900000-9FFFFF (base 16) INNOS TECHNOLOGIES INC. 4711 Yonge Street, 10th Floor, North York, Canada North York Ontario M2N 6K8 CA +34-D7-F5 (hex) AIoTrust +800000-8FFFFF (base 16) AIoTrust + 66 Bd Niels Bohr + Villeurbanne Rhone 69100 + FR + 34-D7-F5 (hex) Lucy Electric Manufacturing and Technologies India Pvt Ltd C00000-CFFFFF (base 16) Lucy Electric Manufacturing and Technologies India Pvt Ltd Survey Number 26-30, Noorpura, Baska, Ta. HalolDist. Panchamahal @@ -37337,44 +37541,92 @@ C00000-CFFFFF (base 16) Lucy Electric Manufacturing and Technologies India Xiamen Fujian 350000 CN -34-D7-F5 (hex) AIoTrust -800000-8FFFFF (base 16) AIoTrust - 66 Bd Niels Bohr - Villeurbanne Rhone 69100 - FR - 6C-47-80 (hex) ZVK GmbH C00000-CFFFFF (base 16) ZVK GmbH Technologiecampus 2 Teisnach 94244 DE -6C-47-80 (hex) CARDIO SISTEMAS COMERCIAL E INDUSTRIAL LTDA -B00000-BFFFFF (base 16) CARDIO SISTEMAS COMERCIAL E INDUSTRIAL LTDA - AVENIDA PAULISTA, 509 ANDAR 1, 3, 21 E 22, CONJUNTOS 308, 309 E 310 - SAO PAULO SAO PAULO 01311910 - BR - -18-C3-E4 (hex) Duress Pty Ltd -200000-2FFFFF (base 16) Duress Pty Ltd - Floor 8, Unit 1/420 St Kilda Rd - Melbourne Victoria 3004 - AU - 18-C3-E4 (hex) Cascadia Motion LLC B00000-BFFFFF (base 16) Cascadia Motion LLC 7929 SW Burns Way, Ste F WILSONVILLE OR 97070 US +6C-47-80 (hex) CARDIO SISTEMAS COMERCIAL E INDUSTRIAL LTDA +B00000-BFFFFF (base 16) CARDIO SISTEMAS COMERCIAL E INDUSTRIAL LTDA + AVENIDA PAULISTA, 509 ANDAR 1, 3, 21 E 22, CONJUNTOS 308, 309 E 310 + SAO PAULO SAO PAULO 01311910 + BR + 6C-47-80 (hex) Monnit Corporation 500000-5FFFFF (base 16) Monnit Corporation 3400 S West Temple S Salt Lake UT 84115 US +18-C3-E4 (hex) Duress Pty Ltd +200000-2FFFFF (base 16) Duress Pty Ltd + Floor 8, Unit 1/420 St Kilda Rd + Melbourne Victoria 3004 + AU + 18-C3-E4 (hex) Proximus sp. z .o.o. 300000-3FFFFF (base 16) Proximus sp. z .o.o. ul. Piatkowska 163 Poznan 60-650 PL + +C4-82-72 (hex) Shanghai Smart Logic Technology Ltd. +800000-8FFFFF (base 16) Shanghai Smart Logic Technology Ltd. + Room 1010,No.2,Lane 288,Kangning Road,Jing'an IDistrict + shanghai shanghai 200020 + CN + +38-B1-4E (hex) QNION Co.,Ltd +800000-8FFFFF (base 16) QNION Co.,Ltd + 165, Jukdong-ro Yuseong-gu + Daejeon Daejeon 34127 + KR + +38-B1-4E (hex) Marssun +200000-2FFFFF (base 16) Marssun + 8 F., No. 13, Ln. 332, Sec. 2, Zhongshan Rd., Zhonghe Dist. + New Taipei 235 + TW + +C4-82-72 (hex) Melecs EWS GmbH +400000-4FFFFF (base 16) Melecs EWS GmbH + GZO-Technologiestrasse 1 + Siegendorf 7011 + AT + +38-B1-4E (hex) Amissiontech Co., Ltd +A00000-AFFFFF (base 16) Amissiontech Co., Ltd + No.8 Huafu Rd, Shangsha, Chang’an Town + Dongguan Guangdong 523843 + CN + +38-B1-4E (hex) Brookhaven National Laboratory +500000-5FFFFF (base 16) Brookhaven National Laboratory + 741 Brookhaven Ave + Upton NY 11973 + US + +20-B3-7F (hex) Aina Computers ,Inc. +200000-2FFFFF (base 16) Aina Computers ,Inc. + 16192 Coastal Highway + Lewes DE 19958 + US + +F4-A4-54 (hex) TRI WORKS +200000-2FFFFF (base 16) TRI WORKS + Kiyokawa place 3F 1-14-18 kiyokawa Chuo-ku + Fukuoka-shi Fukuoka 810-0041 + JP + +20-B3-7F (hex) TDK-Lambda UK +100000-1FFFFF (base 16) TDK-Lambda UK + Kingsley Avenue + Ilfracombe Devon EX348ES + GB diff --git a/hwdb.d/ma-small.txt b/hwdb.d/ma-small.txt index bf3496a60810e..324f86bb3e100 100644 --- a/hwdb.d/ma-small.txt +++ b/hwdb.d/ma-small.txt @@ -7787,6 +7787,12 @@ DD2000-DD2FFF (base 16) SHIELD-CCTV CO.,LTD. Poway CA 92064 US +8C-1F-64 (hex) Taicang T&W Electronics +FFB000-FFBFFF (base 16) Taicang T&W Electronics + 89# Jiang Nan RD + Suzhou Jiangsu 215412 + CN + 8C-1F-64 (hex) Shenzhen zhushida Technology lnformation Co.,Ltd A5D000-A5DFFF (base 16) Shenzhen zhushida Technology lnformation Co.,Ltd 701, Building D, Zone B, Junxing Industrial Zone, Junxing Industrial Zone, Oyster Road, Zhancheng Community, Fuhai Street, @@ -7799,11 +7805,11 @@ A5D000-A5DFFF (base 16) Shenzhen zhushida Technology lnformation Co.,Ltd SHENZHEN Bao'an District 518000 CN -8C-1F-64 (hex) Taicang T&W Electronics -FFB000-FFBFFF (base 16) Taicang T&W Electronics - 89# Jiang Nan RD - Suzhou Jiangsu 215412 - CN +8C-1F-64 (hex) Oriux +20A000-20AFFF (base 16) Oriux + 5825 N. Sam Houston Pkwy WSuite 220 Houston TX 77086 United S + Houston TX 77086 + US 8C-1F-64 (hex) Breas Medical AB 5A1000-5A1FFF (base 16) Breas Medical AB @@ -7811,36 +7817,18 @@ FFB000-FFBFFF (base 16) Taicang T&W Electronics Mölnlycke SE-435 33 SE -8C-1F-64 (hex) Oriux -20A000-20AFFF (base 16) Oriux - 5825 N. Sam Houston Pkwy WSuite 220 Houston TX 77086 United S - Houston TX 77086 - US - 8C-1F-64 (hex) ENBIK Technology Co., Ltd 85A000-85AFFF (base 16) ENBIK Technology Co., Ltd 2F., No.542, Sec. 1, Minsheng N. Rd., Taoyuan City Taoyuan City 333016 TW -8C-1F-64 (hex) ibg Prüfcomputer GmbH -627000-627FFF (base 16) ibg Prüfcomputer GmbH - Pretzfelder Str. 27 - Ebermannstadt 91320 - DE - 8C-1F-64 (hex) Amazon Robotics MTAC Matrix NPI DC4000-DC4FFF (base 16) Amazon Robotics MTAC Matrix NPI 50 Otis Street Westborough MA 01581 US -8C-1F-64 (hex) Potter Electric Signal Co. LLC -316000-316FFF (base 16) Potter Electric Signal Co. LLC - 1609 Park 370 Place - Hazelwood MO 63042 - US - 8C-1F-64 (hex) Eiden Co.,Ltd. 3D7000-3D7FFF (base 16) Eiden Co.,Ltd. 2-7-1 kurigi,asao-ku,kawasaki-shi @@ -7853,6 +7841,18 @@ C09000-C09FFF (base 16) S.E.I. CO.,LTD. Izunokuni Shizuoka 4102133 JP +8C-1F-64 (hex) ibg Prüfcomputer GmbH +627000-627FFF (base 16) ibg Prüfcomputer GmbH + Pretzfelder Str. 27 + Ebermannstadt 91320 + DE + +8C-1F-64 (hex) Potter Electric Signal Co. LLC +316000-316FFF (base 16) Potter Electric Signal Co. LLC + 1609 Park 370 Place + Hazelwood MO 63042 + US + 8C-1F-64 (hex) Boon Arthur Engineering Pte Ltd D8A000-D8AFFF (base 16) Boon Arthur Engineering Pte Ltd 629 Aljunied Road #06-06 Cititech Industrial Building @@ -7878,31 +7878,31 @@ C74000-C74FFF (base 16) Nippon Techno Lab Inc JP 70-B3-D5 (hex) Aplex Technology Inc. -986000-986FFF (base 16) Aplex Technology Inc. - 15F-1, No.186, Jian Yi Road +9B1000-9B1FFF (base 16) Aplex Technology Inc. + 15F-1, No.186, Jian Yi Road Zhonghe District New Taipei City 235 - TW 70-B3-D5 (hex) Aplex Technology Inc. -605000-605FFF (base 16) Aplex Technology Inc. +F00000-F00FFF (base 16) Aplex Technology Inc. 15F-1, No.186, Jian Yi Road Zhonghe District New Taipei City 235 - TW 70-B3-D5 (hex) Aplex Technology Inc. -F00000-F00FFF (base 16) Aplex Technology Inc. - 15F-1, No.186, Jian Yi Road +986000-986FFF (base 16) Aplex Technology Inc. + 15F-1, No.186, Jian Yi Road Zhonghe District New Taipei City 235 - TW 70-B3-D5 (hex) Aplex Technology Inc. -2EE000-2EEFFF (base 16) Aplex Technology Inc. +605000-605FFF (base 16) Aplex Technology Inc. 15F-1, No.186, Jian Yi Road Zhonghe District New Taipei City 235 - TW 70-B3-D5 (hex) Aplex Technology Inc. -9B1000-9B1FFF (base 16) Aplex Technology Inc. +2EE000-2EEFFF (base 16) Aplex Technology Inc. 15F-1, No.186, Jian Yi Road Zhonghe District New Taipei City 235 - TW @@ -7931,23 +7931,17 @@ CCF000-CCFFFF (base 16) Tiptop Platform P. Ltd Jaipur Rajasthan 302001 IN -8C-1F-64 (hex) Taesung Media -8DB000-8DBFFF (base 16) Taesung Media - Room 20, 306, Dalseo-daero 109-gil - Dalseo-gu Daegu 42709 - KR - 8C-1F-64 (hex) Thermaco Incorporated F20000-F20FFF (base 16) Thermaco Incorporated 646 GREENSBORO ST ASHEBORO NC 27203-4739 US -8C-1F-64 (hex) Kite Rise Technologies GmbH -508000-508FFF (base 16) Kite Rise Technologies GmbH - Kaerntner Strasse 355B/1.OG - Graz 8054 - AT +8C-1F-64 (hex) Taesung Media +8DB000-8DBFFF (base 16) Taesung Media + Room 20, 306, Dalseo-daero 109-gil + Dalseo-gu Daegu 42709 + KR 8C-1F-64 (hex) Omnilink Tecnologia S/A 023000-023FFF (base 16) Omnilink Tecnologia S/A @@ -7955,11 +7949,11 @@ F20000-F20FFF (base 16) Thermaco Incorporated Barueri SP 06455-020 BR -8C-1F-64 (hex) GETQCALL -37A000-37AFFF (base 16) GETQCALL - 23 Lorraine Drive - North York ON M2N6Z6 - CA +8C-1F-64 (hex) Kite Rise Technologies GmbH +508000-508FFF (base 16) Kite Rise Technologies GmbH + Kaerntner Strasse 355B/1.OG + Graz 8054 + AT 8C-1F-64 (hex) Vinfast Trading and Production JSC F80000-F80FFF (base 16) Vinfast Trading and Production JSC @@ -7967,12 +7961,24 @@ F80000-F80FFF (base 16) Vinfast Trading and Production JSC Hai Phong Hai Phong 180000 VN +8C-1F-64 (hex) GETQCALL +37A000-37AFFF (base 16) GETQCALL + 23 Lorraine Drive + North York ON M2N6Z6 + CA + 8C-1F-64 (hex) YDIIT Co., Ltd. 1FA000-1FAFFF (base 16) YDIIT Co., Ltd. #3010, U-Tower, 120, Heungdeokjungang-ro, Giheung-gu, Yongin Gyeonggi 16950 KR +8C-1F-64 (hex) AUREKA SMART LIVING W.L.L +689000-689FFF (base 16) AUREKA SMART LIVING W.L.L + Office 22, Bldg 288C, Avenue 16, Hidd + Hidd 0111 + BH + 8C-1F-64 (hex) Embedded Designs Services India Pvt Ltd CD7000-CD7FFF (base 16) Embedded Designs Services India Pvt Ltd Unit No. 1119-20, 11th Floor, Tower 5, 12th Avenue, RPS Infinia, Faridabad, Haryana @@ -7985,11 +7991,11 @@ CD7000-CD7FFF (base 16) Embedded Designs Services India Pvt Ltd 수원 Gyeonggi-do 16690 KR -8C-1F-64 (hex) AUREKA SMART LIVING W.L.L -689000-689FFF (base 16) AUREKA SMART LIVING W.L.L - Office 22, Bldg 288C, Avenue 16, Hidd - Hidd 0111 - BH +8C-1F-64 (hex) Sichuan ZhikongLingxin Technology Co., Ltd. +17A000-17AFFF (base 16) Sichuan ZhikongLingxin Technology Co., Ltd. + No. 22, 5th Floor, Unit 1, Building 3, No.666 Guandong 1st Street, Chengdu Hightech Zone, China (Sichuan) Pilot Free Trade Zone. + Chengdu Sichuan 610095 + CN 8C-1F-64 (hex) Beijing Dangong Technology Co., Ltd DB8000-DB8FFF (base 16) Beijing Dangong Technology Co., Ltd @@ -8003,11 +8009,11 @@ CC9000-CC9FFF (base 16) Benchmark Electronics BV Almelo Overijssel 7602 EA NL -8C-1F-64 (hex) Sichuan ZhikongLingxin Technology Co., Ltd. -17A000-17AFFF (base 16) Sichuan ZhikongLingxin Technology Co., Ltd. - No. 22, 5th Floor, Unit 1, Building 3, No.666 Guandong 1st Street, Chengdu Hightech Zone, China (Sichuan) Pilot Free Trade Zone. - Chengdu Sichuan 610095 - CN +8C-1F-64 (hex) Raspberry Pi (Trading) Ltd +34A000-34AFFF (base 16) Raspberry Pi (Trading) Ltd + Maurice Wilkes Building, St Johns Innovation Park + Cambridge Cambridgeshire CB4 0DS + GB 8C-1F-64 (hex) WHITEBOX TECHNOLOGY HONG KONG LTD E2A000-E2AFFF (base 16) WHITEBOX TECHNOLOGY HONG KONG LTD @@ -8015,12 +8021,6 @@ E2A000-E2AFFF (base 16) WHITEBOX TECHNOLOGY HONG KONG LTD Wan Chai Hong Kong Hong Kong HK -8C-1F-64 (hex) Raspberry Pi (Trading) Ltd -34A000-34AFFF (base 16) Raspberry Pi (Trading) Ltd - Maurice Wilkes Building, St Johns Innovation Park - Cambridge Cambridgeshire CB4 0DS - GB - 8C-1F-64 (hex) Invader Technologies Pvt Ltd 859000-859FFF (base 16) Invader Technologies Pvt Ltd 4th Floor, Landmark TowerPlot No -2, Ashok Marg, Silokhra, South City Part 1 @@ -8039,36 +8039,36 @@ C70000-C70FFF (base 16) INVIXIUM ACCESS INC Toronto Ontario M2H 3R1 CA -8C-1F-64 (hex) Potter Electric Signal Co. LLC -8C8000-8C8FFF (base 16) Potter Electric Signal Co. LLC - 1609 Park 370 Place - Hazelwood MO 63042 - US - 8C-1F-64 (hex) Televic Rail GmbH 9D1000-9D1FFF (base 16) Televic Rail GmbH Teltowkanalstr.1 Berlin 12247 DE +8C-1F-64 (hex) Potter Electric Signal Co. LLC +8C8000-8C8FFF (base 16) Potter Electric Signal Co. LLC + 1609 Park 370 Place + Hazelwood MO 63042 + US + 8C-1F-64 (hex) Kuntu Technology Limited Liability Compant 7CC000-7CCFFF (base 16) Kuntu Technology Limited Liability Compant Presnensky vet municipal district,Presnenskaya emb., 12,room. 10/45 Moscow Select State 123112 RU -8C-1F-64 (hex) VMA GmbH -783000-783FFF (base 16) VMA GmbH - Graefinauer Strasse 2 - Ilmenau 98693 - DE - 8C-1F-64 (hex) VORTIX NETWORKS 96F000-96FFFF (base 16) VORTIX NETWORKS 3230 E Imperial Hwy, Suite 300 Brea CA 92821 US +8C-1F-64 (hex) VMA GmbH +783000-783FFF (base 16) VMA GmbH + Graefinauer Strasse 2 + Ilmenau 98693 + DE + 8C-1F-64 (hex) 浙江红谱科技有限公司 6DA000-6DAFFF (base 16) 浙江红谱科技有限公司 紫宣路18号西投绿城·浙谷深蓝中心7号楼7楼红谱科技 @@ -8093,11 +8093,11 @@ DB4000-DB4FFF (base 16) MB connect line GmbH Anif Salzburg 5081 AT -8C-1F-64 (hex) Abbott Diagnostics Technologies AS -7F6000-7F6FFF (base 16) Abbott Diagnostics Technologies AS - P. O. Box 6863 Rodeløkka - Oslo Oslo 0504 - NO +8C-1F-64 (hex) TECHTUIT CO.,LTD. +2D6000-2D6FFF (base 16) TECHTUIT CO.,LTD. + 1-4-28,MITA,26F MITA KOKUSAIBLDG, + MINATO-KU TOKYO 108-0073 + JP 8C-1F-64 (hex) SEGRON Automation, s.r.o. DC1000-DC1FFF (base 16) SEGRON Automation, s.r.o. @@ -8105,17 +8105,11 @@ DC1000-DC1FFF (base 16) SEGRON Automation, s.r.o. Bratislava 82101 SK -8C-1F-64 (hex) TECHTUIT CO.,LTD. -2D6000-2D6FFF (base 16) TECHTUIT CO.,LTD. - 1-4-28,MITA,26F MITA KOKUSAIBLDG, - MINATO-KU TOKYO 108-0073 - JP - -8C-1F-64 (hex) Zengar Institute Inc -710000-710FFF (base 16) Zengar Institute Inc - 1007 Fort St, 4th FL - Victoria BC V8V 3K5 - CA +8C-1F-64 (hex) Abbott Diagnostics Technologies AS +7F6000-7F6FFF (base 16) Abbott Diagnostics Technologies AS + P. O. Box 6863 Rodeløkka + Oslo Oslo 0504 + NO 8C-1F-64 (hex) RESMED PTY LTD 3C7000-3C7FFF (base 16) RESMED PTY LTD @@ -8123,6 +8117,12 @@ DC1000-DC1FFF (base 16) SEGRON Automation, s.r.o. NSW 2153 AT +8C-1F-64 (hex) Zengar Institute Inc +710000-710FFF (base 16) Zengar Institute Inc + 1007 Fort St, 4th FL + Victoria BC V8V 3K5 + CA + 8C-1F-64 (hex) Creating Cloud Technology Co.,Ltd.,CT-CLOUD C9D000-C9DFFF (base 16) Creating Cloud Technology Co.,Ltd.,CT-CLOUD Rm. 3, 16F., No. 925, Sec. 4, Taiwan Blvd., Xitun Dist. @@ -8135,12 +8135,6 @@ C9D000-C9DFFF (base 16) Creating Cloud Technology Co.,Ltd.,CT-CLOUD Gent Oost-Vlaanderen 9000 BE -8C-1F-64 (hex) ZKTECO EUROPE -734000-734FFF (base 16) ZKTECO EUROPE - CARRETERA DE FUENCARRAL 44 - ALCOBENDAS MADRID 28108 - ES - 8C-1F-64 (hex) Network Rail 18A000-18AFFF (base 16) Network Rail The Quadrant, Elder Gate @@ -8153,6 +8147,12 @@ C9D000-C9DFFF (base 16) Creating Cloud Technology Co.,Ltd.,CT-CLOUD Neusaess Bayern 85356 DE +8C-1F-64 (hex) ZKTECO EUROPE +734000-734FFF (base 16) ZKTECO EUROPE + CARRETERA DE FUENCARRAL 44 + ALCOBENDAS MADRID 28108 + ES + 8C-1F-64 (hex) inmediQ GmbH 6C4000-6C4FFF (base 16) inmediQ GmbH Gebrüder-Freitag-Str. 1 @@ -8171,23 +8171,17 @@ C9D000-C9DFFF (base 16) Creating Cloud Technology Co.,Ltd.,CT-CLOUD Hanoi 151831 VN -8C-1F-64 (hex) PAL Inc. -60C000-60CFFF (base 16) PAL Inc. - 2217-2 Hayashicho - Takamatsu Kagawa 7610301 - JP - 8C-1F-64 (hex) Watthour Engineering Co., Inc. B0E000-B0EFFF (base 16) Watthour Engineering Co., Inc. 333 Crosspark Dr Pearl MS 39208 US -8C-1F-64 (hex) LaserLinc, Inc. -04D000-04DFFF (base 16) LaserLinc, Inc. - 777 Zapata Drive - Fairborn OH 45324 - US +8C-1F-64 (hex) PAL Inc. +60C000-60CFFF (base 16) PAL Inc. + 2217-2 Hayashicho + Takamatsu Kagawa 7610301 + JP 8C-1F-64 (hex) Xi'an Singularity Energy Co., Ltd. 2AA000-2AAFFF (base 16) Xi'an Singularity Energy Co., Ltd. @@ -8201,6 +8195,12 @@ B0E000-B0EFFF (base 16) Watthour Engineering Co., Inc. Suzhou City Jiangsu 215000 CN +8C-1F-64 (hex) LaserLinc, Inc. +04D000-04DFFF (base 16) LaserLinc, Inc. + 777 Zapata Drive + Fairborn OH 45324 + US + 8C-1F-64 (hex) Meisol Co., Ltd. 827000-827FFF (base 16) Meisol Co., Ltd. Yamato Jisho Building 1006, 74-1 Yamashitacho, Naka-ku @@ -8225,6 +8225,36 @@ B0E000-B0EFFF (base 16) Watthour Engineering Co., Inc. Nashua 03062 US +8C-1F-64 (hex) Sensata Technologies Inc. +DA0000-DA0FFF (base 16) Sensata Technologies Inc. + 529 Pleasant Street + Attleboro MA 02703 + US + +8C-1F-64 (hex) Hiwin Mikrosystem Corp. +216000-216FFF (base 16) Hiwin Mikrosystem Corp. + NO 6 JINGKE CENTRAL RD TAICHUNG CITY TAIWAN 40841 + TAICHUNG 40841 + TW + +8C-1F-64 (hex) JES Electronic Systems Private Limited +976000-976FFF (base 16) JES Electronic Systems Private Limited + 9/52/5 Kirti Nagar, near industrial area, New Delhi 110015 + New Delhi 110015 + IN + +8C-1F-64 (hex) Starts Facility Service Co.,Ltd +AC6000-AC6FFF (base 16) Starts Facility Service Co.,Ltd + 3-1-8 Nihonbashi + Chuo-ku Tokyo 103-0027 + JP + +8C-1F-64 (hex) ARKTRON ELECTRONICS +D6F000-D6FFFF (base 16) ARKTRON ELECTRONICS + PLOT NO-605,SECTOR-58 + FARIDABAD HARYANA 121004 + IN + 8C-1F-64 (hex) Jacobs Technology, Inc. A98000-A98FFF (base 16) Jacobs Technology, Inc. 7765 Old Telegraph Road @@ -15983,18 +16013,18 @@ F03000-F03FFF (base 16) Faust ApS Helsinki 00150 FI -8C-1F-64 (hex) NARI TECH Co., Ltd -888000-888FFF (base 16) NARI TECH Co., Ltd - 947, Hanam-daero - Hanam-si Gyeonggi-do 12982 - KR - 8C-1F-64 (hex) Mitsubishi Electric System & Service Co., Ltd. E05000-E05FFF (base 16) Mitsubishi Electric System & Service Co., Ltd. 1-26-43 Yada, Higashi-ku, Nagoya Aichi 461-0040 JP +8C-1F-64 (hex) NARI TECH Co., Ltd +888000-888FFF (base 16) NARI TECH Co., Ltd + 947, Hanam-daero + Hanam-si Gyeonggi-do 12982 + KR + 8C-1F-64 (hex) NSK Co.,Ltd. 2A3000-2A3FFF (base 16) NSK Co.,Ltd. 1-10-15 Daiko,Higashi-ku @@ -16019,36 +16049,36 @@ B41000-B41FFF (base 16) STATE GRID INTELLIGENCE TECHNOLOGY CO.,LTD. Muenster North Rhine-Westphalia 48163 DE -8C-1F-64 (hex) BK LAB -F8C000-F8CFFF (base 16) BK LAB - #1309, Daeryung Technotown 15, Simin-daero 401, Dongan-gu - Anyang-si Gyonggi-do 14057 - KR - 8C-1F-64 (hex) TECZZ LLC D95000-D95FFF (base 16) TECZZ LLC 17 Forest AvenueSuite 017 Fond Du Lac WI 54935 US +8C-1F-64 (hex) BK LAB +F8C000-F8CFFF (base 16) BK LAB + #1309, Daeryung Technotown 15, Simin-daero 401, Dongan-gu + Anyang-si Gyonggi-do 14057 + KR + 8C-1F-64 (hex) Potter Electric Signal Co. LLC 57B000-57BFFF (base 16) Potter Electric Signal Co. LLC 1609 Park 370 Place Hazelwood MO 63042 US -8C-1F-64 (hex) Potter Electric Signal Co. LLC -442000-442FFF (base 16) Potter Electric Signal Co. LLC - 1609 Park 370 Place - Hazelwood MO 63043 - US - 8C-1F-64 (hex) Potter Electric Signal Co. LLC 8FE000-8FEFFF (base 16) Potter Electric Signal Co. LLC 5757 Phantom Drive Hazelwood MO 63042 US +8C-1F-64 (hex) Potter Electric Signal Co. LLC +442000-442FFF (base 16) Potter Electric Signal Co. LLC + 1609 Park 370 Place + Hazelwood MO 63043 + US + 8C-1F-64 (hex) Eyecloud, Inc 072000-072FFF (base 16) Eyecloud, Inc 171 Branham Ln, Ste 10-243 @@ -16073,15 +16103,15 @@ D95000-D95FFF (base 16) TECZZ LLC Hangzhou 310024 CN +8C-1F-64 (hex) Private +1A8000-1A8FFF (base 16) Private + 8C-1F-64 (hex) Shenzhen Arctec Innovation Technology Co.,Ltd 199000-199FFF (base 16) Shenzhen Arctec Innovation Technology Co.,Ltd Room711-713, Yuefu Square, No.481, Fenghuang Street, Guangming Area Shenzhen Guangdong 518107 CN -8C-1F-64 (hex) Private -1A8000-1A8FFF (base 16) Private - 70-B3-D5 (hex) Aplex Technology Inc. 4B7000-4B7FFF (base 16) Aplex Technology Inc. 15F-1, No.186, Jian Yi Road @@ -16181,11 +16211,11 @@ DA4000-DA4FFF (base 16) Foenix Coding Ltd Chertsey Surrey KT16 0AW GB -8C-1F-64 (hex) Rail Telematics Corp -7C1000-7C1FFF (base 16) Rail Telematics Corp - 494 6th Ave S - Jacksonville Beach 32250 - US +8C-1F-64 (hex) YUYAMA MFG Co.,Ltd +70A000-70AFFF (base 16) YUYAMA MFG Co.,Ltd + 1-4-30 + MEISHINGUCHI,TOYONAKA OSAKA 561-0841 + JP 8C-1F-64 (hex) Vigor Electric Corp. A64000-A64FFF (base 16) Vigor Electric Corp. @@ -16193,11 +16223,11 @@ A64000-A64FFF (base 16) Vigor Electric Corp. Danshui Dist. New Taipei City 25152 TW -8C-1F-64 (hex) YUYAMA MFG Co.,Ltd -70A000-70AFFF (base 16) YUYAMA MFG Co.,Ltd - 1-4-30 - MEISHINGUCHI,TOYONAKA OSAKA 561-0841 - JP +8C-1F-64 (hex) Rail Telematics Corp +7C1000-7C1FFF (base 16) Rail Telematics Corp + 494 6th Ave S + Jacksonville Beach 32250 + US 8C-1F-64 (hex) Sonic Italia 8D2000-8D2FFF (base 16) Sonic Italia @@ -16235,12 +16265,6 @@ B30000-B30FFF (base 16) Fujian ONETHING Technology Co.,Ltd. Antwerp Antwerp 2018 BE -8C-1F-64 (hex) VINGLOOP TECHNOLOGY LTD -DFF000-DFFFFF (base 16) VINGLOOP TECHNOLOGY LTD - UNIT 910.9/F.TOWER 1 CHEUNGSHA WAN PLAZA 833 CHEUNG SHA WAN RD CHEUNG SHA WAN - Hong Kong 000000 - HK - 8C-1F-64 (hex) Inex Technologies C46000-C46FFF (base 16) Inex Technologies 155 Willowbrook Blvd., Suite 130 @@ -16253,6 +16277,12 @@ C46000-C46FFF (base 16) Inex Technologies Troy MI 48083 US +8C-1F-64 (hex) VINGLOOP TECHNOLOGY LTD +DFF000-DFFFFF (base 16) VINGLOOP TECHNOLOGY LTD + UNIT 910.9/F.TOWER 1 CHEUNGSHA WAN PLAZA 833 CHEUNG SHA WAN RD CHEUNG SHA WAN + Hong Kong 000000 + HK + 8C-1F-64 (hex) JMV BHARAT PRIVATE LIMITED 961000-961FFF (base 16) JMV BHARAT PRIVATE LIMITED W 50, SECTOR 11, NOIDA, GAUTAM BUDDHA NAGAR @@ -16277,18 +16307,18 @@ B25000-B25FFF (base 16) Thermo Fisher Scientific (Asheville) LLC Geumcheon-gu, Seoul Select State 08592 KR -8C-1F-64 (hex) TCL OPERATIONS POLSKA SP. Z O.O. -233000-233FFF (base 16) TCL OPERATIONS POLSKA SP. Z O.O. - ul. MICKIEWICZA, 31/41, 96-300, ZYRARDOW, POLAN - ZYRARDOW 96-300 - PL - 8C-1F-64 (hex) Eurotronic Technology GmbH E27000-E27FFF (base 16) Eurotronic Technology GmbH Südweg 1 Steinau 36396 DE +8C-1F-64 (hex) TCL OPERATIONS POLSKA SP. Z O.O. +233000-233FFF (base 16) TCL OPERATIONS POLSKA SP. Z O.O. + ul. MICKIEWICZA, 31/41, 96-300, ZYRARDOW, POLAN + ZYRARDOW 96-300 + PL + 8C-1F-64 (hex) Monnit Corporation A28000-A28FFF (base 16) Monnit Corporation 3400 S West Temple @@ -16325,6 +16355,12 @@ EB8000-EB8FFF (base 16) Power Electronics Espana, S.L. Guildford Surrey GU2 7RQ GB +8C-1F-64 (hex) Sentek Pty Ltd +A95000-A95FFF (base 16) Sentek Pty Ltd + 77 Magill Road + Stepney SA 5069 + AU + 8C-1F-64 (hex) FIBERNET LTD F48000-F48FFF (base 16) FIBERNET LTD 9 Hakidma st. Hi-Tech City Park, @@ -16337,12 +16373,6 @@ F48000-F48FFF (base 16) FIBERNET LTD Shanghai Shanghai 201206 CN -8C-1F-64 (hex) Sentek Pty Ltd -A95000-A95FFF (base 16) Sentek Pty Ltd - 77 Magill Road - Stepney SA 5069 - AU - 8C-1F-64 (hex) Aidhom B1E000-B1EFFF (base 16) Aidhom Avenue de la résistance 188 @@ -16397,22 +16427,22 @@ F21000-F21FFF (base 16) nanoTRONIX Computing Inc. Wilmington DE 19806 US -8C-1F-64 (hex) Fairwinds Technologies -D55000-D55FFF (base 16) Fairwinds Technologies - 6165 Guardian Gateway, Suites A-C - Aberdeen Proving Ground MD 21005 - US - 8C-1F-64 (hex) RADIC Technologies, Inc. E91000-E91FFF (base 16) RADIC Technologies, Inc. 1625 The Alameda, Suite 708 SAN JOSE 95126 US -8C-1F-64 (hex) Potter Electric Signal Co. LLC -9AD000-9ADFFF (base 16) Potter Electric Signal Co. LLC - 1609 Park 370 Place - Hazelwood MO 63043 +8C-1F-64 (hex) DEUTA Werke GmbH +02A000-02AFFF (base 16) DEUTA Werke GmbH + ET + Bergisch Gladbach NRW 51465 + DE + +8C-1F-64 (hex) Fairwinds Technologies +D55000-D55FFF (base 16) Fairwinds Technologies + 6165 Guardian Gateway, Suites A-C + Aberdeen Proving Ground MD 21005 US 8C-1F-64 (hex) Microchip Technologies Inc @@ -16421,11 +16451,11 @@ BEA000-BEAFFF (base 16) Microchip Technologies Inc Chandler AZ 85224-6199 US -8C-1F-64 (hex) DEUTA Werke GmbH -02A000-02AFFF (base 16) DEUTA Werke GmbH - ET - Bergisch Gladbach NRW 51465 - DE +8C-1F-64 (hex) Potter Electric Signal Co. LLC +9AD000-9ADFFF (base 16) Potter Electric Signal Co. LLC + 1609 Park 370 Place + Hazelwood MO 63043 + US 8C-1F-64 (hex) RC Systems 1E9000-1E9FFF (base 16) RC Systems @@ -16439,12 +16469,6 @@ AAC000-AACFFF (base 16) CDR SRL Ginestra Fiorentina Florence/Italy 50055 IT -8C-1F-64 (hex) INVENTIA Sp. z o.o. -E50000-E50FFF (base 16) INVENTIA Sp. z o.o. - Poleczki 23 - Warszawa Mazowieckie 02-822 - PL - 8C-1F-64 (hex) LimeSoft Co., Ltd. 3DF000-3DFFFF (base 16) LimeSoft Co., Ltd. 40 Imi-ro, A-816 @@ -16457,6 +16481,12 @@ E50000-E50FFF (base 16) INVENTIA Sp. z o.o. Broomfield CO 80021 US +8C-1F-64 (hex) INVENTIA Sp. z o.o. +E50000-E50FFF (base 16) INVENTIA Sp. z o.o. + Poleczki 23 + Warszawa Mazowieckie 02-822 + PL + 8C-1F-64 (hex) Engage Technologies F1E000-F1EFFF (base 16) Engage Technologies 7041 Boone Avenue North @@ -16487,18 +16517,18 @@ AD6000-AD6FFF (base 16) INTERNATIONAL SECURITY SYSTEMS W.L.L. Rotterdam 3233 KK NL -8C-1F-64 (hex) Ocarina -6A1000-6A1FFF (base 16) Ocarina - 29 Skelwith Road - London W6 9EX - GB - 8C-1F-64 (hex) SungjinDSP Co., LTD 0BA000-0BAFFF (base 16) SungjinDSP Co., LTD 810, 25 Gasan Digital 1-ro, Geumcheon-gu, Seoul (Gasan-dong, Daeryung Techno Town 17th) Geumcheon-gu Seoul 08594 KR +8C-1F-64 (hex) Ocarina +6A1000-6A1FFF (base 16) Ocarina + 29 Skelwith Road + London W6 9EX + GB + 8C-1F-64 (hex) CyberCube ApS 65C000-65CFFF (base 16) CyberCube ApS Munkehatten 1C @@ -16511,30 +16541,24 @@ AD6000-AD6FFF (base 16) INTERNATIONAL SECURITY SYSTEMS W.L.L. Charlottesville VA 22911 US -8C-1F-64 (hex) YUYAMA MFG Co.,Ltd -EAF000-EAFFFF (base 16) YUYAMA MFG Co.,Ltd - 1-4-30 - MEISHINGUCHI,TOYONAKA OSAKA 561-0841 - JP - 8C-1F-64 (hex) MB connect line GmbH 075000-075FFF (base 16) MB connect line GmbH Winnettener Strasse 6 Dinkelsbuehl Bavaria 91550 DE +8C-1F-64 (hex) YUYAMA MFG Co.,Ltd +EAF000-EAFFFF (base 16) YUYAMA MFG Co.,Ltd + 1-4-30 + MEISHINGUCHI,TOYONAKA OSAKA 561-0841 + JP + 8C-1F-64 (hex) Bright Solutions PTE LTD 6C3000-6C3FFF (base 16) Bright Solutions PTE LTD 51 Goldhill Plaza #07-10/11 Singapore 308900 SG -8C-1F-64 (hex) Sensus -052000-052FFF (base 16) Sensus - Industriestr. 16 - Ludwigshafen 67063 - DE - 8C-1F-64 (hex) AvanTimes 030000-030FFF (base 16) AvanTimes Kuipersweg 2 @@ -16547,6 +16571,12 @@ FBB000-FBBFFF (base 16) Telica Uiwang-si Gyeonggi-do 16006 KR +8C-1F-64 (hex) Sensus +052000-052FFF (base 16) Sensus + Industriestr. 16 + Ludwigshafen 67063 + DE + 8C-1F-64 (hex) vtt systems Inc. A66000-A66FFF (base 16) vtt systems Inc. 8 THE GREEN @@ -16556,6 +16586,60 @@ A66000-A66FFF (base 16) vtt systems Inc. 8C-1F-64 (hex) Private D26000-D26FFF (base 16) Private +8C-1F-64 (hex) Breas Medical AB +1C5000-1C5FFF (base 16) Breas Medical AB + Företagsvägen 1 + Mölnlycke SE-435 33 + SE + +8C-1F-64 (hex) CloudRAN.ai +522000-522FFF (base 16) CloudRAN.ai + 12 WOODLANDS SQUARE, #10-73, WOODS, SQUARE, SINGAPORE (737715) + Singapore Singapore 737715 + CN + +8C-1F-64 (hex) Pneumax Spa +5FE000-5FEFFF (base 16) Pneumax Spa + via cascina barbellina, 10 + Lurano Bergamo 24050 + IT + +8C-1F-64 (hex) HEITEC AG +E25000-E25FFF (base 16) HEITEC AG + Dr.-Otto-Leich-Str. 16 + Eckental Bavaria 90542 + DE + +8C-1F-64 (hex) Erba Lachema s.r.o. +BCF000-BCFFFF (base 16) Erba Lachema s.r.o. + Karasek1d + Brno 62100 + CZ + +8C-1F-64 (hex) MITSUBISHI ELECTRIC INDIA PVT. LTD. +FF2000-FF2FFF (base 16) MITSUBISHI ELECTRIC INDIA PVT. LTD. + Plot No B-3, Talegaon Industrial Area,Phase-II, Badhalwadi MIDC, Talegoan,, + Pune Maharashtra 410507 + IN + +8C-1F-64 (hex) BCMTECH +17F000-17FFFF (base 16) BCMTECH + A-1605Ho,Anyang-dong 1432,Manan-gu + Anyang-si Gyeonggi-do 14084 + KR + +8C-1F-64 (hex) PERSOL EXCEL HR PARTNERS CO., LTD. +46B000-46BFFF (base 16) PERSOL EXCEL HR PARTNERS CO., LTD. + 1-6-1-B1 Awaza, Nishi-ku + Osaka City Osaka Prefecture 550-0011 + JP + +8C-1F-64 (hex) Owl Home Inc. +51E000-51EFFF (base 16) Owl Home Inc. + SE #82363, 1-1100 Courtneypark Dr E + Mississauga Ontario L5T1L7 + CA + 8C-1F-64 (hex) Vision Systems Safety Tech E6F000-E6FFFF (base 16) Vision Systems Safety Tech 5 Chemin de Chiradie @@ -22319,12 +22403,6 @@ D9C000-D9CFFF (base 16) Subinitial LLC South San Francisco CA 94080 US -70-B3-D5 (hex) HOERMANN GmbH -B78000-B78FFF (base 16) HOERMANN GmbH - Hauptstr. 45-47 - Kirchseeon Bavaria 85614 - DE - 70-B3-D5 (hex) Private D0A000-D0AFFF (base 16) Private @@ -24272,6 +24350,12 @@ C36000-C36FFF (base 16) ODTech Co., Ltd. Wanju_gun Jeonbuk-do 55322 KR +8C-1F-64 (hex) Tecsys do Brasil Industrial Ltda +7D0000-7D0FFF (base 16) Tecsys do Brasil Industrial Ltda + Rua Oros, 146 + Sao Jose dos Campos SP 12237150 + BR + 8C-1F-64 (hex) Inspinia Technology s.r.o. 595000-595FFF (base 16) Inspinia Technology s.r.o. Paleckeho 493 @@ -24284,23 +24368,17 @@ CB1000-CB1FFF (base 16) Xi’an Sunway Communication Co., Ltd. Road,High-Tech Zone Xi’an 710000 CN -8C-1F-64 (hex) Tecsys do Brasil Industrial Ltda -7D0000-7D0FFF (base 16) Tecsys do Brasil Industrial Ltda - Rua Oros, 146 - Sao Jose dos Campos SP 12237150 - BR - 8C-1F-64 (hex) Season Electronics Ltd 37D000-37DFFF (base 16) Season Electronics Ltd 600 Nest Business Park Havant Hampshire PO9 5TL GB -8C-1F-64 (hex) SURYA ELECTRONICS -3F2000-3F2FFF (base 16) SURYA ELECTRONICS - Plot no115 ALEAP Industrial Estate Gajularamaram village, Quthubullapur Mandal - HYDERABAD Telangana 500055 - IN +8C-1F-64 (hex) DEUTA Werke GmbH +5ED000-5EDFFF (base 16) DEUTA Werke GmbH + ET + Bergisch Gladbach NRW 51465 + DE 8C-1F-64 (hex) TelecomWadi 1F9000-1F9FFF (base 16) TelecomWadi @@ -24308,24 +24386,30 @@ CB1000-CB1FFF (base 16) Xi’an Sunway Communication Co., Ltd. Giza 3244530 EG +8C-1F-64 (hex) SURYA ELECTRONICS +3F2000-3F2FFF (base 16) SURYA ELECTRONICS + Plot no115 ALEAP Industrial Estate Gajularamaram village, Quthubullapur Mandal + HYDERABAD Telangana 500055 + IN + 8C-1F-64 (hex) Efftronics Systems (P) Ltd 063000-063FFF (base 16) Efftronics Systems (P) Ltd Plot No.4, IT Park, Auto Nagar Mangalagiri Andhra Pradesh 520010 IN +8C-1F-64 (hex) SUS Corporation +F69000-F69FFF (base 16) SUS Corporation + 6F, S-patio Bldg. 14-25 Minami-cho, Suruga-ku, + Shizuoka city, Shizuoka 422-8067 + JP + 8C-1F-64 (hex) Vantageo Private Limited 96B000-96BFFF (base 16) Vantageo Private Limited 617, Lodha Supremus II, Wagle Estate, Thane, Mumbai Maharastra 400604 IN -8C-1F-64 (hex) DEUTA Werke GmbH -5ED000-5EDFFF (base 16) DEUTA Werke GmbH - ET - Bergisch Gladbach NRW 51465 - DE - 8C-1F-64 (hex) Shenzhen Angstrom Excellence Technology Co., Ltd 277000-277FFF (base 16) Shenzhen Angstrom Excellence Technology Co., Ltd Angstrom Excellence Building, No. 1310,Guanguang Road,Longhua District @@ -24344,18 +24428,18 @@ FE0000-FE0FFF (base 16) Potter Electric Signal Co. LLC Hazelwood MO 63042 US -8C-1F-64 (hex) Samwell International Inc -3EB000-3EBFFF (base 16) Samwell International Inc - No. 317-1, Sec.2, An Kang Rd., Hsintien Dist - New Taipei City 231 - TW - 8C-1F-64 (hex) Potter Electric Signal Co. LLC 965000-965FFF (base 16) Potter Electric Signal Co. LLC 5757 Phantom Drive Hazelwood MO 63042 US +8C-1F-64 (hex) Samwell International Inc +3EB000-3EBFFF (base 16) Samwell International Inc + No. 317-1, Sec.2, An Kang Rd., Hsintien Dist + New Taipei City 231 + TW + 8C-1F-64 (hex) Potter Electric Signal Co. LLC EBB000-EBBFFF (base 16) Potter Electric Signal Co. LLC 5757 Phantom Drive @@ -24368,36 +24452,36 @@ EBB000-EBBFFF (base 16) Potter Electric Signal Co. LLC Hazelwood MO 63042 US -8C-1F-64 (hex) SUS Corporation -F69000-F69FFF (base 16) SUS Corporation - 6F, S-patio Bldg. 14-25 Minami-cho, Suruga-ku, - Shizuoka city, Shizuoka 422-8067 - JP - 8C-1F-64 (hex) CS-Tech s.r.o. ED7000-ED7FFF (base 16) CS-Tech s.r.o. Lazenska Usti nad Orlici Czech Republic 56201 CZ +8C-1F-64 (hex) Leap Info Systems Pvt. Ltd. +FEE000-FEEFFF (base 16) Leap Info Systems Pvt. Ltd. + 301 Melinkeri, Plot no.4, Survey No.149/1A, ITI Road,Parihar Chowk, Aundh, Pune – 411007 + Pune Maharashtra 411007 + IN + 8C-1F-64 (hex) SARV WEBS PRIVATE LIMITED CA0000-CA0FFF (base 16) SARV WEBS PRIVATE LIMITED IT-10,EPIP RIICO INDUSTRIAL AREA SITAPURA JAIPUR 302022 JAIPUR RAJASTHAN 302022 IN +8C-1F-64 (hex) CHUGOKU ELECTRICAL INSTRUMENTS Co.,LTD. +DD3000-DD3FFF (base 16) CHUGOKU ELECTRICAL INSTRUMENTS Co.,LTD. + 2-4-6,Tsurue,Fuchu-cho,Aki-gun, + Hiroshima Japan 735-0008 + JP + 8C-1F-64 (hex) Wi-Tronix, LLC D8D000-D8DFFF (base 16) Wi-Tronix, LLC 631 E Boughton Rd, Suite 240 Bolingbrook IL 60440 US -70-B3-D5 (hex) Aplex Technology Inc. -F57000-F57FFF (base 16) Aplex Technology Inc. - 15F-1, No.186, Jian Yi Road - Zhonghe District New Taipei City 235 - - TW - 8C-1F-64 (hex) YUYAMA MFG Co.,Ltd 24E000-24EFFF (base 16) YUYAMA MFG Co.,Ltd 1-4-30 @@ -24417,8 +24501,8 @@ F57000-F57FFF (base 16) Aplex Technology Inc. TW 70-B3-D5 (hex) Aplex Technology Inc. -906000-906FFF (base 16) Aplex Technology Inc. - 15F-1, No.186, Jian Yi Road +F57000-F57FFF (base 16) Aplex Technology Inc. + 15F-1, No.186, Jian Yi Road Zhonghe District New Taipei City 235 - TW @@ -24428,17 +24512,29 @@ B96000-B96FFF (base 16) Observable Space Los Angeles CA 90064 US -8C-1F-64 (hex) CHUGOKU ELECTRICAL INSTRUMENTS Co.,LTD. -DD3000-DD3FFF (base 16) CHUGOKU ELECTRICAL INSTRUMENTS Co.,LTD. - 2-4-6,Tsurue,Fuchu-cho,Aki-gun, - Hiroshima Japan 735-0008 - JP +70-B3-D5 (hex) Aplex Technology Inc. +906000-906FFF (base 16) Aplex Technology Inc. + 15F-1, No.186, Jian Yi Road + Zhonghe District New Taipei City 235 - + TW -8C-1F-64 (hex) Leap Info Systems Pvt. Ltd. -FEE000-FEEFFF (base 16) Leap Info Systems Pvt. Ltd. - 301 Melinkeri, Plot no.4, Survey No.149/1A, ITI Road,Parihar Chowk, Aundh, Pune – 411007 - Pune Maharashtra 411007 - IN +8C-1F-64 (hex) Yu Heng Electric CO. TD +1FC000-1FCFFF (base 16) Yu Heng Electric CO. TD + No. 8, Gongye 2nd Road, Renwu District, + Kaohiung City Taiwan 814 + CN + +8C-1F-64 (hex) REO AG +CD0000-CD0FFF (base 16) REO AG + Brühlerstr. 100 + Solingen 42657 + DE + +8C-1F-64 (hex) Intenseye Inc. +A20000-A20FFF (base 16) Intenseye Inc. + 1250 Broadway Suite 401 + New York NY 10001 + US 8C-1F-64 (hex) BOE Smart IoT Technology Co.,Ltd 761000-761FFF (base 16) BOE Smart IoT Technology Co.,Ltd @@ -24446,12 +24542,6 @@ FEE000-FEEFFF (base 16) Leap Info Systems Pvt. Ltd. Beijing Beijing 100176 CN -8C-1F-64 (hex) Yu Heng Electric CO. TD -1FC000-1FCFFF (base 16) Yu Heng Electric CO. TD - No. 8, Gongye 2nd Road, Renwu District, - Kaohiung City Taiwan 814 - CN - 8C-1F-64 (hex) TRATON AB 741000-741FFF (base 16) TRATON AB Lärlingsvägen 3 @@ -24470,24 +24560,12 @@ ED0000-ED0FFF (base 16) Shanghai Jupper Technology Co.Ltd Hazelwood MO 63042 US -8C-1F-64 (hex) Intenseye Inc. -A20000-A20FFF (base 16) Intenseye Inc. - 1250 Broadway Suite 401 - New York NY 10001 - US - 8C-1F-64 (hex) Smith meter Inc 6D1000-6D1FFF (base 16) Smith meter Inc 1602 Wagner Ave Erie 16510 US -8C-1F-64 (hex) REO AG -CD0000-CD0FFF (base 16) REO AG - Brühlerstr. 100 - Solingen 42657 - DE - 8C-1F-64 (hex) Racelogic Ltd FB6000-FB6FFF (base 16) Racelogic Ltd Unit 10-11 Osier Way,Swan Business Centre @@ -24542,18 +24620,24 @@ FA0000-FA0FFF (base 16) Pneumax Spa Lurano Bergamo 24050 IT -8C-1F-64 (hex) Coral Infratel Pvt Ltd -750000-750FFF (base 16) Coral Infratel Pvt Ltd - First Floor, 144 Subhash Nagar - Rohtak Haryana 124001 - IN - 8C-1F-64 (hex) Fortus 9A3000-9A3FFF (base 16) Fortus 32 Lavery Avenue Dublin Dublin D12 A611 IE +8C-1F-64 (hex) Potter Electric Signal Co. LLC +690000-690FFF (base 16) Potter Electric Signal Co. LLC + 1609 Park 370 Place + Hazelwood MO 63042 + US + +8C-1F-64 (hex) Coral Infratel Pvt Ltd +750000-750FFF (base 16) Coral Infratel Pvt Ltd + First Floor, 144 Subhash Nagar + Rohtak Haryana 124001 + IN + 8C-1F-64 (hex) AMC Europe Kft. A2F000-A2FFFF (base 16) AMC Europe Kft. Csiri utca 13 @@ -24566,12 +24650,6 @@ A2F000-A2FFFF (base 16) AMC Europe Kft. San Antonio TX 78218 US -8C-1F-64 (hex) Potter Electric Signal Co. LLC -690000-690FFF (base 16) Potter Electric Signal Co. LLC - 1609 Park 370 Place - Hazelwood MO 63042 - US - 8C-1F-64 (hex) HARBIN DIGITAL ECONOMY DEVELOPMENT CO.,LTD EC2000-EC2FFF (base 16) HARBIN DIGITAL ECONOMY DEVELOPMENT CO.,LTD No. 22, Binhe Avenue, Pingfang District @@ -24596,17 +24674,23 @@ EC2000-EC2FFF (base 16) HARBIN DIGITAL ECONOMY DEVELOPMENT CO.,LTD HELSINKI 00380 FI +8C-1F-64 (hex) BRS Sistemas Eletrônicos +944000-944FFF (base 16) BRS Sistemas Eletrônicos + Rua Capistrano de Abreu, 68 + Canoas RS 92120130 + BR + 8C-1F-64 (hex) Maven Pet Inc B7E000-B7EFFF (base 16) Maven Pet Inc 800 N King Street Suite 304 2873 Wilmington Wilmington DE 19801 US -8C-1F-64 (hex) BRS Sistemas Eletrônicos -944000-944FFF (base 16) BRS Sistemas Eletrônicos - Rua Capistrano de Abreu, 68 - Canoas RS 92120130 - BR +8C-1F-64 (hex) YONNET BILISIM YAZ. EGT. VE DAN. HIZ. TIC. A.S. +75E000-75EFFF (base 16) YONNET BILISIM YAZ. EGT. VE DAN. HIZ. TIC. A.S. + CUMHURIYET MAH. + ISTANBUL 34870 + TR 8C-1F-64 (hex) FaceLabs.AI DBA PropTech.AI FA9000-FA9FFF (base 16) FaceLabs.AI DBA PropTech.AI @@ -24620,12 +24704,6 @@ EC0000-EC0FFF (base 16) VOOST analytics Riyadh Al Riyadh 11391 SA -8C-1F-64 (hex) YONNET BILISIM YAZ. EGT. VE DAN. HIZ. TIC. A.S. -75E000-75EFFF (base 16) YONNET BILISIM YAZ. EGT. VE DAN. HIZ. TIC. A.S. - CUMHURIYET MAH. - ISTANBUL 34870 - TR - 8C-1F-64 (hex) MobileMustHave 6A7000-6A7FFF (base 16) MobileMustHave 63 Key Road Suite 3-1011 @@ -24638,12 +24716,6 @@ CE9000-CE9FFF (base 16) Landis+Gyr Equipamentos de Medição Ltda Curitiba Paraná 81460-120 BR -8C-1F-64 (hex) TOKYO INTERPHONE CO.,LTD. -652000-652FFF (base 16) TOKYO INTERPHONE CO.,LTD. - 8F, JS Shibuya Building3-8-10 Shibuya, Shibuya-ku - TOKYO 150-0002 - JP - 8C-1F-64 (hex) Förster-Technik GmbH 448000-448FFF (base 16) Förster-Technik GmbH Gerwigstrasse 25 @@ -24656,10 +24728,16 @@ CE9000-CE9FFF (base 16) Landis+Gyr Equipamentos de Medição Ltda Cheyenne WY 82001 US -8C-1F-64 (hex) MAYSUN CORPORATION -784000-784FFF (base 16) MAYSUN CORPORATION - 966-2 Gokanjima - Fuji-shi Shizuoka-ken 416-0946 +8C-1F-64 (hex) TOKYO INTERPHONE CO.,LTD. +652000-652FFF (base 16) TOKYO INTERPHONE CO.,LTD. + 8F, JS Shibuya Building3-8-10 Shibuya, Shibuya-ku + TOKYO 150-0002 + JP + +8C-1F-64 (hex) YUYAMA MFG Co.,Ltd +65A000-65AFFF (base 16) YUYAMA MFG Co.,Ltd + 1-4-30 + MEISHINGUCHI,TOYONAKA OSAKA 561-0841 JP 8C-1F-64 (hex) Pro Design Electronic GmbH @@ -24668,10 +24746,10 @@ CE9000-CE9FFF (base 16) Landis+Gyr Equipamentos de Medição Ltda Bruckmuehl Bavaria 83052 DE -8C-1F-64 (hex) YUYAMA MFG Co.,Ltd -65A000-65AFFF (base 16) YUYAMA MFG Co.,Ltd - 1-4-30 - MEISHINGUCHI,TOYONAKA OSAKA 561-0841 +8C-1F-64 (hex) MAYSUN CORPORATION +784000-784FFF (base 16) MAYSUN CORPORATION + 966-2 Gokanjima + Fuji-shi Shizuoka-ken 416-0946 JP 8C-1F-64 (hex) Buckeye Mountain @@ -24704,24 +24782,18 @@ F37000-F37FFF (base 16) Polarity Inc RANCHO CORDOVA CA 95742-6599 US -8C-1F-64 (hex) Attack do Brasil Ind Com Apar de Som LTDA -178000-178FFF (base 16) Attack do Brasil Ind Com Apar de Som LTDA - AV AYRTON SENNA DA SILVA, 400 – PQ INDL ZONA OESTE - Apucarana Parana 86803-570 - BR - -8C-1F-64 (hex) Grinn Sp. z o.o. -156000-156FFF (base 16) Grinn Sp. z o.o. - Strzegomska 140A - Wrocław 54-429 - PL - 8C-1F-64 (hex) Infosoft Digital Design and Services P L EDC000-EDCFFF (base 16) Infosoft Digital Design and Services P L 484, SECTOR-8 ,IMT MANESER,GURGAONMANESER GURGAON Haryana 122050 IN +8C-1F-64 (hex) Attack do Brasil Ind Com Apar de Som LTDA +178000-178FFF (base 16) Attack do Brasil Ind Com Apar de Som LTDA + AV AYRTON SENNA DA SILVA, 400 – PQ INDL ZONA OESTE + Apucarana Parana 86803-570 + BR + 8C-1F-64 (hex) Guangzhou Beizeng Information Technology Co.,Ltd 39F000-39FFFF (base 16) Guangzhou Beizeng Information Technology Co.,Ltd Room 714, Building D3, No. 197, Shuixi Road, Huangpu District, Guangzhou City, China @@ -24734,11 +24806,11 @@ EDC000-EDCFFF (base 16) Infosoft Digital Design and Services P L Kaohsiung City 81358 TW -8C-1F-64 (hex) Unitron Systems b.v. -1AC000-1ACFFF (base 16) Unitron Systems b.v. - SCHANSESTRAAT 7 - IJzendijke 4515 RN - NL +8C-1F-64 (hex) Grinn Sp. z o.o. +156000-156FFF (base 16) Grinn Sp. z o.o. + Strzegomska 140A + Wrocław 54-429 + PL 8C-1F-64 (hex) ANADOLU TRAFİK KONTROL SİS.TAŞ.SAN.VE TİC. LTD.ŞTİ D14000-D14FFF (base 16) ANADOLU TRAFİK KONTROL SİS.TAŞ.SAN.VE TİC. LTD.ŞTİ @@ -24746,23 +24818,23 @@ D14000-D14FFF (base 16) ANADOLU TRAFİK KONTROL SİS.TAŞ.SAN.VE TİC. LTD. ANKARA ANKARA 06180 TR +8C-1F-64 (hex) Unitron Systems b.v. +1AC000-1ACFFF (base 16) Unitron Systems b.v. + SCHANSESTRAAT 7 + IJzendijke 4515 RN + NL + 8C-1F-64 (hex) Kinemetrics, Inc. B50000-B50FFF (base 16) Kinemetrics, Inc. 222 Vista Avenue Pasadena CA 91107 US -8C-1F-64 (hex) Kneron (Taiwan) Co., Ltd. -1EE000-1EEFFF (base 16) Kneron (Taiwan) Co., Ltd. - 12F-1., No.386, Sec. 6, Nanjing E. Rd., Neihu Dist., - Taipei City 11470 - TW - -8C-1F-64 (hex) NodOn SAS -606000-606FFF (base 16) NodOn SAS - 121 rue des Hêtres - Saint Cyr en Val Loiret 45590 - FR +8C-1F-64 (hex) Tech Mobility Aps +31D000-31DFFF (base 16) Tech Mobility Aps + Lille Frederikslund 2 + Holte 2840 + DK 8C-1F-64 (hex) Nine Fives LLC D22000-D22FFF (base 16) Nine Fives LLC @@ -24770,24 +24842,30 @@ D22000-D22FFF (base 16) Nine Fives LLC Spokane WA 99201 US +8C-1F-64 (hex) Kneron (Taiwan) Co., Ltd. +1EE000-1EEFFF (base 16) Kneron (Taiwan) Co., Ltd. + 12F-1., No.386, Sec. 6, Nanjing E. Rd., Neihu Dist., + Taipei City 11470 + TW + 8C-1F-64 (hex) FemtoTools AG 7A9000-7A9FFF (base 16) FemtoTools AG Furtbachstrasse 4 Buchs Zurich 8107 CH -8C-1F-64 (hex) Tech Mobility Aps -31D000-31DFFF (base 16) Tech Mobility Aps - Lille Frederikslund 2 - Holte 2840 - DK - 8C-1F-64 (hex) AooGee Controls Co., LTD. 458000-458FFF (base 16) AooGee Controls Co., LTD. Siming District office building 14, Fu Lian Xiamen Fujian 361000 CN +8C-1F-64 (hex) NodOn SAS +606000-606FFF (base 16) NodOn SAS + 121 rue des Hêtres + Saint Cyr en Val Loiret 45590 + FR + 8C-1F-64 (hex) Sigmann Elektronik GmbH 49A000-49AFFF (base 16) Sigmann Elektronik GmbH Hauptstrasse 53 @@ -24806,42 +24884,24 @@ C84000-C84FFF (base 16) Luceor Montigny-le-Bretonneux 78180 FR -8C-1F-64 (hex) Currux Vision LLC -66B000-66BFFF (base 16) Currux Vision LLC - 520 Post Oak Boulevard, Suite 260 - Houston TX 77027 - US - 8C-1F-64 (hex) SHODEN Co., Ltd. 259000-259FFF (base 16) SHODEN Co., Ltd. 365, Sannocho Inage-ku Chiba Chiba 2630002 JP -8C-1F-64 (hex) Power Electronics Espana, S.L. -773000-773FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - -8C-1F-64 (hex) Vision Systems Safety Tech -AD9000-AD9FFF (base 16) Vision Systems Safety Tech - 5 Chemin de Chiradie - Brignais 69530 - FR - -8C-1F-64 (hex) Wesync -190000-190FFF (base 16) Wesync - 506Ho, Pyeongchondigitalempire, 16, Heungan-daero 427beon-gil, Dongan-gu - Anyang-si Gyeonggi-do 14059 - KR - 8C-1F-64 (hex) ChamSys 143000-143FFF (base 16) ChamSys Unit 5Adanac Park southampton Hampshire SO16 0BT GB +8C-1F-64 (hex) Currux Vision LLC +66B000-66BFFF (base 16) Currux Vision LLC + 520 Post Oak Boulevard, Suite 260 + Houston TX 77027 + US + 8C-1F-64 (hex) LyconSys GmbH & Co.KG 134000-134FFF (base 16) LyconSys GmbH & Co.KG Hildegardstr. 12A @@ -24854,10 +24914,22 @@ AD9000-AD9FFF (base 16) Vision Systems Safety Tech Ithaca NY 14850 US -70-B3-D5 (hex) ICTK Co., Ltd. -5C9000-5C9FFF (base 16) ICTK Co., Ltd. - 3F Ventureforum B'd, Pangyodae-ro - Seung-nam Si Gyeonggi-Do 13488 +8C-1F-64 (hex) Power Electronics Espana, S.L. +773000-773FFF (base 16) Power Electronics Espana, S.L. + C/ Leonardo Da Vinci, 24-26 + Paterna Valencia 46980 + ES + +8C-1F-64 (hex) Vision Systems Safety Tech +AD9000-AD9FFF (base 16) Vision Systems Safety Tech + 5 Chemin de Chiradie + Brignais 69530 + FR + +8C-1F-64 (hex) Wesync +190000-190FFF (base 16) Wesync + 506Ho, Pyeongchondigitalempire, 16, Heungan-daero 427beon-gil, Dongan-gu + Anyang-si Gyeonggi-do 14059 KR 8C-1F-64 (hex) PASO SPA @@ -24866,11 +24938,17 @@ CF8000-CF8FFF (base 16) PASO SPA Lainate Italy 20045 IT -8C-1F-64 (hex) ASI -B53000-B53FFF (base 16) ASI - 1001 Av. de la République - Marcq-en-Baroeul 59700 - FR +70-B3-D5 (hex) ICTK Co., Ltd. +5C9000-5C9FFF (base 16) ICTK Co., Ltd. + 3F Ventureforum B'd, Pangyodae-ro + Seung-nam Si Gyeonggi-Do 13488 + KR + +8C-1F-64 (hex) Hitachi Energy Australia Pty. Ltd. +505000-505FFF (base 16) Hitachi Energy Australia Pty. Ltd. + 88 Beresford Road + Lilydale 3140 + AU 8C-1F-64 (hex) Potter Electric Signal Co. LLC 75D000-75DFFF (base 16) Potter Electric Signal Co. LLC @@ -24878,11 +24956,17 @@ B53000-B53FFF (base 16) ASI Hazelwood MO 63042 US -8C-1F-64 (hex) Hitachi Energy Australia Pty. Ltd. -505000-505FFF (base 16) Hitachi Energy Australia Pty. Ltd. - 88 Beresford Road - Lilydale 3140 - AU +8C-1F-64 (hex) ASI +B53000-B53FFF (base 16) ASI + 1001 Av. de la République + Marcq-en-Baroeul 59700 + FR + +8C-1F-64 (hex) therlys GmbH +D25000-D25FFF (base 16) therlys GmbH + Heidenkampsweg 40 + Hamburg 20097 + DE 8C-1F-64 (hex) Blackline Systems Corp. BE5000-BE5FFF (base 16) Blackline Systems Corp. @@ -24896,12 +24980,48 @@ BE5000-BE5FFF (base 16) Blackline Systems Corp. St. Gallen 9008 CH -8C-1F-64 (hex) therlys GmbH -D25000-D25FFF (base 16) therlys GmbH - Heidenkampsweg 40 - Hamburg 20097 +8C-1F-64 (hex) Vision Systems Safety Tech +436000-436FFF (base 16) Vision Systems Safety Tech + 5 Chemin de Chiradie + Brignais 69530 + FR + +8C-1F-64 (hex) DORLET SAU +BC5000-BC5FFF (base 16) DORLET SAU + C/ ALBERT EINSTEIN 34, PARQUE TECNOLOGICO DE ALAVA + VITORIA - GASTEIZ ALAVA 01510 + ES + +8C-1F-64 (hex) ACS Motion Control +64C000-64CFFF (base 16) ACS Motion Control + 5 Ha'Tnufa st. + Yokneam 2066717 + IL + +70-B3-D5 (hex) Hörmann Warnsysteme GmbH +B78000-B78FFF (base 16) Hörmann Warnsysteme GmbH + Hauptstr. 45-47 + Kirchseeon Bavaria 85614 DE +8C-1F-64 (hex) Groundtruth Ltd +D67000-D67FFF (base 16) Groundtruth Ltd + 14 Tilley Road + Paekakariki 5034 + NZ + +8C-1F-64 (hex) CRUXELL Corp. +3E1000-3E1FFF (base 16) CRUXELL Corp. + A-405 Migun techno world II,187 techno 2-ro, Yusong-gu + Daejeon Daejeon 34025 + KR + +8C-1F-64 (hex) RFT Corp. +0A9000-0A9FFF (base 16) RFT Corp. + 516 Kamikocho, Omiya-ku + Saitama-shi Saitama 330-0855 + JP + 8C-1F-64 (hex) Flow Power 82B000-82BFFF (base 16) Flow Power Suite 2, Level 3, 18 - 20 York St @@ -32684,18 +32804,18 @@ AB3000-AB3FFF (base 16) VELVU TECHNOLOGIES PRIVATE LIMITED Skovlunde 2740 DK -8C-1F-64 (hex) wincker international enterprise co., ltd -B1F000-B1FFFF (base 16) wincker international enterprise co., ltd - 1FL No. 345 Yen Shou St., Taipei, Taiwan - Taipei 10577 - TW - 8C-1F-64 (hex) Chengdu Xiuwei TechnologyDevelopment Co., Ltd 870000-870FFF (base 16) Chengdu Xiuwei TechnologyDevelopment Co., Ltd 10th Floor, Building 10, No. 8 Guangfu Road, Qingyang District Chengdu City Please Select 610073 CN +8C-1F-64 (hex) wincker international enterprise co., ltd +B1F000-B1FFFF (base 16) wincker international enterprise co., ltd + 1FL No. 345 Yen Shou St., Taipei, Taiwan + Taipei 10577 + TW + 8C-1F-64 (hex) IQ Tools LLC FF5000-FF5FFF (base 16) IQ Tools LLC Zemlyanoy Val, 64, building 2 @@ -32786,18 +32906,18 @@ B33000-B33FFF (base 16) Aplex Technology Inc. Zhonghe District New Taipei City 235 - TW -8C-1F-64 (hex) Boeing India Private Limited -533000-533FFF (base 16) Boeing India Private Limited - Plot No: 55-B,56,57,59 Hitech-Defence and Aerospace park, Aerospace Sector, Unachur Village, Yelahanka Taluk, Bangaloe North - Bengaluru Karnataka 562149 - IN - 8C-1F-64 (hex) Jemac Sweden AB 42D000-42DFFF (base 16) Jemac Sweden AB Trångsundsvägen 20A Kalmar 39356 SE +8C-1F-64 (hex) Boeing India Private Limited +533000-533FFF (base 16) Boeing India Private Limited + Plot No: 55-B,56,57,59 Hitech-Defence and Aerospace park, Aerospace Sector, Unachur Village, Yelahanka Taluk, Bangaloe North + Bengaluru Karnataka 562149 + IN + 70-B3-D5 (hex) Aplex Technology Inc. 3D9000-3D9FFF (base 16) Aplex Technology Inc. 15F-1, No.186, Jian Yi Road @@ -32834,12 +32954,6 @@ C31000-C31FFF (base 16) Ambarella Inc. Santa Clara CA 95054 US -8C-1F-64 (hex) Aegex Technologies LLC Magyarországi Fióktelepe -A9D000-A9DFFF (base 16) Aegex Technologies LLC Magyarországi Fióktelepe - Tildy Zoltán utca - Pécs Baranya 7632 - HU - 8C-1F-64 (hex) Q (Cue), Inc. 6A6000-6A6FFF (base 16) Q (Cue), Inc. Abba Hillel Silver Rd 21 @@ -32852,6 +32966,12 @@ A9D000-A9DFFF (base 16) Aegex Technologies LLC Magyarországi Fióktelepe Beijing Haidian District 100085 CN +8C-1F-64 (hex) Aegex Technologies LLC Magyarországi Fióktelepe +A9D000-A9DFFF (base 16) Aegex Technologies LLC Magyarországi Fióktelepe + Tildy Zoltán utca + Pécs Baranya 7632 + HU + 8C-1F-64 (hex) Automation Displays Inc. 4F2000-4F2FFF (base 16) Automation Displays Inc. 3533 White Ave @@ -32906,12 +33026,6 @@ BF7000-BF7FFF (base 16) Intellicon Private Limited Chuou-ku Tokyo 104-0061 JP -8C-1F-64 (hex) Lumiplan-Duhamel -0D0000-0D0FFF (base 16) Lumiplan-Duhamel - 215 rue Guynemer - Le versoud 38420 - FR - 8C-1F-64 (hex) Private B80000-B80FFF (base 16) Private @@ -32921,6 +33035,12 @@ B80000-B80FFF (base 16) Private Bellingham WA 98225 US +8C-1F-64 (hex) Lumiplan-Duhamel +0D0000-0D0FFF (base 16) Lumiplan-Duhamel + 215 rue Guynemer + Le versoud 38420 + FR + 8C-1F-64 (hex) Breas Medical AB 348000-348FFF (base 16) Breas Medical AB Företagsvägen 1 @@ -32933,18 +33053,18 @@ B80000-B80FFF (base 16) Private Calgary Alberta T3H 5T9 CA -00-1B-C5 (hex) CyanConnode -0C6000-0C6FFF (base 16) CyanConnode - Suite 2, Ground Floor, The Jeffreys Building, Cowley Road - Milton Cambridge CB4 0DS - GB - 8C-1F-64 (hex) Thales Nederland BV 29C000-29CFFF (base 16) Thales Nederland BV Haaksbergerstraat 49 Hengelo Overijssel 7554PA NL +00-1B-C5 (hex) CyanConnode +0C6000-0C6FFF (base 16) CyanConnode + Suite 2, Ground Floor, The Jeffreys Building, Cowley Road + Milton Cambridge CB4 0DS + GB + 8C-1F-64 (hex) SMC Gateway 0B5000-0B5FFF (base 16) SMC Gateway 78 HIGH BEECHES @@ -32975,29 +33095,35 @@ F99000-F99FFF (base 16) Sysinno Technology Inc. Hsinchu 300 TW -8C-1F-64 (hex) Bounce Imaging -1AE000-1AEFFF (base 16) Bounce Imaging - 247 Cayuga Rd., Suite 15e - Cheektowaga NY 14225 - US - 8C-1F-64 (hex) InfoMac Sp. z o.o. Sp.k. 840000-840FFF (base 16) InfoMac Sp. z o.o. Sp.k. UL. WOJSKA POLSKIEGO 6 Szczecinek zachodniopomorskie 78-400 PL +8C-1F-64 (hex) RSC +B31000-B31FFF (base 16) RSC + 36 27th Street, Umm Suqeim 3 + Dubai Dubai 00000 + AE + +8C-1F-64 (hex) Bounce Imaging +1AE000-1AEFFF (base 16) Bounce Imaging + 247 Cayuga Rd., Suite 15e + Cheektowaga NY 14225 + US + 8C-1F-64 (hex) Asteelflash Design Solutions Hamburg GmbH 1EA000-1EAFFF (base 16) Asteelflash Design Solutions Hamburg GmbH Meiendorfer Straße 205c Hamburg 22145 DE -8C-1F-64 (hex) RSC -B31000-B31FFF (base 16) RSC - 36 27th Street, Umm Suqeim 3 - Dubai Dubai 00000 - AE +8C-1F-64 (hex) Chengdu Xinyuandi Technology Co., Ltd. +C34000-C34FFF (base 16) Chengdu Xinyuandi Technology Co., Ltd. + No. 7, Tianxianqiao North Road, Jinjiang District, Chengdu, Sichuan Province, China + Chengdu 610021 + CN 70-B3-D5 (hex) AML Oceanographic 0CD000-0CDFFF (base 16) AML Oceanographic @@ -33005,11 +33131,11 @@ B31000-B31FFF (base 16) RSC DARTMOUTH NS B3B 1S4 CA -8C-1F-64 (hex) Chengdu Xinyuandi Technology Co., Ltd. -C34000-C34FFF (base 16) Chengdu Xinyuandi Technology Co., Ltd. - No. 7, Tianxianqiao North Road, Jinjiang District, Chengdu, Sichuan Province, China - Chengdu 610021 - CN +8C-1F-64 (hex) Produkcija studio C.P.G d.o.o. +A0C000-A0CFFF (base 16) Produkcija studio C.P.G d.o.o. + Svetice 23 + Zagreb Zagreb 10000 + HR 8C-1F-64 (hex) ADETEC SAS 835000-835FFF (base 16) ADETEC SAS @@ -33023,12 +33149,6 @@ C34000-C34FFF (base 16) Chengdu Xinyuandi Technology Co., Ltd. Chengdu SiChuan 610000 CN -8C-1F-64 (hex) Produkcija studio C.P.G d.o.o. -A0C000-A0CFFF (base 16) Produkcija studio C.P.G d.o.o. - Svetice 23 - Zagreb Zagreb 10000 - HR - 8C-1F-64 (hex) Raycon A09000-A09FFF (base 16) Raycon 1115 Broadway, Suite 12 @@ -33059,23 +33179,17 @@ BD0000-BD0FFF (base 16) Mesa Labs, Inc. Lakewood CO 80228 US -8C-1F-64 (hex) Anhui Wenxiang Technology Co.,Ltd. -3CB000-3CBFFF (base 16) Anhui Wenxiang Technology Co.,Ltd. - The intersection of Fengming Avenue and Hanjiang Road, Jiangnan Emerging Industry Concentration Zone - Chizhou Anhui 247100 - CN - 8C-1F-64 (hex) Starview Asia Company 83B000-83BFFF (base 16) Starview Asia Company Level 40, 140 Williams Street Melbourne Victoria 3000 AU -8C-1F-64 (hex) INTERNET PROTOCOLO LOGICA SL -06E000-06EFFF (base 16) INTERNET PROTOCOLO LOGICA SL - Avenida Somosierra 12. Portal A. Planta 1ª. Letra I - San Sebastián de los Reyes Madrid 28703 - ES +8C-1F-64 (hex) Anhui Wenxiang Technology Co.,Ltd. +3CB000-3CBFFF (base 16) Anhui Wenxiang Technology Co.,Ltd. + The intersection of Fengming Avenue and Hanjiang Road, Jiangnan Emerging Industry Concentration Zone + Chizhou Anhui 247100 + CN 8C-1F-64 (hex) Eltvor Instruments B58000-B58FFF (base 16) Eltvor Instruments @@ -33083,6 +33197,12 @@ B58000-B58FFF (base 16) Eltvor Instruments Tabor 39002 CZ +8C-1F-64 (hex) INTERNET PROTOCOLO LOGICA SL +06E000-06EFFF (base 16) INTERNET PROTOCOLO LOGICA SL + Avenida Somosierra 12. Portal A. Planta 1ª. Letra I + San Sebastián de los Reyes Madrid 28703 + ES + 8C-1F-64 (hex) Rudolf Riester GmbH 27A000-27AFFF (base 16) Rudolf Riester GmbH P.O. Box 35 Bruckstrasse 31 @@ -33113,17 +33233,23 @@ A78000-A78FFF (base 16) TAIT Global LLC Lititz PA 17543 US +8C-1F-64 (hex) netmon +434000-434FFF (base 16) netmon + B-1023 TERA Tower#1, 167 SONGPA-DAERO, SONGPA-GU + Seoul 05855 + KR + 8C-1F-64 (hex) OES Inc. 578000-578FFF (base 16) OES Inc. 4056 Blakie Road London ON N6L1P7 CA -8C-1F-64 (hex) netmon -434000-434FFF (base 16) netmon - B-1023 TERA Tower#1, 167 SONGPA-DAERO, SONGPA-GU - Seoul 05855 - KR +8C-1F-64 (hex) Diatech co.,ltd. +26C000-26CFFF (base 16) Diatech co.,ltd. + 201 City Dolce Iogi 2-2-9 Igusa + Suginami Ku Tokyo 167-0021 + JP 8C-1F-64 (hex) inomatic GmbH 96E000-96EFFF (base 16) inomatic GmbH @@ -33131,6 +33257,42 @@ A78000-A78FFF (base 16) TAIT Global LLC Nordhorn Germany 48531 DE +8C-1F-64 (hex) Pneumax Spa +431000-431FFF (base 16) Pneumax Spa + via cascina barbellina, 10 + Lurano Bergamo 24050 + IT + +8C-1F-64 (hex) Apantac LLC +471000-471FFF (base 16) Apantac LLC + 7556 SW Bridgeport Road + Durham OR 97224 + US + +8C-1F-64 (hex) Fischer & Connectors SA +0A3000-0A3FFF (base 16) Fischer & Connectors SA + Chemin du Glapin 20 + Saint-Prex CH-1162 + CH + +8C-1F-64 (hex) Schildknecht AG +F16000-F16FFF (base 16) Schildknecht AG + Haugweg 26 + Murr 71711 + DE + +8C-1F-64 (hex) Teledyne Scientific and Imaging +590000-590FFF (base 16) Teledyne Scientific and Imaging + 1049 Camino Dos Rios + Thousand Oaks CA 91360-2362 + US + +8C-1F-64 (hex) CMI, Inc. +5A2000-5A2FFF (base 16) CMI, Inc. + 316 East 9th Street + Owensboro KY 42303 + US + 8C-1F-64 (hex) Mobileye D63000-D63FFF (base 16) Mobileye 13 Hartom st. @@ -40793,11 +40955,11 @@ C75000-C75FFF (base 16) Abbott Diagnostics Technologies AS Oslo Oslo 0504 NO -8C-1F-64 (hex) Oriental Electronics, Inc. -68E000-68EFFF (base 16) Oriental Electronics, Inc. - 2-4-1 Tanabe-Chuo - Kyo-Tanabe Kyoto 610-0334 - JP +8C-1F-64 (hex) OnAsset Intelligence +415000-415FFF (base 16) OnAsset Intelligence + 8407 Sterling Street + Irving TX 75063 + US 8C-1F-64 (hex) OOO Mig Trading C19000-C19FFF (base 16) OOO Mig Trading @@ -40811,10 +40973,16 @@ C19000-C19FFF (base 16) OOO Mig Trading Dinkelsbuehl Bavaria 91550 DE -8C-1F-64 (hex) OnAsset Intelligence -415000-415FFF (base 16) OnAsset Intelligence - 8407 Sterling Street - Irving TX 75063 +8C-1F-64 (hex) Oriental Electronics, Inc. +68E000-68EFFF (base 16) Oriental Electronics, Inc. + 2-4-1 Tanabe-Chuo + Kyo-Tanabe Kyoto 610-0334 + JP + +8C-1F-64 (hex) Pulcro.io LLC +C22000-C22FFF (base 16) Pulcro.io LLC + 551 S IH 35, Ste 300 + Round Rock TX 78664 US 8C-1F-64 (hex) Digitella Inc. @@ -40823,29 +40991,29 @@ C19000-C19FFF (base 16) OOO Mig Trading Anyang-si Gyeonggi-do 14084 KR -8C-1F-64 (hex) Pulcro.io LLC -C22000-C22FFF (base 16) Pulcro.io LLC - 551 S IH 35, Ste 300 - Round Rock TX 78664 - US - 8C-1F-64 (hex) Melissa Climate Jsc 6CA000-6CAFFF (base 16) Melissa Climate Jsc Gen. Gurko 4 Street Sofia 1000 BG +8C-1F-64 (hex) wonder meditec +B2D000-B2DFFF (base 16) wonder meditec + 2F, 12-11, Seonjam-ro, Seongbuk-gu + Seoul, Korea 02836 + KR + 8C-1F-64 (hex) IGL 8F0000-8F0FFF (base 16) IGL 1, Allée des Chevreuils, Lissieu 69380 FR -8C-1F-64 (hex) wonder meditec -B2D000-B2DFFF (base 16) wonder meditec - 2F, 12-11, Seonjam-ro, Seongbuk-gu - Seoul, Korea 02836 - KR +8C-1F-64 (hex) In-lite Design BV +F4A000-F4AFFF (base 16) In-lite Design BV + Stephensonweg 18 + Gorinchem Zuid-Holland 4207 HB + NL 70-B3-D5 (hex) Teenage Engineering AB 1AF000-1AFFFF (base 16) Teenage Engineering AB @@ -40871,12 +41039,6 @@ D64000-D64FFF (base 16) Potter Electric Signal Co. LLC Hazelwood MO 63042 US -8C-1F-64 (hex) In-lite Design BV -F4A000-F4AFFF (base 16) In-lite Design BV - Stephensonweg 18 - Gorinchem Zuid-Holland 4207 HB - NL - 8C-1F-64 (hex) IDA North America Inc. 275000-275FFF (base 16) IDA North America Inc. 16 16th Street S @@ -40889,30 +41051,30 @@ F4A000-F4AFFF (base 16) In-lite Design BV Zhonghe District New Taipei City 235 - TW -70-B3-D5 (hex) Aplex Technology Inc. -FF3000-FF3FFF (base 16) Aplex Technology Inc. - 15F-1, No.186, Jian Yi Road - Zhonghe District New Taipei City 235 - - TW - -70-B3-D5 (hex) Aplex Technology Inc. -65C000-65CFFF (base 16) Aplex Technology Inc. - 15F-1, No.186, Jian Yi Road - Zhonghe District New Taipei City 235 - - TW - 8C-1F-64 (hex) Shenzhen Broadradio RFID Technology Co., Ltd 057000-057FFF (base 16) Shenzhen Broadradio RFID Technology Co., Ltd B222, 2nd Floor, Building B, Fuhai Technology Industrial Park shenzhen guangdong 5178000 CN +70-B3-D5 (hex) Aplex Technology Inc. +FF3000-FF3FFF (base 16) Aplex Technology Inc. + 15F-1, No.186, Jian Yi Road + Zhonghe District New Taipei City 235 - + TW + 8C-1F-64 (hex) Yu Heng Electric CO. TD 575000-575FFF (base 16) Yu Heng Electric CO. TD No 8 , Gongye 2nd Rd., Renwu Industry Park Kaohsiung Kaohsiung City 814 TW +70-B3-D5 (hex) Aplex Technology Inc. +65C000-65CFFF (base 16) Aplex Technology Inc. + 15F-1, No.186, Jian Yi Road + Zhonghe District New Taipei City 235 - + TW + 8C-1F-64 (hex) SPX Flow Technology BV CDA000-CDAFFF (base 16) SPX Flow Technology BV Munnikenheiweg 41 @@ -41063,18 +41225,18 @@ E2D000-E2DFFF (base 16) BAE Systems Guildford Surrey GU2 7RQ GB -8C-1F-64 (hex) RADA Electronics Industries Ltd. -E37000-E37FFF (base 16) RADA Electronics Industries Ltd. - 7 Gibory Israel St. - Netanya 42504 - IL - 8C-1F-64 (hex) XYZ Digital Private Limited 4B3000-4B3FFF (base 16) XYZ Digital Private Limited KH NO 1126 GROUND FLOOR STREET NO 17 VILLAGE RITHALA LANDMARK HONDA SHOW ROOM, North Delhi Rohini Delhi 110085 IN +8C-1F-64 (hex) RADA Electronics Industries Ltd. +E37000-E37FFF (base 16) RADA Electronics Industries Ltd. + 7 Gibory Israel St. + Netanya 42504 + IL + 8C-1F-64 (hex) Meiji Electric Industry 75B000-75BFFF (base 16) Meiji Electric Industry 48-1 Itabari , Yamayashiki-cho @@ -41084,29 +41246,35 @@ E37000-E37FFF (base 16) RADA Electronics Industries Ltd. 8C-1F-64 (hex) Private D48000-D48FFF (base 16) Private +8C-1F-64 (hex) Fugro Technology B.V. +7CD000-7CDFFF (base 16) Fugro Technology B.V. + Prismastraat 3 + Nootdorp 2631RT + NL + 8C-1F-64 (hex) Hiwin Mikrosystem Corp. A74000-A74FFF (base 16) Hiwin Mikrosystem Corp. NO 6 JINGKE CENTRAL RD TAICHUNG CITY TAIWAN 40841 TAICHUNG 40841 TW +8C-1F-64 (hex) Irmos Technologies AG +DDD000-DDDFFF (base 16) Irmos Technologies AG + Technoparkstrasse 1 + Zürich 8005 + CH + 8C-1F-64 (hex) 37130 81E000-81EFFF (base 16) 37130 Gaildorfer Strasse 6 Backnang 71540 DE -8C-1F-64 (hex) Fugro Technology B.V. -7CD000-7CDFFF (base 16) Fugro Technology B.V. - Prismastraat 3 - Nootdorp 2631RT - NL - -8C-1F-64 (hex) Irmos Technologies AG -DDD000-DDDFFF (base 16) Irmos Technologies AG - Technoparkstrasse 1 - Zürich 8005 - CH +8C-1F-64 (hex) SAEL SRL +60F000-60FFFF (base 16) SAEL SRL + Via Dei Genieri, 31 + Torri di Quartesolo Vicenza 36040 + IT 8C-1F-64 (hex) Kyowakiden Industry Co.,Ltd. 3D6000-3D6FFF (base 16) Kyowakiden Industry Co.,Ltd. @@ -41120,12 +41288,6 @@ DDD000-DDDFFF (base 16) Irmos Technologies AG Shannon Co. Clare V14 V99 IE -8C-1F-64 (hex) SAEL SRL -60F000-60FFFF (base 16) SAEL SRL - Via Dei Genieri, 31 - Torri di Quartesolo Vicenza 36040 - IT - 8C-1F-64 (hex) CEI Ptd Ltd 0FD000-0FDFFF (base 16) CEI Ptd Ltd 2 Ang Mo Kio Ave 12 @@ -41144,18 +41306,18 @@ DDD000-DDDFFF (base 16) Irmos Technologies AG Gramastetten Oberoesterreich 4201 AT -8C-1F-64 (hex) MYIR Electronics Limited -A1D000-A1DFFF (base 16) MYIR Electronics Limited - Room 04, 6th Floor, Building No.2, Fada Road, Yunli Smart Park,Bantian, Longgang District, Shenzhen, Guangdong, China - Shenzhen Guangdong 518129 - CN - 8C-1F-64 (hex) Sicon srl CC8000-CC8FFF (base 16) Sicon srl Via Sila 1/3 Isola Vicentina Vicenza 36033 IT +8C-1F-64 (hex) MYIR Electronics Limited +A1D000-A1DFFF (base 16) MYIR Electronics Limited + Room 04, 6th Floor, Building No.2, Fada Road, Yunli Smart Park,Bantian, Longgang District, Shenzhen, Guangdong, China + Shenzhen Guangdong 518129 + CN + 8C-1F-64 (hex) Nortek(QingDao) Measuring Equipment Co., Ltd 988000-988FFF (base 16) Nortek(QingDao) Measuring Equipment Co., Ltd 18A2, Yingdelong Buliding,No.15 Donghaixi Rd, Qingdao P.R.China @@ -41168,12 +41330,6 @@ CC8000-CC8FFF (base 16) Sicon srl Saint-Laurent Quebec H4T 1W7 CA -8C-1F-64 (hex) SAMSON CO.,LTD. -490000-490FFF (base 16) SAMSON CO.,LTD. - 3-4-15 YAHATA-CHO - Kanonji-City Kagawa 768-8602 - JP - 8C-1F-64 (hex) IRONWOOD ELECTRONICS C26000-C26FFF (base 16) IRONWOOD ELECTRONICS 1335 Eagandale Court @@ -41186,6 +41342,12 @@ A72000-A72FFF (base 16) First Design System Inc. Tokyo Shinjuku-ku 160-0023 JP +8C-1F-64 (hex) SAMSON CO.,LTD. +490000-490FFF (base 16) SAMSON CO.,LTD. + 3-4-15 YAHATA-CHO + Kanonji-City Kagawa 768-8602 + JP + 8C-1F-64 (hex) Innovative Signal Analysis 1BA000-1BAFFF (base 16) Innovative Signal Analysis 3301 E Renner Rd, Ste 200 @@ -41198,24 +41360,12 @@ A72000-A72FFF (base 16) First Design System Inc. Toronto Ontario M2H 3R1 CA -8C-1F-64 (hex) AEviso Video Solution Co., Ltd. -1E4000-1E4FFF (base 16) AEviso Video Solution Co., Ltd. - 15 F.-6, No. 716, Zhongzheng Rd., Zhonghe Dist., - New Taipei City n.a 235603 - TW - 8C-1F-64 (hex) Smart Dynamics SIA 576000-576FFF (base 16) Smart Dynamics SIA Ūdeles Amatciems Cēsu novads LV-4101 LV -8C-1F-64 (hex) Expromo Europe A/S -C39000-C39FFF (base 16) Expromo Europe A/S - Langdyssen 3 - Aarhus N 8200 - DK - 8C-1F-64 (hex) NEBERO SYSTEMS PRIVATE LIMTED 71C000-71CFFF (base 16) NEBERO SYSTEMS PRIVATE LIMTED Plot 691, Sector 82, Industrial Area, SAS Nagar @@ -41228,11 +41378,17 @@ E6B000-E6BFFF (base 16) Terratel Technology s.r.o. Benesov CZ 25601 CZ -8C-1F-64 (hex) SMITEC S.p.A. -E82000-E82FFF (base 16) SMITEC S.p.A. - Via Carlo Ceresa, 10 - San Giovanni Bianco Bergamo 24015 - IT +8C-1F-64 (hex) AEviso Video Solution Co., Ltd. +1E4000-1E4FFF (base 16) AEviso Video Solution Co., Ltd. + 15 F.-6, No. 716, Zhongzheng Rd., Zhonghe Dist., + New Taipei City n.a 235603 + TW + +8C-1F-64 (hex) Expromo Europe A/S +C39000-C39FFF (base 16) Expromo Europe A/S + Langdyssen 3 + Aarhus N 8200 + DK 8C-1F-64 (hex) I2V Systems Pvt. Ltd. 1E0000-1E0FFF (base 16) I2V Systems Pvt. Ltd. @@ -41252,11 +41408,11 @@ E82000-E82FFF (base 16) SMITEC S.p.A. England OL10 4HU GB -8C-1F-64 (hex) Mootek Technologies Private Limited -CEA000-CEAFFF (base 16) Mootek Technologies Private Limited - No.20, First Floor, East Jones Road,SaidapetChennai - Chennai Tamilnadu 600015 - IN +8C-1F-64 (hex) SMITEC S.p.A. +E82000-E82FFF (base 16) SMITEC S.p.A. + Via Carlo Ceresa, 10 + San Giovanni Bianco Bergamo 24015 + IT 8C-1F-64 (hex) Talius Services Pty Ltd 5D2000-5D2FFF (base 16) Talius Services Pty Ltd @@ -41264,8 +41420,11 @@ CEA000-CEAFFF (base 16) Mootek Technologies Private Limited Brisbane QLD 4009 AU -8C-1F-64 (hex) Private -B94000-B94FFF (base 16) Private +8C-1F-64 (hex) Mootek Technologies Private Limited +CEA000-CEAFFF (base 16) Mootek Technologies Private Limited + No.20, First Floor, East Jones Road,SaidapetChennai + Chennai Tamilnadu 600015 + IN 8C-1F-64 (hex) Vojensky Technicky Ustav, s.p. B51000-B51FFF (base 16) Vojensky Technicky Ustav, s.p. @@ -41273,17 +41432,8 @@ B51000-B51FFF (base 16) Vojensky Technicky Ustav, s.p. Praha 19700 CZ -8C-1F-64 (hex) BAOLIHDER CO.,LTD. -1CF000-1CFFFF (base 16) BAOLIHDER CO.,LTD. - 5 F., No. 46, Ln. 394, Longjiang Rd., Zhongshan Dist., Taipei City 10474, Taiwan (R.O.C.) - Taipei 10474 - TW - -70-B3-D5 (hex) RoboCore Tecnologia -0AC000-0ACFFF (base 16) RoboCore Tecnologia - Av Honorio Alvares Penteado, 97 - Galpao 77 - Santana de Parnaiba SP 06543-320 - BR +8C-1F-64 (hex) Private +B94000-B94FFF (base 16) Private 8C-1F-64 (hex) Terragene 248000-248FFF (base 16) Terragene @@ -41296,3 +41446,45 @@ BA5000-BA5FFF (base 16) Campus Genevois de Haute Horlogerie Rue André-De-Garrini 7 Meyrin 1217 CH + +70-B3-D5 (hex) RoboCore Tecnologia +0AC000-0ACFFF (base 16) RoboCore Tecnologia + Av Honorio Alvares Penteado, 97 - Galpao 77 + Santana de Parnaiba SP 06543-320 + BR + +8C-1F-64 (hex) BAOLIHDER CO.,LTD. +1CF000-1CFFFF (base 16) BAOLIHDER CO.,LTD. + 5 F., No. 46, Ln. 394, Longjiang Rd., Zhongshan Dist., Taipei City 10474, Taiwan (R.O.C.) + Taipei 10474 + TW + +8C-1F-64 (hex) ATAL s.r.o. +805000-805FFF (base 16) ATAL s.r.o. + Lesni 47 + Tabor 39001 + CZ + +8C-1F-64 (hex) AK Automation +C48000-C48FFF (base 16) AK Automation + 105 /106, New Bharat Industrial Estate,LBS Marg, Lake Road, Bhandup West, Mumbai 400078 + MUMBAI Maharashtra 400078 + IN + +8C-1F-64 (hex) DEUTA Werke GmbH +D2C000-D2CFFF (base 16) DEUTA Werke GmbH + ET + Bergisch Gladbach NRW 51465 + DE + +8C-1F-64 (hex) Johnson and Johnson Medtech +668000-668FFF (base 16) Johnson and Johnson Medtech + 5490 Great America Pkwy + Santa Clara CA 95054 + US + +8C-1F-64 (hex) PROVENRUN +ACF000-ACFFFF (base 16) PROVENRUN + 77 avenue Niel + PARIS 75017 + FR diff --git a/hwdb.d/meson.build b/hwdb.d/meson.build index 36a9937a60a3f..3299eaf8a75bf 100644 --- a/hwdb.d/meson.build +++ b/hwdb.d/meson.build @@ -19,6 +19,7 @@ hwdb_files_notest = files( hwdb_files_test = files( '20-dmi-id.hwdb', '20-net-ifname.hwdb', + '40-imds.hwdb', '60-autosuspend.hwdb', '60-autosuspend-fingerprint-reader.hwdb', '60-evdev.hwdb', @@ -26,6 +27,7 @@ hwdb_files_test = files( '60-keyboard.hwdb', '60-seat.hwdb', '60-sensor.hwdb', + '60-tpm2.hwdb', '70-analyzers.hwdb', '70-av-production.hwdb', '70-cameras.hwdb', diff --git a/hwdb.d/parse_hwdb.py b/hwdb.d/parse_hwdb.py index 1a56f40c46102..e70b0ff04e94e 100755 --- a/hwdb.d/parse_hwdb.py +++ b/hwdb.d/parse_hwdb.py @@ -100,13 +100,14 @@ # Patterns that are used to set general properties on a device GENERAL_MATCHES = {'acpi', 'bluetooth', - 'usb', + 'dmi', + 'ieee1394', + 'OUI', 'pci', 'sdio', + 'tpm2', + 'usb', 'vmbus', - 'OUI', - 'ieee1394', - 'dmi', } def upperhex_word(length): @@ -124,7 +125,7 @@ def hwdb_grammar(): matchline = (matchline_typed | matchline_general) + EOL propertyline = (White(' ', exact=1).suppress() + - Combine(UDEV_TAG - '=' - Optional(Word(alphanums + '_=:@*.!-;, "/')) + Combine(UDEV_TAG - '=' - Optional(Word(alphanums + '_=:@*.!-;, "/?&')) - Optional(pythonStyleComment)) + EOL) propertycomment = White(' ', exact=1) + pythonStyleComment + EOL @@ -147,6 +148,7 @@ def property_grammar(): mount_matrix = Group(mount_matrix_row + ';' + mount_matrix_row + ';' + mount_matrix_row)('MOUNT_MATRIX') xkb_setting = Optional(Word(alphanums + '+-/@._')) id_input_setting = Optional(Or((Literal('0'), Literal('1')))) + zero_one = Or((Literal('0'), Literal('1'))) # Although this set doesn't cover all of characters in database entries, it's enough for test targets. name_literal = Word(printables + ' ') @@ -156,13 +158,13 @@ def property_grammar(): ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER), ('MOUSE_WHEEL_CLICK_COUNT', INTEGER), ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', INTEGER), - ('ID_INPUT_3D_MOUSE', Or((Literal('0'), Literal('1')))), - ('ID_AUTOSUSPEND', Or((Literal('0'), Literal('1')))), + ('ID_INPUT_3D_MOUSE', zero_one), + ('ID_AUTOSUSPEND', zero_one), ('ID_AUTOSUSPEND_DELAY_MS', INTEGER), - ('ID_AV_PRODUCTION_CONTROLLER', Or((Literal('0'), Literal('1')))), - ('ID_AV_LIGHTS', Or((Literal('0'), Literal('1')))), - ('ID_PERSIST', Or((Literal('0'), Literal('1')))), - ('ID_PDA', Or((Literal('0'), Literal('1')))), + ('ID_AV_PRODUCTION_CONTROLLER', zero_one), + ('ID_AV_LIGHTS', zero_one), + ('ID_PERSIST', zero_one), + ('ID_PDA', zero_one), ('ID_INPUT', id_input_setting), ('ID_INPUT_ACCELEROMETER', id_input_setting), ('ID_INPUT_JOYSTICK', id_input_setting), @@ -176,12 +178,12 @@ def property_grammar(): ('ID_INPUT_TOUCHPAD', id_input_setting), ('ID_INPUT_TOUCHSCREEN', id_input_setting), ('ID_INPUT_TRACKBALL', id_input_setting), - ('ID_SIGNAL_ANALYZER', Or((Literal('0'), Literal('1')))), - ('ID_MAKER_TOOL', Or((Literal('0'), Literal('1')))), - ('ID_HARDWARE_WALLET', Or((Literal('0'), Literal('1')))), - ('ID_SOFTWARE_RADIO', Or((Literal('0'), Literal('1')))), - ('ID_MM_DEVICE_IGNORE', Or((Literal('0'), Literal('1')))), - ('ID_NET_AUTO_LINK_LOCAL_ONLY', Or((Literal('0'), Literal('1')))), + ('ID_SIGNAL_ANALYZER', zero_one), + ('ID_MAKER_TOOL', zero_one), + ('ID_HARDWARE_WALLET', zero_one), + ('ID_SOFTWARE_RADIO', zero_one), + ('ID_MM_DEVICE_IGNORE', zero_one), + ('ID_NET_AUTO_LINK_LOCAL_ONLY', zero_one), ('POINTINGSTICK_SENSITIVITY', INTEGER), ('ID_INTEGRATION', Or(('internal', 'external'))), ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))), @@ -193,25 +195,44 @@ def property_grammar(): ('ACCEL_MOUNT_MATRIX', mount_matrix), ('ACCEL_LOCATION', Or(('display', 'base'))), ('PROXIMITY_NEAR_LEVEL', INTEGER), - ('IEEE1394_UNIT_FUNCTION_MIDI', Or((Literal('0'), Literal('1')))), - ('IEEE1394_UNIT_FUNCTION_AUDIO', Or((Literal('0'), Literal('1')))), - ('IEEE1394_UNIT_FUNCTION_VIDEO', Or((Literal('0'), Literal('1')))), + ('IEEE1394_UNIT_FUNCTION_MIDI', zero_one), + ('IEEE1394_UNIT_FUNCTION_AUDIO', zero_one), + ('IEEE1394_UNIT_FUNCTION_VIDEO', zero_one), ('ID_VENDOR_FROM_DATABASE', name_literal), ('ID_MODEL_FROM_DATABASE', name_literal), ('ID_TAG_MASTER_OF_SEAT', Literal('1')), - ('ID_INFRARED_CAMERA', Or((Literal('0'), Literal('1')))), + ('ID_INFRARED_CAMERA', zero_one), ('ID_CAMERA_DIRECTION', Or(('front', 'rear'))), ('SOUND_FORM_FACTOR', Or(('internal', 'webcam', 'speaker', 'headphone', 'headset', 'handset', 'microphone'))), - ('ID_SYS_VENDOR_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_PRODUCT_NAME_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_PRODUCT_VERSION_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_BOARD_VERSION_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_PRODUCT_SKU_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_CHASSIS_ASSET_TAG_IS_RUBBISH', Or((Literal('0'), Literal('1')))), + ('ID_SYS_VENDOR_IS_RUBBISH', zero_one), + ('ID_PRODUCT_NAME_IS_RUBBISH', zero_one), + ('ID_PRODUCT_VERSION_IS_RUBBISH', zero_one), + ('ID_BOARD_VERSION_IS_RUBBISH', zero_one), + ('ID_PRODUCT_SKU_IS_RUBBISH', zero_one), + ('ID_CHASSIS_ASSET_TAG_IS_RUBBISH', zero_one), ('ID_CHASSIS', name_literal), ('ID_SYSFS_ATTRIBUTE_MODEL', name_literal), ('ID_NET_NAME_FROM_DATABASE', name_literal), - ('ID_NET_NAME_INCLUDE_DOMAIN', Or((Literal('0'), Literal('1')))), + ('ID_NET_NAME_INCLUDE_DOMAIN', zero_one), + ('TPM2_BROKEN_NVPCR', zero_one), + ('IMDS_VENDOR', name_literal), + ('IMDS_TOKEN_URL', name_literal), + ('IMDS_REFRESH_HEADER_NAME', name_literal), + ('IMDS_DATA_URL', name_literal), + ('IMDS_DATA_URL_SUFFIX', name_literal), + ('IMDS_TOKEN_HEADER_NAME', name_literal), + ('IMDS_EXTRA_HEADER', name_literal), + ('IMDS_ADDRESS_IPV4', name_literal), + ('IMDS_ADDRESS_IPV6', name_literal), + ('IMDS_KEY_HOSTNAME', name_literal), + ('IMDS_KEY_REGION', name_literal), + ('IMDS_KEY_ZONE', name_literal), + ('IMDS_KEY_IPV4_PUBLIC', name_literal), + ('IMDS_KEY_IPV6_PUBLIC', name_literal), + ('IMDS_KEY_SSH_KEY', name_literal), + ('IMDS_KEY_USERDATA', name_literal), + ('IMDS_KEY_USERDATA_BASE', name_literal), + ('IMDS_KEY_USERDATA_BASE64', name_literal), ) fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE') for name, val in props] diff --git a/hwdb.d/pci.ids b/hwdb.d/pci.ids index 369ef78ff6dce..5dbb806e2e481 100644 --- a/hwdb.d/pci.ids +++ b/hwdb.d/pci.ids @@ -1,8 +1,8 @@ # # List of PCI IDs # -# Version: 2026.02.24 -# Date: 2026-02-24 03:15:02 +# Version: 2026.03.16 +# Date: 2026-03-16 03:15:01 # # Maintained by Albert Pool, Martin Mares, and other volunteers from # the PCI ID Project at https://pci-ids.ucw.cz/. @@ -329,6 +329,9 @@ f130 NetFlex-3/P ThunderLAN 1.0 f150 NetFlex-3/P ThunderLAN 2.3 0e55 HaSoTec GmbH +# Real MediaTek ID is 0x14c3, this actually the USB VID and was used on at least the MT7621 +0e8d MediaTek Inc. (Wrong ID) + 0801 MT7621 PCIe Bridge 0eac SHF Communication Technologies AG 0008 Ethernet Powerlink Managing Node 01 0f62 Acrox Technologies Co., Ltd. @@ -4121,7 +4124,7 @@ 1eae 7901 RX-79XMERCB9 [SPEEDSTER MERC 310 RX 7900 XTX] 1eae 790a RX-79GMERCBR [XFX RX 7900 GRE] 745e Navi 31 [Radeon Pro W7800] - 7460 Navi32 GL-XL [AMD Radeon PRO V710] + 7460 Navi 32 GL-XL [AMD Radeon PRO V710] 7461 Navi 32 [AMD Radeon PRO V710] 7470 Navi 32 [Radeon PRO W7700] 747e Navi 32 [Radeon RX 7700 XT / 7800 XT] @@ -4145,7 +4148,8 @@ 74b9 Aqua Vanjaram [Instinct MI325X VF] 74bd Aqua Vanjaram [Instinct MI300X HF] 7550 Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] - 148c 2435 Reaper Radeon RX 9070 XT 16GB GDDR6 (RX9070XT 16G-A) + 148c 2435 Radeon RX 9070 XT 16GB + 1849 5403 Navi 48 XTX [Steel Legend Radeon RX 9070 XT] 1da2 e490 Navi 48 XTX [Sapphire Pulse Radeon RX 9070 XT] 7551 Navi 48 [Radeon AI PRO R9700] 7590 Navi 44 [Radeon RX 9060 XT] @@ -7821,6 +7825,7 @@ 1020 ISP1020/1040 Fast-wide SCSI 1022 ISP1022 Fast-wide SCSI 1080 ISP1080 SCSI Host Adapter + 1077 0001 QLA1080 1216 ISP12160 Dual Channel Ultra3 SCSI Processor 101e 8471 QLA12160 on AMI MegaRAID 101e 8493 QLA12160 on AMI MegaRAID @@ -13529,7 +13534,7 @@ 2c34 GB203GL [RTX PRO 4000 Blackwell] 2c38 GB203GLM [RTX PRO 5000 Blackwell Generation Laptop GPU] 2c39 GB203GLM [RTX PRO 4000 Blackwell Generation Laptop GPU] - 2c3a GB203GL [RTX PRO 4500 Blackwell] + 2c3a GB203GL [RTX PRO 4500 Blackwell Server Edition] 2c58 GB203M / GN22-X11 [GeForce RTX 5090 Max-Q / Mobile] 2c59 GB203M / GN22-X9 [GeForce RTX 5080 Max-Q / Mobile] 2c77 GB203GLM [RTX PRO 5000 Blackwell Embedded GPU] @@ -17535,7 +17540,7 @@ a000 2000 Parallel Port a000 6000 SPI a000 7000 Local Bus - ea50 1c10 RXi2-BP + ea50 1c10 RXi2-BP Serial Port 9105 AX99100 PCIe to I/O Bridge 125c Aurora Technologies, Inc. 0101 Saturn 4520P @@ -18692,6 +18697,19 @@ 1028 23a6 MTFDLBQ30T7THL-1BK1JABDA 1028 23a7 MTFDLAL61T4THL-1BK1JABDA 1028 23a8 MTFDLAL30T7THL-1BK1JABDA + 51cc 6600 ION NVMe SSD + 1028 2453 MTFDLBQ122T8QHF-1BQ1JABDA + 1028 2483 MTFDLBQ61T4QHF-1BQ1JABDA + 1028 2484 MTFDLBQ30T7QHF-1BQ1JABDA + 1028 2485 MTFDLBQ122T8QHF-1BQ1DFCDA + 1028 2486 MTFDLBQ61T4QHF-1BQ1DFCDA + 1028 2487 MTFDLBQ30T7QHF-1BQ1DFCDA + 1028 2489 MTFDLAL122T8QHF-1BQ1JABDA + 1028 248a MTFDLAL61T4QHF-1BQ1JABDA + 1028 248b MTFDLAL30T7QHF-1BQ1JABDA + 1028 248d MTFDLAL122T8QHF-1BQ1DFCDA + 1028 248e MTFDLAL61T4QHF-1BQ1DFCDA + 1028 248f MTFDLAL30T7QHF-1BQ1DFCDA 51cd 9650 PRO NVMe SSD 5404 2210 NVMe SSD [Cobain] 5405 2300 NVMe SSD [Santana] @@ -20925,6 +20943,7 @@ 7662 MT7662E 802.11ac PCI Express Wireless Network Adapter 7663 MT7663 802.11ac PCI Express Wireless Network Adapter 7902 MT7902 802.11ax PCIe Wireless Network Adapter [Filogic 310] + 7906 MT7916A/MT7916D normal link PCIe Wi-Fi 6(802.11ax) 160MHz 2x2 Wireless Network Adapter [Filogic 630] 7915 MT7915A/MT7915D normal link PCIe Wi-Fi 6(802.11ax) 80MHz 4x4/2x2 Wireless Network Adapter [Filogic 615] # MT7905D/MT7975 contain MT7915. If it works at G1 speed this extra device appears for extra bandwidth 7916 MT7915A/MT7915D hif link PCIe Wi-Fi 6(802.11ax) 80MHz 4x4/2x2 Wireless Network Adapter [Filogic 615] @@ -21584,6 +21603,8 @@ 14e4 9340 BCM57608 4x100G OCP Ethernet NIC 14e4 9345 BCM57608 4x25G OCP Ethernet NIC 14e4 d125 BCM57608 2x200G PCIe Ethernet NIC + 193d 105b NIC-ETH2030F-LP-2P 2x200G PCIe Ethernet NIC + 193d 105c NIC-ETH4030F-LP-1P 1x400G PCIe Ethernet NIC 1800 BCM57502 NetXtreme-E Ethernet Partition 1801 BCM57504 NetXtreme-E Ethernet Partition 1590 0420 Ethernet NPAR 6310C Adapter @@ -21879,6 +21900,7 @@ 5f72 BCM4388 Bluetooth Controller # Bluetooth PCI function of the BRCM4377 Wireless Network Adapter 5fa0 BRCM4377 Bluetooth Controller + 6865 BCM68650 [Aspen] XGSPON OLT 8411 BCM47xx PCIe Bridge 8602 BCM7400/BCM7405 Serial ATA Controller 9026 CN99xx [ThunderX2] Integrated USB 3.0 xHCI Host Controller @@ -25418,6 +25440,7 @@ 15d9 0821 X10DRW-i (AST2400 BMC) 15d9 0832 X10SRL-F (AST2400 BMC) 15d9 086b X10DRS (AST2400 BMC) + 15d9 086d X10SDV (AST2400 BMC) 15d9 1b95 H12SSL-i (AST2500 BMC) 15d9 1d50 X14DBG-AP (AST2600 BMC) 1849 2000 Onboard Graphics @@ -26685,6 +26708,7 @@ 33f3 IM2P33F3 NVMe SSD (DRAM-less) 33f4 IM2P33F4 NVMe SSD (DRAM-less) 33f8 IM2P33F8 series NVMe SSD (DRAM-less) + 413d SM2P41D3Q NVMe SSD (DRAM-less) 41c3 SM2P41C3 NVMe SSD (DRAM-less) 41c8 SM2P41C8 NVMe SSD (DRAM-less) 41d3 SM2P41D3 NVMe SSD (DRAM-less) @@ -28419,7 +28443,7 @@ # aka SED Systems 1e94 Calian SED 1e95 Solid State Storage Technology Corporation - 1000 XA1-311024 NVMe SSD M.2 + 1000 XA1 Series NVMe SSD M.2 (DRAM-less) 1001 CA6-8D512 NVMe SSD M.2 1002 NVMe SSD [3DNAND] 2.5" U.2 (LJ1) 1e95 1101 NVMe SSD [3DNAND] 2.5" U.2 (LJ1) @@ -28427,7 +28451,7 @@ 1003 CLR-8W512 NVMe SSD M.2 (DRAM-less) 1005 PLEXTOR M10P(GN) NVMe SSD M.2 1006 CA8 Series NVMe SSD M.2 - 1007 CL4-8D512 NVMe SSD M.2 (DRAM-less) + 1007 CL4 Series NVMe SSD M.2 (DRAM-less) 1008 CL5-8D512 NVMe SSD M.2 (DRAM-less) 100b XB2-311024 NVMe SSD M.2 (DRAM-less) 100c CL6 Series NVMe SSD M.2 (DRAM-less) @@ -28945,6 +28969,7 @@ 3504 M18305 Family BASE-T 1f0f 0001 S2025XT, 2x 10GbE, Base-T, PCIe 4.0 x8, Fan 1f0f 0002 S2025XT, 2x 10GbE, Base-T, PCIe 4.0 x8 + 1f0f 0003 S2045XT, 4x 10GbE, Base-T, PCIe 4.0 x8 350a M18305 Family Virtual Function 1f0f 0001 M18305 Family Virtual Function 9088 D1055AS PCI Express Switch Downstream Port @@ -28990,6 +29015,8 @@ 4512 NE1N NVMe SSD 451b NN4LE NVMe SSD (DRAM-less) 4622 NEM-PAC NVMe SSD (DRAM-less) +1f32 Wuhan YuXin Semiconductor Co., Ltd. + ed55 U800G NVMe SSD 1f3f 3SNIC Ltd 2100 SSSHBA SAS/SATA HBA 1f3f 0120 HBA 32 Ports @@ -29419,6 +29446,10 @@ 10a1 NIC1160 Ethernet Controller Family 1ff2 0c11 10GE Ethernet Adapter 1160-2X 10a2 NIC1160 Ethernet Controller Virtual Function Family + 10b1 NIC 1260 Ethernet Controller Family + 10b2 NIC 1260 Ethernet Controller Virtual Function Family + 10b3 NIC 1260C Ethernet Controller Family + 10b4 NIC 1260C Ethernet Controller Virtual Function Family 20a1 IOC2110 Storage Controller 1ff2 0a11 2120-16i SATA3/SAS3 HBA Adapter 1ff2 0a12 2120-8i SATA3/SAS3 HBA Adapter @@ -29684,15 +29715,22 @@ 7101 LS X710-E 7103 LS X710-M 7104 LS X710-P + 7180 LS X718 + 7211 LS X721-E + 7223 LS X722-M + 7224 LS X722-P 20e3 Elix Systems SA 20e7 TOPSSD 20f4 TRENDnet 20f6 Shenzhen Zhishi Network Technology Co., Ltd. 0001 MPU H1 20f9 Shenzhen Silicon Dynamic Networks Co., Ltd. +2105 Shanghai Timar Integrated Circuit Co., LTD 2106 ZCHL Technology Co., Ltd 0001 HL100 Accelerator Controller 2106 0001 HLC100 Accelerator Card +# HXQ +2108 HuiLink Technologies (Xiamen) Co., Ltd. 2116 ZyDAS Technology Corp. 21b4 Hunan Goke Microelectronics Co., Ltd 21c3 21st Century Computer Corp. @@ -39990,6 +40028,24 @@ d156 Core Processor Semaphore and Scratchpad Registers d157 Core Processor System Control and Status Registers d158 Core Processor Miscellaneous Registers + d323 Nova Lake PCD-H SPI Controller + d325 Nova Lake PCD-H Serial IO UART Controller #0 + d326 Nova Lake PCD-H Serial IO UART Controller #1 + d327 Nova Lake PCD-H Serial IO SPI Controller #0 + d330 Nova Lake PCD-H Serial IO SPI Controller #1 + d331 Nova Lake-H Thunderbolt 5 USB Controller + d333 Nova Lake-H Thunderbolt 5 NHI #0 + d347 Nova Lake PCD-H Serial IO SPI Controller #2 + d34e Nova Lake-H Thunderbolt 5 PCI Express Root Port #0 + d34f Nova Lake-H Thunderbolt 5 PCI Express Root Port #1 + d350 Nova Lake PCD-H Serial IO I2C Controller #4 + d351 Nova Lake PCD-H Serial IO I2C Controller #5 + d352 Nova Lake PCD-H Serial IO UART Controller #2 + d360 Nova Lake-H Thunderbolt 5 PCI Express Root Port #2 + d378 Nova Lake PCD-H Serial IO I2C Controller #0 + d379 Nova Lake PCD-H Serial IO I2C Controller #1 + d37a Nova Lake PCD-H Serial IO I2C Controller #2 + d37b Nova Lake PCD-H Serial IO I2C Controller #3 d431 Nova Lake-S Thunderbolt 5 USB Controller d433 Nova Lake-S Thunderbolt 5 NHI #0 d44e Nova Lake-S Thunderbolt 5 PCI Express Root Port #0 @@ -40001,6 +40057,15 @@ d743 NVL-HX d744 NVL-UL d745 NVL-HX + d750 NVL-P + d751 NVL-P + d752 NVL-P + d753 NVL-P + d754 NVL-P + d755 NVL-P + d756 NVL-P + d757 NVL-P + d75f NVL-P e202 Battlemage G21 [Intel Graphics] e20b Battlemage G21 [Arc B580] e20c Battlemage G21 [Arc B570] diff --git a/man/SD_ELF_NOTE_DLOPEN.xml b/man/SD_ELF_NOTE_DLOPEN.xml new file mode 100644 index 0000000000000..0e3eed84cfd09 --- /dev/null +++ b/man/SD_ELF_NOTE_DLOPEN.xml @@ -0,0 +1,143 @@ + + + + + + + + SD_ELF_NOTE_DLOPEN + systemd + + + + SD_ELF_NOTE_DLOPEN + 3 + + + + SD_ELF_NOTE_DLOPEN + SD_ELF_NOTE_DLOPEN_VENDOR + SD_ELF_NOTE_DLOPEN_TYPE + SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED + + Embed ELF .note.dlopen metadata for shared library dependencies + + + + + #include <systemd/sd-dlopen.h> + + SD_ELF_NOTE_DLOPEN(feature, description, priority, soname...) + + #define SD_ELF_NOTE_DLOPEN_VENDOR "FDO" + #define SD_ELF_NOTE_DLOPEN_TYPE UINT32_C(0x407c0c0a) + #define SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED "required" + #define SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED "recommended" + #define SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED "suggested" + + + + + + Description + + SD_ELF_NOTE_DLOPEN() is a macro that embeds a + .note.dlopen ELF note section in the compiled binary, declaring a weak dependency + on a shared library loaded via dlopen(). This implements the + ELF dlopen + metadata specification, allowing package managers and build systems to discover runtime + dependencies that are not visible through regular ELF DT_NEEDED entries. + + The macro takes the following parameters: + + + + feature + A short string identifying the feature this library provides (e.g. + XKB, PCRE2). + + + + + description + A human-readable description of what the library is used for. + + + + + priority + One of SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, or + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, indicating how important the + dependency is. + + + + + soname... + One or more shared object names (sonames) for the library, e.g. + libfoo.so.1. Multiple sonames may be specified as separate arguments (up to 5) + for libraries that have changed soname across versions. + + + + + The embedded metadata can be read from a compiled ELF binary using: + systemd-analyze dlopen-metadata binary + + + + + Examples + + + Single soname + #include <systemd/sd-dlopen.h> + +SD_ELF_NOTE_DLOPEN("XKB", "Keyboard layout support", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libxkbcommon.so.0"); + + + + Multiple sonames for different library versions + SD_ELF_NOTE_DLOPEN("crypt", "Support for hashing passwords", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libcrypt.so.2", "libcrypt.so.1", "libcrypt.so.1.1"); + + + + + + Notes + + The macros described here are header-only and do not require runtime linkage against + libsystemd3. + Only the installed header and include path (as provided by + pkg-config --cflags libsystemd) are needed. + + + + History + SD_ELF_NOTE_DLOPEN(), + SD_ELF_NOTE_DLOPEN_VENDOR, + SD_ELF_NOTE_DLOPEN_TYPE, + SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, and + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED were added in version 261. + + + + See Also + + + systemd1 + sd-dlopen3 + dlopen3 + + + + diff --git a/man/bootctl.xml b/man/bootctl.xml index 5f53023c83d27..558891eaa1169 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -406,6 +406,15 @@ + + + + Print the EFI architecture string of the local firmware. This is useful to + generically format filenames such as bootx64.efi that + include the local firmware architecture in the name. + + + Controls whether to touch the firmware's boot loader list stored in EFI variables, diff --git a/man/clonetab.xml b/man/clonetab.xml new file mode 100644 index 0000000000000..e19777f59237e --- /dev/null +++ b/man/clonetab.xml @@ -0,0 +1,119 @@ + + + + + + + + clonetab + systemd + + + + clonetab + 5 + + + + clonetab + Configuration for dm-clone block devices + + + + /etc/clonetab + + + + Description + + The /etc/clonetab file describes + dm-clone block devices that are set up during system boot. + + Empty lines and lines starting with the # + character are ignored. Each of the remaining lines describes one + dm-clone device. Fields are delimited by white space. + + Each line is in the formname source-device destination-device metadata-device [options] + The first four fields are mandatory, the fifth is optional. + + The four fields of /etc/clonetab are defined as follows: + + + + The first field contains the name of the resulting dm-clone device; its + block device is set up below /dev/mapper/. + + The second field contains a path to the read-only source block device. + This is the device whose data is cloned to the destination device. Reads to regions not + yet hydrated are served directly from this device. + + The third field contains a path to the writable destination block device. + The source device's data is copied here in the background. It must be at least as + large as the source device. + + The fourth field contains a path to the metadata block device. This + small device tracks which regions of the destination have been hydrated and is managed + exclusively by dm-clone. + + The fifth field, if present, contains comma-separated key=value + options. The following option is supported: + + + + + Controls the granularity of background hydration copying — how much + data is copied at a time. Region size is specified in 512-byte sectors, so + region_size=8 means 8 × 512 = 4KB per region. One region is the + atomic unit dm-clone tracks: it is either fully hydrated (copied to the destination) + or not, never partially. If a copy is interrupted mid-region, that whole region is + retried from scratch on next boot. Smaller regions mean finer progress tracking; + larger regions reduce metadata overhead. Must be a power of two between + 8 and 2097152 sectors. Defaults to + 8 (4KB). For background, see + dm-clone kernel documentation. + Device mapper sectors are always 512 bytes regardless of physical block size + (include/linux/blk_types.h: SECTOR_SIZE = 1 << 9). + + + + + If no options are needed, the field may be omitted entirely, + - may be used as a placeholder, or a quoted empty string + "" may be used (it is silently ignored). + + + + At early boot and when the system manager configuration is reloaded, this file is + translated into native systemd units by + systemd-clonesetup-generator8. + + + + Examples + + + /etc/clonetab + Clone a source device to a destination, using a separate metadata device: + mydevice /dev/sdb /dev/sdc /dev/sdd + + + + /etc/clonetab + Clone a source device to a destination, using a separate metadata device: + mydevice /dev/sdb /dev/sdc /dev/sdd region_size=16 + + + + + See Also + + systemd1 + systemd-clonesetup-generator8 + dmsetup8 + + + + diff --git a/man/homectl.xml b/man/homectl.xml index a59efd7112ee9..bb827d1e6ba96 100644 --- a/man/homectl.xml +++ b/man/homectl.xml @@ -366,6 +366,16 @@ + + + + Takes a birth date for the user in ISO 8601 calendar date format + (YYYY-MM-DD). The earliest representable year is 1900. If an empty string is + passed the birth date is reset to unset. + + + + diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 4da1796a97ca2..83544b3606464 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -69,6 +69,7 @@ systemd.import_credentials= systemd.reload_limit_interval_sec= systemd.reload_limit_burst= + systemd.minimum_uptime_sec= Parameters understood by the system and service manager to control system behavior. For details, see @@ -710,8 +711,8 @@ systemd.allow_userspace_verity= Takes a boolean argument. Controls whether disk images that are Verity protected may - be authenticated in userspace signature checks via /etc/verity.d/ (and related - directories) public key drop-ins, or whether in-kernel signature checking only. Defaults to + be authenticated in userspace signature checks via /etc/verity.d/*.crt (and + related directories) public key drop-ins, or whether in-kernel signature checking only. Defaults to on. @@ -782,6 +783,28 @@ + + systemd.tpm2_software_fallback= + + Controls whether to start a fallback software TPM service in case a hardware TPM is + not available, implemented by + systemd-tpm2-generator8. + + + + + + systemd.tpm2_measured_os= + + Controls whether to execute various boot and runtime TPM PCR measurements. Takes a + boolean argument. If not specified explicitly this behaviour is enabled automatically in case + systemd-stub7 is + used and it succeeded in doing pre-boot measurements of the booted UKI, and otherwise + disabled. + + + + systemd.factory_reset= @@ -793,6 +816,35 @@ + + systemd.imds= + systemd.imds.*= + + Controls various Instance Metadata Service (IMDS) cloud aspects, see + systemd-imdsd@.service8 + and + systemd-imds-generator8 + for details. + + + + + + systemd.sysext= + systemd.confext= + rd.systemd.sysext= + rd.systemd.confext= + + Take boolean arguments, default to on. Control whether system and configuration + extensions for the initrd (rd.systemd.sysext=, rd.systemd.confext=) + and for the main system (systemd.sysext=, systemd.confext=) are + merged automatically on boot. See + systemd-sysext8 + for details. + + + + diff --git a/man/kernel-install.xml b/man/kernel-install.xml index 38c183be243e3..0bf57b4d5341e 100644 --- a/man/kernel-install.xml +++ b/man/kernel-install.xml @@ -616,9 +616,9 @@ /proc/cmdline Specifies the kernel command line to use. The first of the files that is found will be used. - When running in a container, /proc/cmdline is ignored. - $KERNEL_INSTALL_CONF_ROOT may be used to override the search path; see below for - details. + Lines starting with the # character are ignored. When running in a container, + /proc/cmdline is ignored. $KERNEL_INSTALL_CONF_ROOT may be + used to override the search path; see below for details. diff --git a/man/machinectl.xml b/man/machinectl.xml index e64a20bb1d045..b4fb15b4f93a3 100644 --- a/man/machinectl.xml +++ b/man/machinectl.xml @@ -262,16 +262,17 @@ poweroff NAME - Power off one or more containers. This will - trigger a shutdown by sending SIGRTMIN+4 to the container's init - process, which causes systemd-compatible init systems to shut - down cleanly. Use stop as alias for poweroff. - This operation does not work on containers that do not run a + Power off one or more machines. For VMs managed by + systemd-vmspawn1, + this sends an ACPI powerdown request via QMP. For containers, this + sends SIGRTMIN+4 to the container's init process, which causes + systemd-compatible init systems to shut down cleanly. This + operation does not work on containers that do not run a systemd1-compatible init system, such as sysvinit. Use - terminate (see below) to immediately - terminate a container or VM, without cleanly shutting it - down. + stop as alias for poweroff. + Use terminate (see below) to immediately + terminate a machine without cleanly shutting it down. @@ -279,16 +280,40 @@ reboot NAME - Reboot one or more containers. This will - trigger a reboot by sending SIGINT to the container's init - process, which is roughly equivalent to pressing Ctrl+Alt+Del - on a non-containerized system, and is compatible with - containers running any system manager. Use restart as alias - for reboot. + Reboot one or more machines. For VMs managed by + systemd-vmspawn1, + this sends a system reset request via QMP. For containers, this + sends SIGINT to the container's init process, which is roughly + equivalent to pressing Ctrl+Alt+Del on a non-containerized + system, and is compatible with containers running any system + manager. Use restart as alias for + reboot. + + pause NAME + + Pause one or more machines. For VMs managed by + systemd-vmspawn1, + this freezes vCPU execution at the hypervisor level — the guest + operating system is not notified and does not observe an ACPI suspend. + From the guest's perspective time simply stops until the machine is + resumed with resume. + + + + + + resume NAME + + Resume one or more previously paused machines. + This restarts execution after a pause. + + + + terminate NAME diff --git a/man/oomd.conf.xml b/man/oomd.conf.xml index a4be5e1274ff9..f8c3c0a173e15 100644 --- a/man/oomd.conf.xml +++ b/man/oomd.conf.xml @@ -62,7 +62,7 @@ Note that this is a privileged option as, even if it has a timeout, is synchronous and delays the kill, so use with care. The typically preferable mechanism to process memory pressure is to do what - MEMORY_PRESSURE describes which is unprivileged, + Resource Pressure Handling describes which is unprivileged, asynchronous and does not delay the kill. diff --git a/man/org.freedesktop.hostname1.xml b/man/org.freedesktop.hostname1.xml index c70258459c246..3d98b88ebc177 100644 --- a/man/org.freedesktop.hostname1.xml +++ b/man/org.freedesktop.hostname1.xml @@ -60,6 +60,8 @@ node /org/freedesktop/hostname1 { out ay uuid); GetHardwareSerial(out s serial); Describe(out s json); + GetMachineInfo(in s field, + out s value); properties: readonly s Hostname = '...'; readonly s StaticHostname = '...'; @@ -146,6 +148,8 @@ node /org/freedesktop/hostname1 { + + @@ -377,6 +381,13 @@ node /org/freedesktop/hostname1 { access to unprivileged clients through the polkit framework. Describe() returns a JSON representation of all properties in one. + + GetMachineInfo() returns the value of the given field from + /etc/machine-info. For well-known fields, this method reads values from the same + internal cache used by the corresponding D-Bus property getters, but returns only the raw + /etc/machine-info values (i.e. without property-level fallback logic such as + DMI/chassis-based detection). Custom (non-standard) fields are read directly from the file. + An error is returned if the field name is empty, invalid, or not set in the file. @@ -494,6 +505,7 @@ node /org/freedesktop/hostname1 { OperatingSystemImageVersion, HardwareSKU, and HardwareVersion were added in version 258. OperatingSystemFancyName was added in version 260. + GetMachineInfo() was added in version 261. diff --git a/man/org.freedesktop.network1.xml b/man/org.freedesktop.network1.xml index 0b7a6b5ed3d11..1c3abcad23068 100644 --- a/man/org.freedesktop.network1.xml +++ b/man/org.freedesktop.network1.xml @@ -458,6 +458,10 @@ node /org/freedesktop/network1/link/_1 { interface org.freedesktop.network1.DHCPServer { properties: readonly a(uayayayayt) Leases = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly u PoolSize = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly u PoolOffset = ...; }; interface org.freedesktop.DBus.Peer { ... }; interface org.freedesktop.DBus.Introspectable { ... }; @@ -466,8 +470,6 @@ node /org/freedesktop/network1/link/_1 { }; - - @@ -480,10 +482,23 @@ node /org/freedesktop/network1/link/_1 { + + + + - Provides information about leases. + Provides information about the DHCP server. The Leases property contains + the currently active leases. The PoolSize property contains the total number + of addresses in the dynamic address pool. The PoolOffset property contains + the offset from the subnet base address where the pool starts. These correspond to the + PoolSize= and PoolOffset= settings in + systemd.network5. + UINT32_MAX is used as a sentinel value for PoolSize + and PoolOffset to indicate that the information is unavailable (i.e. no + DHCP server is configured or the link is in relay mode), rather than a valid pool size or + offset. @@ -589,6 +604,10 @@ $ gdbus introspect --system \ History + + DHCP Server Object + PoolSize and PoolOffset were added in version 261. + DHCPv4 Client Object State was added in version 255. diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index f4a06901b0368..76a8dd045f6c6 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -552,6 +552,14 @@ node /org/freedesktop/systemd1 { readonly t DefaultMemoryPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s DefaultMemoryPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t DefaultCPUPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s DefaultCPUPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t DefaultIOPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s DefaultIOPressureWatch = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly t TimerSlackNSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") @@ -564,6 +572,8 @@ node /org/freedesktop/systemd1 { readonly s CtrlAltDelBurstAction = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly u SoftRebootsCount = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly b DefaultMemoryZSwapWriteback = ...; }; interface org.freedesktop.DBus.Peer { ... }; interface org.freedesktop.DBus.Introspectable { ... }; @@ -791,6 +801,14 @@ node /org/freedesktop/systemd1 { + + + + + + + + @@ -801,6 +819,8 @@ node /org/freedesktop/systemd1 { + + @@ -1239,6 +1259,14 @@ node /org/freedesktop/systemd1 { + + + + + + + + @@ -1251,6 +1279,8 @@ node /org/freedesktop/systemd1 { + + @@ -3060,6 +3090,14 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t CPUPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s IOPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t IOPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -3729,6 +3767,14 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + + + + + + + @@ -4421,6 +4467,14 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + + + + + + + @@ -5320,6 +5374,14 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t CPUPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s IOPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t IOPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -6005,6 +6067,14 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + + + + + + + @@ -6671,6 +6741,14 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + + + + + + + @@ -7393,6 +7471,14 @@ node /org/freedesktop/systemd1/unit/home_2emount { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t CPUPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s IOPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t IOPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -8002,6 +8088,14 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + + + + + + @@ -8576,6 +8670,14 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + + + + + + @@ -9431,6 +9533,14 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t CPUPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s IOPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t IOPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -10022,6 +10132,14 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + + + + + + + @@ -10578,6 +10696,14 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + + + + + + + @@ -11286,6 +11412,14 @@ node /org/freedesktop/systemd1/unit/system_2eslice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t CPUPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s IOPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t IOPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -11459,6 +11593,14 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + + + + + + + @@ -11647,6 +11789,14 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + + + + + + + @@ -11858,6 +12008,14 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t CPUPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s IOPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t IOPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -12045,6 +12203,14 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + + + + + + + @@ -12257,6 +12423,14 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + + + + + + + @@ -12469,6 +12643,11 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ RemoveSubgroupFromUnit(), and KillUnitSubgroup() were added in version 258. TransactionsWithOrderingCycle was added in version 259. + DefaultMemoryZSwapWriteback, + DefaultCPUPressureThresholdUSec, + DefaultCPUPressureWatch, + DefaultIOPressureThresholdUSec, and + DefaultIOPressureWatch were added in version 261. Unit Objects @@ -12560,6 +12739,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ExecReloadPostEx were added in version 259. BindNetworkInterface, MemoryTHP, RefreshOnReload, and RootMStack were added in version 260. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, and + IOPressureWatch were added in version 261. Socket Unit Objects @@ -12630,6 +12813,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ManagedOOMKills were added in 259. BindNetworkInterface MemoryTHP, and RootMStack were added in version 260. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, and + IOPressureWatch were added in version 261. Mount Unit Objects @@ -12695,6 +12882,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ManagedOOMKills were added in 259. BindNetworkInterface MemoryTHP, and RootMStack were added in version 260. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, and + IOPressureWatch were added in version 261. Swap Unit Objects @@ -12758,6 +12949,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ManagedOOMKills were added in 259. BindNetworkInterface, MemoryTHP, and RootMStack were added in version 260. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, and + IOPressureWatch were added in version 261. Slice Unit Objects @@ -12791,6 +12986,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ OOMKills, and ManagedOOMKills were added in 259. BindNetworkInterface was added in version 260. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, and + IOPressureWatch were added in version 261. Scope Unit Objects @@ -12822,6 +13021,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ OOMKills, and ManagedOOMKills were added in 259. BindNetworkInterface was added in version 260. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, and + IOPressureWatch were added in version 261. Job Objects diff --git a/man/os-release.xml b/man/os-release.xml index 8b652d78f0253..94617ae243d6f 100644 --- a/man/os-release.xml +++ b/man/os-release.xml @@ -750,8 +750,9 @@ VERSION_ID=32 - See docs for - platform.freedesktop_os_release for more details. + See docs for platform.freedesktop_os_release + for more details. diff --git a/man/repart.d.xml b/man/repart.d.xml index 7d3dc4e04b254..028929ac9e842 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -402,7 +402,7 @@ be empty. If this option is used, the size allocation algorithm is slightly altered: the partition is created at least as big as required to fit the data in, i.e. the data size is an additional minimum size value taken into consideration for the allocation algorithm, similar to and in addition to the - SizeMin= value configured above. + SizeMinBytes= value configured above. This option has no effect if the partition it is declared for already exists, i.e. existing data is never overwritten. Note that the data is copied in before the partition table is updated, @@ -679,6 +679,46 @@ + + Discard= + + Takes a boolean argument. The default is no if + is used for the invocation of systemd-repart + or if Integrity=inline is set. It is yes otherwise. + + If set to yes, when creating the LUKS2 superblock for the partition, the + allow-discards activation flag will be set so that future activations will allow + discards by default. + + This option has no effect if the partition already exists or if Encrypt=off is used. + + + + + + EncryptKDF= + + Specifies the key derivation function (KDF) to use for LUKS2 encryption keyslots. + Takes one of argon2id, pbkdf2, or minimal. + If not specified, the default is determined by the cryptsetup library (typically + argon2id). This option has no effect if Encrypt= is + off. + + When set to argon2id or pbkdf2, the specified KDF is + used with parameters benchmarked by the cryptsetup library. When set to minimal, + PBKDF2 is used with SHA-512, 1000 iterations, and no benchmarking — this is appropriate for + high-entropy keys (e.g. generated by a hardware key manager or sealed to a TPM) where the KDF only + needs to satisfy the LUKS2 format requirement, not strengthen a weak passphrase. + + Note that Argon2-based KDFs may require significant memory (up to 1GB) during key derivation. + In memory-constrained environments such as kdump with limited crashkernel memory, + minimal or pbkdf2 may be more appropriate. When + Encrypt= includes tpm2, the TPM2 keyslot always uses a minimal + PBKDF2 configuration regardless of this setting. + + + + Verity= @@ -826,9 +866,9 @@ off when false, and best when true). Defaults to off. If set to best, the partition will have the minimal size required to store the sources configured with CopyFiles=. best - is currently only supported for read-only filesystems. If set to guess, the - partition is created at least as big as required to store the sources configured with - CopyFiles=. Note that unless the filesystem is a read-only filesystem, + is currently only supported for read-only filesystems and btrfs. If set to guess, + the partition is created at least as big as required to store the sources configured with + CopyFiles=. Note that unless the filesystem is a read-only filesystem or btrfs, systemd-repart will have to populate the filesystem twice to guess the minimal required size, so enabling this option might slow down repart when populating large partitions. diff --git a/man/resolved.conf.xml b/man/resolved.conf.xml index 9adc0143c7c05..f8899fe662c95 100644 --- a/man/resolved.conf.xml +++ b/man/resolved.conf.xml @@ -363,12 +363,33 @@ DNSStubListenerExtra=udp:[2001:db8:0:f102::13]:9953 ReadEtcHosts= Takes a boolean argument. If yes (the default), systemd-resolved will read /etc/hosts, and try to resolve - hosts or address by using the entries in the file before sending query to DNS servers. + hosts or addresses by using the entries in the file before sending query to DNS servers. + + ReadStaticRecords= + Takes a boolean argument. If yes (the default), + systemd-resolved will read + /etc/systemd/resolve/static.d/*.rr, + /run/systemd/resolve/static.d/*.rr, + /usr/local/lib/systemd/resolve/static.d/*.rr, + /usr/lib/systemd/resolve/static.d/*.rr, and try to resolve lookups by using the + entries in these files before sending query to DNS servers. This functionality is very similar to the + one controlled by ReadEtcHosts=, but allows more flexible control of DNS resource + records fields beyond just A/AAAA/PTR. See + systemd.rr5 for + details. + + If both this option and ReadEtcHosts= are enabled then this mechanism takes + precedence: any records discovered via static resource records will take precedence over records + under the same name from /etc/hosts. + + + + ResolveUnicastSingleLabel= Takes a boolean argument. When false (the default), @@ -418,6 +439,7 @@ DNSStubListenerExtra=udp:[2001:db8:0:f102::13]:9953 systemd-resolved.service8 systemd-networkd.service8 dnssec-trust-anchors.d5 + systemd.rr5 resolv.conf5 diff --git a/man/rules/meson.build b/man/rules/meson.build index d69793150be97..34572f22279d1 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -10,6 +10,7 @@ manpages = [ ['coredump.conf', '5', ['coredump.conf.d'], 'ENABLE_COREDUMP'], ['coredumpctl', '1', [], 'ENABLE_COREDUMP'], ['crypttab', '5', [], 'HAVE_LIBCRYPTSETUP'], + ['clonetab', '5', [], ''], ['daemon', '7', [], ''], ['dnssec-trust-anchors.d', '5', @@ -127,6 +128,7 @@ manpages = [ 'SD_WARNING'], ''], ['sd-device', '3', [], ''], + ['sd-dlopen', '3', [], ''], ['sd-event', '3', [], ''], ['sd-hwdb', '3', [], ''], ['sd-id128', @@ -154,6 +156,14 @@ manpages = [ ['sd-login', '3', [], 'HAVE_PAM'], ['sd-path', '3', [], ''], ['sd-varlink', '3', [], ''], + ['SD_ELF_NOTE_DLOPEN', + '3', + ['SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED', + 'SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED', + 'SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED', + 'SD_ELF_NOTE_DLOPEN_TYPE', + 'SD_ELF_NOTE_DLOPEN_VENDOR'], + ''], ['sd_booted', '3', [], ''], ['sd_bus_add_match', '3', @@ -599,7 +609,13 @@ manpages = [ ''], ['sd_event_add_memory_pressure', '3', - ['sd_event_source_set_memory_pressure_period', + ['sd_event_add_cpu_pressure', + 'sd_event_add_io_pressure', + 'sd_event_source_set_cpu_pressure_period', + 'sd_event_source_set_cpu_pressure_type', + 'sd_event_source_set_io_pressure_period', + 'sd_event_source_set_io_pressure_type', + 'sd_event_source_set_memory_pressure_period', 'sd_event_source_set_memory_pressure_type', 'sd_event_trim_memory'], ''], @@ -852,6 +868,17 @@ manpages = [ 'sd_json_dispatch_variant', 'sd_json_dispatch_variant_noref'], ''], + ['sd_json_parse', + '3', + ['SD_JSON_PARSE_MUST_BE_ARRAY', + 'SD_JSON_PARSE_MUST_BE_OBJECT', + 'SD_JSON_PARSE_SENSITIVE', + 'sd_json_parse_continue', + 'sd_json_parse_file', + 'sd_json_parse_file_at', + 'sd_json_parse_with_source', + 'sd_json_parse_with_source_continue'], + ''], ['sd_listen_fds', '3', ['SD_LISTEN_FDS_START', 'sd_listen_fds_with_names'], @@ -987,6 +1014,7 @@ manpages = [ ['systemd-creds', '1', [], ''], ['systemd-cryptenroll', '1', [], 'HAVE_LIBCRYPTSETUP'], ['systemd-cryptsetup-generator', '8', [], 'HAVE_LIBCRYPTSETUP'], + ['systemd-clonesetup-generator', '8', [], ''], ['systemd-cryptsetup', '8', ['systemd-cryptsetup@.service'], @@ -1025,6 +1053,14 @@ manpages = [ ['systemd-hostnamed.service', '8', ['systemd-hostnamed'], 'ENABLE_HOSTNAMED'], ['systemd-hwdb', '8', [], 'ENABLE_HWDB'], ['systemd-id128', '1', [], ''], + ['systemd-imds-generator', '8', [], 'ENABLE_IMDS'], + ['systemd-imds', '1', ['systemd-imds-import.service'], 'ENABLE_IMDS'], + ['systemd-imdsd@.service', + '8', + ['systemd-imdsd', + 'systemd-imdsd-early-network.service', + 'systemd-imdsd.socket'], + 'ENABLE_IMDS'], ['systemd-import-generator', '8', [], ''], ['systemd-importd.service', '8', ['systemd-importd'], 'ENABLE_IMPORTD'], ['systemd-inhibit', '1', [], ''], @@ -1110,6 +1146,7 @@ manpages = [ 'systemd-pcrfs@.service', 'systemd-pcrmachine.service', 'systemd-pcrnvdone.service', + 'systemd-pcrosseparator.service', 'systemd-pcrphase-initrd.service', 'systemd-pcrphase-sysinit.service', 'systemd-pcrproduct.service'], @@ -1168,8 +1205,10 @@ manpages = [ '8', ['systemd-confext', 'systemd-confext-initrd.service', + 'systemd-confext-sysroot.service', 'systemd-confext.service', 'systemd-sysext-initrd.service', + 'systemd-sysext-sysroot.service', 'systemd-sysext.service'], 'ENABLE_SYSEXT'], ['systemd-system-update-generator', '8', [], ''], @@ -1209,6 +1248,10 @@ manpages = [ '8', ['systemd-tpm2-setup', 'systemd-tpm2-setup-early.service'], 'ENABLE_BOOTLOADER'], + ['systemd-tpm2-swtpm.service', + '8', + ['systemd-tpm2-swtpm'], + 'ENABLE_BOOTLOADER'], ['systemd-tty-ask-password-agent', '1', [], ''], ['systemd-udev-settle.service', '8', [], ''], ['systemd-udevd.service', @@ -1260,6 +1303,7 @@ manpages = [ ['systemd.pcrlock', '5', ['systemd.pcrlock.d'], ''], ['systemd.preset', '5', [], ''], ['systemd.resource-control', '5', [], ''], + ['systemd.rr', '5', [], 'ENABLE_RESOLVE'], ['systemd.scope', '5', [], ''], ['systemd.service', '5', [], ''], ['systemd.slice', '5', [], ''], diff --git a/man/sd-dlopen.xml b/man/sd-dlopen.xml new file mode 100644 index 0000000000000..ed43e396d69bf --- /dev/null +++ b/man/sd-dlopen.xml @@ -0,0 +1,80 @@ + + + + + + + + sd-dlopen + systemd + + + + sd-dlopen + 3 + + + + sd-dlopen + ELF dlopen metadata annotation macros + + + + + #include <systemd/sd-dlopen.h> + + + + pkg-config --cflags libsystemd + + + + + + Description + + sd-dlopen.h provides macros for embedding + .note.dlopen metadata in ELF binaries, implementing the + ELF dlopen + metadata specification for declaring optional shared library dependencies that are loaded via + dlopen3 + at runtime. + + The header is self-contained and does not require runtime linkage against + libsystemd3. + Projects only need the installed header to use the macros. + + Package managers and build systems can read the embedded ELF notes to discover runtime + dependencies that are not visible in ELF DT_NEEDED entries. + + See + SD_ELF_NOTE_DLOPEN3 + for details on the available macros and constants. + + + + Notes + + The macros described here are header-only and do not require runtime linkage against + libsystemd3. + Only the installed header and include path (as provided by + pkg-config --cflags libsystemd) are needed. + + + + History + SD_ELF_NOTE_DLOPEN() and associated macros and constants were added in + version 261. + + + + See Also + + systemd1 + SD_ELF_NOTE_DLOPEN3 + dlopen3 + + + + diff --git a/man/sd_event_add_memory_pressure.xml b/man/sd_event_add_memory_pressure.xml index b112855f061b0..e472e620439c9 100644 --- a/man/sd_event_add_memory_pressure.xml +++ b/man/sd_event_add_memory_pressure.xml @@ -21,7 +21,15 @@ sd_event_source_set_memory_pressure_period sd_event_trim_memory - Add and configure an event source run as result of memory pressure + sd_event_add_cpu_pressure + sd_event_source_set_cpu_pressure_type + sd_event_source_set_cpu_pressure_period + + sd_event_add_io_pressure + sd_event_source_set_io_pressure_type + sd_event_source_set_io_pressure_period + + Add and configure an event source for memory, CPU, or IO pressure notifications @@ -51,6 +59,48 @@ uint64_t window_usec + + int sd_event_add_cpu_pressure + sd_event *event + sd_event_source **ret_source + sd_event_handler_t handler + void *userdata + + + + int sd_event_source_set_cpu_pressure_type + sd_event_source *source + const char *type + + + + int sd_event_source_set_cpu_pressure_period + sd_event_source *source + uint64_t threshold_usec + uint64_t window_usec + + + + int sd_event_add_io_pressure + sd_event *event + sd_event_source **ret_source + sd_event_handler_t handler + void *userdata + + + + int sd_event_source_set_io_pressure_type + sd_event_source *source + const char *type + + + + int sd_event_source_set_io_pressure_period + sd_event_source *source + uint64_t threshold_usec + uint64_t window_usec + + int sd_event_trim_memory void @@ -62,18 +112,25 @@ Description sd_event_add_memory_pressure() adds a new event source that is triggered - whenever memory pressure is seen. This functionality is built around the Linux kernel's sd_event_add_cpu_pressure() and sd_event_add_io_pressure() add + new event sources that are triggered whenever CPU or IO pressure is seen, respectively. This functionality + is built around the Linux kernel's Pressure Stall Information (PSI) logic. - Expects an event loop object as first parameter, and returns the allocated event source object in - the second parameter, on success. The handler parameter is a function to call when - memory pressure is seen, or NULL. The handler function will be passed the + These functions expect an event loop object as first parameter, and return the allocated event source + object in the second parameter, on success. The handler parameter is a function to + call when pressure is seen, or NULL. The handler function will be passed the userdata pointer, which may be chosen freely by the caller. The handler may return negative to signal an error (see below), other return values are ignored. If - handler is NULL, a default handler that compacts allocation - caches maintained by libsystemd as well as glibc (via malloc_trim3) - will be used. + handler is NULL, a default handler is used. For + sd_event_add_memory_pressure(), the default handler compacts allocation caches + maintained by libsystemd as well as glibc (via malloc_trim3). + For sd_event_add_cpu_pressure() and + sd_event_add_io_pressure(), the default handler is a no-op. It is recommended to + pass a custom handler for CPU and IO pressure to take meaningful action when pressure is + detected. To destroy an event source object use sd_event_source_unref3, @@ -83,12 +140,13 @@ sd_event_source_set_enabled3 with SD_EVENT_OFF. - If the second parameter of sd_event_add_memory_pressure() is + If the second parameter of sd_event_add_memory_pressure(), + sd_event_add_cpu_pressure(), or sd_event_add_io_pressure() is NULL no reference to the event source object is returned. In this case, the event source is considered "floating", and will be destroyed implicitly when the event loop itself is destroyed. - The event source will fire according to the following logic: + The memory pressure event source will fire according to the following logic: If the @@ -111,6 +169,18 @@ /proc/pressure/memory is watched instead. + The CPU pressure event source follows the same logic, but uses the + $CPU_PRESSURE_WATCH/$CPU_PRESSURE_WRITE environment variables, + the cpu.pressure cgroup file, and the system-wide PSI interface file + /proc/pressure/cpu instead. Note that /proc/pressure/cpu only + provides the some line, not the full line, so only + some is valid when watching at the system level. + + The IO pressure event source follows the same logic, but uses the + $IO_PRESSURE_WATCH/$IO_PRESSURE_WRITE environment variables, + the io.pressure cgroup file, and the system-wide PSI interface file + /proc/pressure/io instead. + Or in other words: preferably any explicit configuration passed in by an invoking service manager (or similar) is used as notification source, before falling back to local notifications of the service, and finally to global notifications of the system. @@ -143,7 +213,7 @@ The sd_event_source_set_memory_pressure_type() and sd_event_source_set_memory_pressure_period() functions can be used to fine-tune the - PSI parameters for pressure notifications. The former takes either some, + PSI parameters for memory pressure notifications. The former takes either some, full as second parameter, the latter takes threshold and period times in microseconds as parameters. For details about these three parameters see the PSI documentation. Note that these two calls must be invoked immediately after allocating the event source, as they must be configured before @@ -152,6 +222,19 @@ environment variables (or in other words: configuration supplied by a service manager wins over internal settings). + Similarly, sd_event_source_set_cpu_pressure_type() and + sd_event_source_set_cpu_pressure_period() can be used to fine-tune the PSI + parameters for CPU pressure notifications, and + sd_event_source_set_io_pressure_type() and + sd_event_source_set_io_pressure_period() can be used to fine-tune the PSI + parameters for IO pressure notifications. They work identically to their memory pressure counterparts. + The type parameter takes either some or full, and the period + function takes threshold and period times in microseconds. The same constraints apply: these calls must + be invoked immediately after allocating the event source, and will fail if pressure parameterization + has been passed in via the corresponding + $*_PRESSURE_WATCH/$*_PRESSURE_WRITE environment + variables. + The sd_event_trim_memory() function releases various internal allocation caches maintained by libsystemd and then invokes glibc's malloc_trim3. This @@ -161,7 +244,7 @@ LOG_DEBUG level (with message ID f9b0be465ad540d0850ad32172d57c21) about the memory pressure operation. - For further details see Memory Pressure Handling in + For further details see Resource Pressure Handling in systemd. @@ -197,8 +280,9 @@ -EHOSTDOWN - The $MEMORY_PRESSURE_WATCH variable has been set to the literal - string /dev/null, in order to explicitly disable memory pressure + The $MEMORY_PRESSURE_WATCH, + $CPU_PRESSURE_WATCH, or $IO_PRESSURE_WATCH variable has been + set to the literal string /dev/null, in order to explicitly disable pressure handling. @@ -207,8 +291,9 @@ -EBADMSG - The $MEMORY_PRESSURE_WATCH variable has been set to an invalid - string, for example a relative rather than an absolute path. + The $MEMORY_PRESSURE_WATCH, + $CPU_PRESSURE_WATCH, or $IO_PRESSURE_WATCH variable has been + set to an invalid string, for example a relative rather than an absolute path. @@ -216,8 +301,9 @@ -ENOTTY - The $MEMORY_PRESSURE_WATCH variable points to a regular file - outside of the procfs or cgroupfs file systems. + The $MEMORY_PRESSURE_WATCH, + $CPU_PRESSURE_WATCH, or $IO_PRESSURE_WATCH variable points + to a regular file outside of the procfs or cgroupfs file systems. @@ -225,7 +311,8 @@ -EOPNOTSUPP - No configuration via $MEMORY_PRESSURE_WATCH has been specified + No configuration via $MEMORY_PRESSURE_WATCH, + $CPU_PRESSURE_WATCH, or $IO_PRESSURE_WATCH has been specified and the local kernel does not support the PSI interface. @@ -234,8 +321,12 @@ -EBUSY - This is returned by sd_event_source_set_memory_pressure_type() - and sd_event_source_set_memory_pressure_period() if invoked on event sources + This is returned by sd_event_source_set_memory_pressure_type(), + sd_event_source_set_memory_pressure_period(), + sd_event_source_set_cpu_pressure_type(), + sd_event_source_set_cpu_pressure_period(), + sd_event_source_set_io_pressure_type(), + and sd_event_source_set_io_pressure_period() if invoked on event sources at a time later than immediately after allocating them. @@ -277,6 +368,12 @@ sd_event_source_set_memory_pressure_type(), sd_event_source_set_memory_pressure_period(), and sd_event_trim_memory() were added in version 254. + sd_event_add_cpu_pressure(), + sd_event_source_set_cpu_pressure_type(), + sd_event_source_set_cpu_pressure_period(), + sd_event_add_io_pressure(), + sd_event_source_set_io_pressure_type(), and + sd_event_source_set_io_pressure_period() were added in version 261. diff --git a/man/sd_journal_get_data.xml b/man/sd_journal_get_data.xml index e3c8e0b5cd99e..a902d76c73e3b 100644 --- a/man/sd_journal_get_data.xml +++ b/man/sd_journal_get_data.xml @@ -83,8 +83,10 @@ sd_journal_get_data() gets the data object associated with a specific field from the current journal entry. It takes four arguments: the journal context object, a string with the - field name to request, plus a pair of pointers to pointer/size variables where the data object and its - size shall be stored in. The field name should be an entry field name. Well-known field names are listed in + field name to request, plus a pair of optional pointers to pointer/size variables where the data object and + its size shall be stored in. Either pointer may be NULL, in which case the corresponding + value is not returned (this is supported since version 261). The field name should be an entry field name. + Well-known field names are listed in systemd.journal-fields7, but any field can be specified. The returned data is in a read-only memory map and is only valid until the next invocation of sd_journal_get_data(), @@ -162,6 +164,10 @@ -EINVAL One of the required parameters is NULL or invalid. + For sd_journal_get_data(), only the journal context object and field name + are required non-NULL. For sd_journal_enumerate_data() + and sd_journal_enumerate_available_data(), + ret_data and ret_size are required as well. diff --git a/man/sd_json_parse.xml b/man/sd_json_parse.xml new file mode 100644 index 0000000000000..c5234734dab1a --- /dev/null +++ b/man/sd_json_parse.xml @@ -0,0 +1,248 @@ + + + + + + + + sd_json_parse + systemd + + + + sd_json_parse + 3 + + + + sd_json_parse + sd_json_parse_continue + sd_json_parse_with_source + sd_json_parse_with_source_continue + sd_json_parse_file + sd_json_parse_file_at + SD_JSON_PARSE_SENSITIVE + SD_JSON_PARSE_MUST_BE_OBJECT + SD_JSON_PARSE_MUST_BE_ARRAY + + Parse JSON strings and files into JSON variant objects + + + + + #include <systemd/sd-json.h> + + + int sd_json_parse + const char *string + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + int sd_json_parse_continue + const char **p + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + int sd_json_parse_with_source + const char *string + const char *source + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + int sd_json_parse_with_source_continue + const char **p + const char *source + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + int sd_json_parse_file + FILE *f + const char *path + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + int sd_json_parse_file_at + FILE *f + int dir_fd + const char *path + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + + + Description + + sd_json_parse() parses the JSON string in string and + returns the resulting JSON variant object in ret. The input must contain exactly + one JSON value (object, array, string, number, boolean, or null); any trailing non-whitespace content + after the first parsed value is considered an error. + + If parsing fails, the reterr_line and reterr_column + arguments are set to the line and column (both one-based) where the parse error occurred. One or both + may be passed as NULL if the caller is not interested in error location + information. On success, the return value is non-negative and ret is set to a + newly allocated JSON variant object (which must be freed with + sd_json_variant_unref3 + when no longer needed). ret may be passed as NULL, in which + case the input is validated but no object is returned. + + sd_json_parse_continue() is similar, but is intended for parsing a sequence of + concatenated JSON values from a single input string. Instead of taking a const char * string + directly, it takes a pointer to a const char * pointer. After each successful parse, the + pointer is advanced past the consumed input, so that subsequent calls will parse the next JSON + value. This is useful for parsing newline-delimited JSON (NDJSON) streams or similar concatenated JSON + formats. Unlike sd_json_parse(), trailing content after the first JSON value is not + considered an error — it is expected to be the beginning of the next value. + + sd_json_parse_with_source() and + sd_json_parse_with_source_continue() are similar to + sd_json_parse() and sd_json_parse_continue(), respectively, but + take an additional source argument. This is a human-readable string (typically a + file name or other origin identifier) that is attached to the parsed JSON variant object and can later be + retrieved via + sd_json_variant_get_source3. If + source is NULL, no source information is + attached. sd_json_parse() and sd_json_parse_continue() are + equivalent to calling their _with_source counterparts with + source set to NULL. + + sd_json_parse_file() reads and parses a JSON value from a file. If the + f argument is non-NULL, the JSON text is read from the + specified FILE stream. If f is NULL, the file + indicated by path is opened and read instead. The path + argument serves a dual purpose: it is both used for opening the file (if f is + NULL) and recorded as source information in the resulting JSON variant (see + above). + + sd_json_parse_file_at() is similar to + sd_json_parse_file(), but takes an additional dir_fd argument + which specifies a file descriptor referring to the directory to resolve relative paths specified in + path against. If set to AT_FDCWD, relative paths are resolved + against the current working directory, which is the default behaviour of + sd_json_parse_file(). + + The flags argument is a bitmask of zero or more of the following + flags: + + + + SD_JSON_PARSE_SENSITIVE + + Marks the resulting JSON variant as "sensitive", indicating that it contains secret + key material or similar confidential data. Sensitive variants are erased from memory when freed and + are excluded from certain debug logging and introspection operations. See + sd_json_variant_sensitive3 + for details. + + + + SD_JSON_PARSE_MUST_BE_OBJECT + + Requires that the top-level JSON value be a JSON object (i.e. {…}). + If the top-level value is an array, string, number, boolean, or null, parsing fails with + -EINVAL. + + + + + + SD_JSON_PARSE_MUST_BE_ARRAY + + Requires that the top-level JSON value be a JSON array (i.e. […]). + If the top-level value is an object, string, number, boolean, or null, parsing fails with + -EINVAL. + + + + + + If both SD_JSON_PARSE_MUST_BE_OBJECT and + SD_JSON_PARSE_MUST_BE_ARRAY are set, both objects and arrays are accepted, but + non-container values (strings, numbers, booleans, null) are still refused. + + + + Return Value + + On success, these functions return 0. On failure, they return a negative errno-style error + code. + + + Errors + + Returned errors may indicate the following problems: + + + + -EINVAL + + The input is not valid JSON, the input contains trailing content after the parsed + value (only for non-_continue variants), or a top-level type constraint + specified via SD_JSON_PARSE_MUST_BE_OBJECT or + SD_JSON_PARSE_MUST_BE_ARRAY was violated. + + + + -ENODATA + + The input string is empty or NULL. + + + + -ENOMEM + + Memory allocation failed. + + + + + + + + + History + sd_json_parse(), + sd_json_parse_continue(), + sd_json_parse_with_source(), + sd_json_parse_with_source_continue(), + sd_json_parse_file(), and + sd_json_parse_file_at() were added in version 257. + + + + See Also + + + systemd1 + sd-json3 + sd_json_variant_unref3 + sd_json_variant_get_source3 + sd_json_dispatch3 + + + diff --git a/man/sd_path_lookup.xml b/man/sd_path_lookup.xml index 9190e6b00a49a..07592e71cebfc 100644 --- a/man/sd_path_lookup.xml +++ b/man/sd_path_lookup.xml @@ -68,6 +68,7 @@ SD_PATH_USER_PUBLIC, SD_PATH_USER_TEMPLATES, SD_PATH_USER_DESKTOP, + SD_PATH_USER_PROJECTS, SD_PATH_SEARCH_BINARIES, SD_PATH_SEARCH_BINARIES_DEFAULT, diff --git a/man/smbios-type-11.xml b/man/smbios-type-11.xml index 95754333a8818..c881592722850 100644 --- a/man/smbios-type-11.xml +++ b/man/smbios-type-11.xml @@ -88,9 +88,10 @@ io.systemd.boot.loglevel=LEVEL - This allows configuration of the log level, and is read by systemd-boot. - For details see - systemd-boot7. + This allows configuration of the log level, and is read by + systemd-boot and systemd-stub. For details see + systemd-boot7 and + systemd-stub7. diff --git a/man/systemctl.xml b/man/systemctl.xml index c514c849265bd..8b8d1065556f2 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -501,7 +501,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err - enqueue-marked-jobs + enqueue-marked Enqueue start/stop/restart/reload jobs for all units that have the respective needs-* markers set. When a unit marked for reload does not support reload, @@ -521,7 +521,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err are not running yet, they will be started. When used in combination with , it is a deprecated alias of - enqueue-marked-jobs. + enqueue-marked. @@ -1499,6 +1499,17 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err systemd listens on behalf of user configuration will stay accessible. + When unit aliasing is introduced during reload (e.g., converting + b.service to a symlink pointing to + a.service), the running state of the canonical + unit (a.service) is preserved. The old serialized + state of the now-aliased unit is discarded to prevent stale data from + corrupting the canonical unit's live state. Dependencies referencing + the alias name are automatically resolved to the canonical unit, and + the dependency graph is rebuilt from unit files, ensuring consistency. + If the now-aliased unit had running processes, they are abandoned and + will no longer be tracked by the service manager. + This command should not be confused with the reload command. @@ -2784,6 +2795,21 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err + + + + + When used with kexec, append the specified string to the kernel command + line options of the kexec kernel. The kernel command line is taken from the boot loader entry of + the currently booted kernel (as selected automatically when no kexec kernel is preloaded, see + kexec above). This string is appended verbatim, separated from the existing + options by a single space. systemctl kexec will fail if this option is specified + when a kexec kernel is already loaded. + + + + + diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index e64f9be57ee7f..f3dfd7479f87f 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -195,6 +195,11 @@ OPTIONS has-tpm2 + + systemd-analyze + OPTIONS + identify-tpm2 + systemd-analyze OPTIONS @@ -1028,6 +1033,27 @@ default ignore - - + + <command>systemd-analyze identify-tpm2</command> + + Shows vendor information about the TPM 2.0 device discovered. + + + Example Output + + Family Indicator: 2.0 + Level: 0 + Revision: 1.59 +Specification Date: Mon 2023-01-09 + Manufacturer: STM + Vendor String: ST33KTPM2XSPI + Firmware Version: 9.258 + Modalias String: fi2.0:lv0:rv1.59:sy2023:sd9:mfSTM:vsST33KTPM2XSPI:ty0:fw9.258.0: + + + + + <command>systemd-analyze pcrs <optional><replaceable>PCR</replaceable>…</optional></command> diff --git a/man/systemd-boot-check-no-failures.service.xml b/man/systemd-boot-check-no-failures.service.xml index dac5037ece9e8..c21cb98581ba4 100644 --- a/man/systemd-boot-check-no-failures.service.xml +++ b/man/systemd-boot-check-no-failures.service.xml @@ -24,7 +24,7 @@ systemd-boot-check-no-failures.service - /usr/lib/systemd/system-boot-check-no-failures + /usr/lib/systemd/systemd-boot-check-no-failures diff --git a/man/systemd-cat.xml b/man/systemd-cat.xml index b60984b8a0838..6691fc4d5621b 100644 --- a/man/systemd-cat.xml +++ b/man/systemd-cat.xml @@ -34,9 +34,9 @@ Description systemd-cat may be used to connect the - standard input and output of a process to the journal, or as a - filter tool in a shell pipeline to pass the output the previous - pipeline element generates to the journal. + standard output and error output of a command to the journal, or + as a filter tool in a shell pipeline to pass the output the + previous pipeline element generates to the journal. If no parameter is passed, systemd-cat will write everything it reads from standard input (stdin) to the diff --git a/man/systemd-cgls.xml b/man/systemd-cgls.xml index 5280992c8c67c..60cf6fa41aa2c 100644 --- a/man/systemd-cgls.xml +++ b/man/systemd-cgls.xml @@ -114,22 +114,23 @@ + - Controls whether to include information about extended attributes of the listed - control groups in the output. With the long option, expects a boolean value. Defaults to no. + control groups in the output. With the long option only, optionally accepts a boolean value. Defaults + to no. + - Controls whether to include the numeric ID of the listed control groups in the - output. With the long option, expects a boolean value. Defaults to no. + output. With the long option only, optionally accepts a boolean value. Defaults to no. diff --git a/man/systemd-clonesetup-generator.xml b/man/systemd-clonesetup-generator.xml new file mode 100644 index 0000000000000..28c8b5ad004c4 --- /dev/null +++ b/man/systemd-clonesetup-generator.xml @@ -0,0 +1,50 @@ + + + + + + + + systemd-clonesetup-generator + systemd + + + + systemd-clonesetup-generator + 8 + + + + systemd-clonesetup-generator + Unit generator for /etc/clonetab + + + + /usr/lib/systemd/system-generators/systemd-clonesetup-generator + + + + Description + + systemd-clonesetup-generator is a generator that translates + /etc/clonetab into native systemd units early at boot and when + configuration of the system manager is reloaded. This will create + systemd-clonesetup@.service8 + units as necessary. + + systemd-clonesetup-generator implements + systemd.generator7. + + + + See Also + + systemd1 + clonetab5 + systemd-clonesetup@.service8 + dmsetup8 + + + + diff --git a/man/systemd-creds.xml b/man/systemd-creds.xml index 60ded4219327e..a35e534bfa18d 100644 --- a/man/systemd-creds.xml +++ b/man/systemd-creds.xml @@ -339,8 +339,9 @@ where credentials shall be generated. Note that decryption of such credentials is refused on systems that have a TPM2 chip and where UEFI SecureBoot is enabled (this is done so that such a locked down system cannot be tricked into loading a credential generated this way that lacks authentication - information). If set to auto-initrd a TPM2 key is used if a TPM2 is found. If not - a fixed zero length key is used, equivalent to null mode. This option is + information. If either UEFI SecureBoot or a TPM2 are not available, then loading such credentials is + allowed by default). If set to auto-initrd a TPM2 key is used if a TPM2 is found. + If not, a fixed zero length key is used, equivalent to null mode. This option is particularly useful to generate credentials files that are encrypted/authenticated against TPM2 where available but still work on systems lacking support for this. The special value help may be used to list supported key types. @@ -424,7 +425,9 @@ - Allow decrypting credentials that use a null key. By default decryption of credentials encrypted/authenticated with a null key is only allowed if UEFI SecureBoot is off. + Allow decrypting credentials that use a null key. By default decryption of + credentials encrypted/authenticated with a null key is only allowed if UEFI SecureBoot is off or if + a TPM2 is not available. @@ -432,7 +435,8 @@ - Refuse decrypting credentials that use a null key, regardless of the UEFI SecureBoot state (see above). + Refuse decrypting credentials that use a null key, regardless of the UEFI SecureBoot + state or TPM2 availability (see above). diff --git a/man/systemd-firstboot.xml b/man/systemd-firstboot.xml index 86a85f0bf2855..db6f2569a8d1f 100644 --- a/man/systemd-firstboot.xml +++ b/man/systemd-firstboot.xml @@ -447,6 +447,17 @@ + + + firstboot.hostname + + This credential specifies the static system hostname to set during first boot. The + user will not be prompted for the hostname. Note that this controls the static hostname, not the + transient hostname, and only has an effect on first boot, unlike + system.hostname. + + + Note that by default the systemd-firstboot.service unit file is set up to diff --git a/man/systemd-fstab-generator.xml b/man/systemd-fstab-generator.xml index 43f573dd722fe..8e684b29863d8 100644 --- a/man/systemd-fstab-generator.xml +++ b/man/systemd-fstab-generator.xml @@ -214,12 +214,12 @@ systemd.volatile= Controls whether the system shall boot up in volatile mode. Takes a boolean argument or the - special value . + special values state and overlay. - If false (the default), this generator makes no changes to the mount tree and the system is booted up in - normal mode. + If no (the default), this generator makes no changes to the mount tree and the system + is booted up in normal mode. - If true the generator ensures + If yes the generator ensures systemd-volatile-root.service8 is run in the initrd. This service changes the mount table before transitioning to the host system, so that a volatile memory file system (tmpfs) is used as root directory, with only @@ -228,7 +228,7 @@ and lost at shutdown, as /etc/ and /var/ will be served from the (initially unpopulated) volatile memory file system. - If set to the generator will leave the root directory mount point unaltered, + If set to state the generator will leave the root directory mount point unaltered, however will mount a tmpfs file system to /var/. In this mode the normal system configuration (i.e. the contents of /etc/) is in effect (and may be modified during system runtime), however the system state (i.e. the contents of /var/) is reset at boot and diff --git a/man/systemd-gpt-auto-generator.xml b/man/systemd-gpt-auto-generator.xml index e267fc952870e..6494a79d22dcc 100644 --- a/man/systemd-gpt-auto-generator.xml +++ b/man/systemd-gpt-auto-generator.xml @@ -75,6 +75,13 @@ discovery based on the boot loader reported ESP which is also enabled if no root= parameter is specified at all. (The latter relies on systemd-udevd.service's /dev/gpt-auto-root block device symlink generation). + + It will also generate a unit file mounting the EFI System Partition (ESP) to + /sysefi/, if applicable. Unlike the file systems mentioned above this mount is not + activated by default however, but can be pulled in by services requiring ESP access from within the + initrd. Note that this mount point is initrd-specific and does not make use of autofs. The ESP is + typically mounted at a different place and via autofs once the system transitions out of the + initrd. @@ -179,7 +186,7 @@ SD_GPT_ESP c12a7328-f81f-11d2-ba4b-00a0c93ec93b EFI System Partition (ESP) - /efi/ or /boot/ + /efi/ or /boot/ once the system transitioned out of the initrd, /sysefi/ before The first partition with this type UUID located on the same disk as the root partition is mounted to /boot/ or /efi/, see below. @@ -259,7 +266,9 @@ The ESP is mounted to /boot/ if that directory exists and is not used for XBOOTLDR, and otherwise to /efi/. Same as for /boot/, an - automount unit is used. The mount point will be created if necessary. + automount unit is used. The mount point will be created if necessary. These apply once the system + transitioned out of the initrd phase. Before that, if components in the initrd require ESP access, it + will be mounted to /sysefi/. No configuration is created for mount points that are configured in fstab5 or when diff --git a/man/systemd-imds-generator.xml b/man/systemd-imds-generator.xml new file mode 100644 index 0000000000000..d5eaf1e16a05f --- /dev/null +++ b/man/systemd-imds-generator.xml @@ -0,0 +1,107 @@ + + + + + + + + systemd-imds-generator + systemd + + + + systemd-imds-generator + 8 + + + + systemd-imds-generator + Generator to automatically enable IMDS on supporting environments + + + + /usr/lib/systemd/system-generators/systemd-imds-generator + + + + Description + + systemd-imds-generator is a generator that enables IMDS (Instance Metadata + Service) functionality at boot on systems that support it. Specifically it does three things: + + + It pulls the systemd-imdsd.socket unit (which activates + systemd-imdsd@.service8) + into the initial transaction, which provides IMDS access to local applications via Varlink + IPC. + + It pulls the systemd-imds-early-network.service unit into the + initial transaction, which generates a suitable + systemd.network5 + network configuration file that allows early-boot network access to the IMDS + functionality. + + It pulls the systemd-imds-import.service unit into the initial + transaction, which automatically imports various credentials from IMDS into the local system, storing + them in /run/credstore/. + + + By default, whether to pull in these services or not is decided based on + hwdb7 information, + that detects various IMDS environments automatically. However, this logic may be overridden via + systemd.imds=, see below. + + systemd-imds-generator implements + systemd.generator7. + + + + Kernel Command Line + + systemd-imds-generator understands the following kernel command line + parameters: + + + + + systemd.imds= + + Takes a boolean argument or the special value auto, and may be used to + enable or disable the IMDS logic. Note that this controls only whether the relevant services (as + listed above) are automatically pulled into the initial transaction, it has no effect if some other + unit or the user explicitly activates the relevant units. If this option is not used (or set to + auto) automatic detection of IMDS is used, see above. + + + + + + + + + systemd.imds.import= + + Takes a boolean argument. If false the systemd-imds-import.service (see + above) is not pulled into the initial transaction, i.e. no credentials are imported from + IMDS. Defaults to true. + + + + + + + + + + See Also + + systemd1 + systemd-imds1 + systemd-imdsd@.service8 + systemd.system-credentials7 + + + + diff --git a/man/systemd-imds.xml b/man/systemd-imds.xml new file mode 100644 index 0000000000000..3980c7560c351 --- /dev/null +++ b/man/systemd-imds.xml @@ -0,0 +1,174 @@ + + + + + + + + systemd-imds + systemd + + + + systemd-imds + 1 + + + + systemd-imds + systemd-imds-import.service + Cloud IMDS (Instance Metadata Service) tool + + + + systemd-imds-import.service + + systemd-imds OPTIONS KEY + + + + + Description + + systemd-imds is a tool for acquiring data from IMDS (Instance Metadata Service), + as provided in many cloud environments. It is a client to + systemd-imdsd@.service8, + and provides access to IMDS data from shell environments. + + The tool can operate in one of five modes: + + + Without positional arguments (and without the switch) + general IMDS service data and a few well known fields are displayed in human friendly + form. + + With a positional argument (and without ) the IMDS data + referenced by the specified key is acquired and written to standard output, in unprocessed form. IMDS + keys are the part of the IMDS acquisition URL that are suffixed to the base URL. IMDS keys must begin + with a slash (/). Note that IMDS keys are typically + implementation-specific. + + With the option specified (see below), the indicated + well-known field is written to standard output, in unprocessed form. The concept of well-known fields + abstracts IMDS implementation differences to some level, exposing a unified interface for IMDS fields + that typically exist on many different implementations, but under implementation-specific + keys. + + With the option specified (see below) the "userdata" + provided via IMDS is written to standard output. Under the hood this is similar to + , or + . Each of the three is tried in turn (in this order), and + the first available is returned. For the + systemd-userdata userdata item is requested. For + the returned data is automatically + Base64-decoded. + + With the option specified, various well known and userdata + fields are imported into the local credential store, where they are used to configure and parameterize + the system. For details see below. + + + + + Options and Commands + + + + + + + Takes one of hostname, region, + zone, ipv4-public, ipv6-public, + ssh-key, userdata, userdata-base, + userdata-base64. Acquires a specific "well-known" field from IMDS. Many of these + fields are commonly supported by various IMDS implementations, but typically some fields are + not. Note that if is used an additional subkey should be + specified as positional argument, which encodes the specific userdata item to acquire. + + + + + + + + Takes a time in seconds as argument, and indicates the required "freshness" of the + data, in case cached data is used. + + + + + + + + Takes a boolean. If set to false local caching of IMDS is disabled, and the data is + always acquired fresh from the IMDS endpoint. + + + + + + + + + Acquire this instance's IMDS user data, if available. See above for + details. + + + + + + + + Acquires IMDS data and writes relevant fields as credentials to + /run/credstore/. This currently covers: + + + If the IMDS user data is a valid JSON object containing a field + systemd.credentials (with a JSON array as value) it is processed, importing + arbitrary credentials listed in the array. Each array item must have a name + field indicating the credential name. It may have one text, + data or encrypted field, containing the credential data. If + text is used the value shall be a literal string of the credential value. If + data is used the value may be arbitrary binary data encoded in a Base64 + string. If encrypted is used the value shall be a Base64 encoded encrypted + credential. See + systemd.system-credentials7 + for information about credentials that may be imported this way. + + If the well-known ssh-key field is available, its value will be + imported into the ssh.authorized_keys.root credential. + + If the well-known hostname field is available, its value will be + imported into the firstboot.hostname credential. + + + This command is invoked by the systemd-imds-import.service run at + boot. + + + + + + + + + + + Exit status + + On success, 0 is returned, a non-zero failure code otherwise. + + + + See Also + + systemd1 + systemd-imdsd@.service8 + systemd-imds-generator8 + systemd.system-credentials7 + + + + diff --git a/man/systemd-imdsd@.service.xml b/man/systemd-imdsd@.service.xml new file mode 100644 index 0000000000000..0d08a58120c38 --- /dev/null +++ b/man/systemd-imdsd@.service.xml @@ -0,0 +1,269 @@ + + + + + + + + systemd-imdsd@.service + systemd + + + + systemd-imdsd@.service + 8 + + + + systemd-imdsd@.service + systemd-imdsd + systemd-imdsd.socket + systemd-imdsd-early-network.service + Cloud IMDS (Instance Metadata Service) client + + + + systemd-imdsd@.service + systemd-imdsd.socket + systemd-imdsd-early-network.service + /usr/lib/systemd/systemd-imdsd + + + + Description + + systemd-imdsd@.service is a system service that provides local access to IMDS + (Instance Metadata Service; or equivalent) functionality, as provided by many public clouds. + + The service provides a Varlink IPC interface via + /run/systemd/io.systemd.InstanceMetadata to query IMDS fields. + + systemd-imdsd-early-network.service is a system service that generates a + systemd-networkd.service8 + compatible + systemd.network5 file + for configuring the early-boot network in order to be able to contact the IMDS endpoint. + + The + systemd-imds1 tool may + be used to query information from this service. + + + + + + Kernel Command Line Options + + The IMDS endpoint is typically determined automatically via + hwdb7 records, but can + also be configured explicitly via the kernel command line, via the following options: + + + + systemd.imds.network= + + Takes one of off, locked, + unlocked. Controls whether and how to set up networking for IMDS endpoint + access. Unless set to off early boot networking is enabled, ensuring that the + IMDS endpoint can be reached. If set to locked (the default) direct access to + the IMDS endpoint by regular unprivileged processes is disabled via a "prohibit" route, so that any + access must be done through systemd-imdsd@.service or its associated tools. If + set to unlocked this "prohibit" route is not created, and regular unprivileged + processes can directly contact IMDS. + + + + + + + systemd.imds.vendor= + + A short string identifying the cloud vendor. + + Example: systemd.imds.vendor=foobarcloud + + + + + + + systemd.imds.token_url= + + If a bearer token must be acquired to talk to the IMDS service, this is the URL to acquire it + from. + + + + + + + systemd.imds.refresh_header_name= + + Takes a HTTP header field name (excluding the :) that declares the header + field for passing the TTL value (in seconds) to the HTTP server when acquiring a token. Only + applies if systemd.imds.token_url= is set too. + + + + + + + systemd.imds.data_url= + + Takes the base URL to acquire the IMDS data from (the IMDS "endpoint"). All data fields are + acquired from below this URL. This URL should typically not end in /. + + The data URLs are concatenated from this base URL, the IMDS "key" and the suffix configured + via systemd.imds.data_url_suffix= below. Well-known IMDS "keys" can be + configured via the systemd.imds.key=* options below. + + Example: systemd.imds.data_url=http://169.254.169.254/metadata + + + + + + + systemd.imds.data_url_suffix= + + If specified, this field is appended to the end of the data URL (after appending the IMDS + "key" to the data base URL), see above. + + Example: systemd.imds.data_url_suffix=?api-version=2025-04-07&format=text + + + + + + + systemd.imds.token_header_name= + + Takes a HTTP header field name (excluding the :) that declares the header + field to pass the bearer token acquired from the token URL (see above) in. Only applies if + systemd.imds.token_url= is set too. + + + + + + + systemd.imds.extra_header= + + Takes a full HTTP header expression (both field name and value, separated by a colon + :) to pass to the HTTP server when requesting data. May be used multiple times + to set multiple headers. + + Example: systemd.imds.extra_header=Metadata:true + + + + + + + systemd.imds.address_ipv4= + + Configures the IPv4 address the IMDS endpoint is contacted on. This should typically be the + IP address also configured via systemd.imds.data_url= (if IPv4 is used) and is + used to set up IP routing. + + Example: systemd.imds.address_ipv4=169.254.169.254 + + + + + + + systemd.imds.address_ipv6= + + Configures the IPv6 address the IMDS endpoint is contacted on. This should typically be the + IP address also configured via systemd.imds.data_url= (if IPv6 is used) and is + used to set up IP routing. + + + + + + + systemd.imds.key.hostname= + systemd.imds.key.region= + systemd.imds.key.zone= + systemd.imds.key.ipv4_public= + systemd.imds.key.ipv6_public= + systemd.imds.key.ssh_key= + systemd.imds.key.userdata= + systemd.imds.key.userdata_base= + systemd.imds.key.userdata_base64= + + Configures strings to concatenate to the data base URL (see above) to acquire data for + various "well-known" fields. These strings must begin with a /. They should + return the relevant data in plain text. + + A special case are the three "userdata" keys: the option + systemd.imds.key.userdata_base= should be used if the IMDS service knows a + concept of multiple userdata fields, and a field identifier thus still needs to be appended to the + userdata base URL. The option systemd.imds.key.userdata= should be used if only + a single userdata field is supported. The option systemd.imds.key.userdata_base64= + should be used in the same case, but only if the userdata field is encoded in Base64. + + Example: systemd.imds.key.hostname=/instance/compute/osProfile/computerName + + + + + + + + + Credentials + + systemd-imdsd@.service supports the service credentials logic as implemented by + ImportCredential=/LoadCredential=/SetCredential= + (see systemd.exec5 for + details). The following credentials are used when passed in: + + + + imds.vendor + imds.vendor_token + imds.refresh_header_name + imds.data_url + imds.data_url_suffix + imds.token_header_name + imds.extra_header + imds.extra_header2 + imds.extra_header3 + imds.extra_header… + imds.address_ipv4 + imds.address_ipv6 + imds.key_hostname + imds.key_region + imds.key_zone + imds.key_ipv4_public + imds.key_ipv6_public + imds.key_ssh_key + imds.key_userdata + imds.key_userdata_base + imds.key_userdata_base64 + The various IMDS endpoint parameters. The semantics are very close to those configurable + via kernel command line, see above for the matching list. + + + + + + + + See Also + + systemd1 + systemd-imds1 + systemd-imds-generator8 + systemd-networkd.service8 + + + + diff --git a/man/systemd-journal-remote.service.xml b/man/systemd-journal-remote.service.xml index d6258ce2fcd0f..7beb96403d195 100644 --- a/man/systemd-journal-remote.service.xml +++ b/man/systemd-journal-remote.service.xml @@ -333,6 +333,19 @@ + + + + + + + These options override the corresponding settings from the configuration file + (see journal-remote.conf5). + See that page for the descriptions of these options. + + + + diff --git a/man/systemd-logind.service.xml b/man/systemd-logind.service.xml index 34f6330bf7c07..30d9dab14e830 100644 --- a/man/systemd-logind.service.xml +++ b/man/systemd-logind.service.xml @@ -84,6 +84,15 @@ org.freedesktop.LogControl15 for information about the D-Bus APIs systemd-logind provides. + In addition to the D-Bus interface, systemd-logind also provides a Varlink + interface io.systemd.Shutdown for shutting down or rebooting the system. It + supports PowerOff, Reboot, Halt, + KExec, and SoftReboot methods. Each method accepts an + optional skipInhibitors boolean parameter to bypass active block inhibitors + (matching the SD_LOGIND_SKIP_INHIBITORS flag of the D-Bus interface). The + interface can be queried with + varlinkctl introspect /run/systemd/io.systemd.Login io.systemd.Shutdown. + For more information see Inhibitor Locks. diff --git a/man/systemd-mountfsd.service.xml b/man/systemd-mountfsd.service.xml index 7cc607c4c5c48..2e623a2728140 100644 --- a/man/systemd-mountfsd.service.xml +++ b/man/systemd-mountfsd.service.xml @@ -45,7 +45,7 @@ /usr/lib/ it is assumed to be trusted. If the disk image contains a Verity enabled disk image, along with a signature - partition with a key in the kernel keyring or in /etc/verity.d/ (and related + partition with a key in the kernel keyring or in /etc/verity.d/*.crt (and related directories) the disk image is considered trusted. diff --git a/man/systemd-network-generator.service.xml b/man/systemd-network-generator.service.xml index ccdb57b62b270..7b20f8516d0b5 100644 --- a/man/systemd-network-generator.service.xml +++ b/man/systemd-network-generator.service.xml @@ -100,10 +100,27 @@ + + BOOTIF= + rd.bootif= + + When BOOTIF is specified in the interface field of ip=, it is treated + as a special placeholder rather than a real interface name. Then, in combination with the MAC address provided + by BOOTIF=, this is translated into a + systemd.network5 file + which matches on the the provided MAC address. When rd.bootif=0 is passed, this functionality + is disabled, and the BOOTIF= option is ignored. + + See dracut.cmdline7 + for details on the usage of these options. + + + + + + + + + + + systemd-tpm2-swtpm.service + systemd + + + + systemd-tpm2-swtpm.service + 8 + + + + systemd-tpm2-swtpm.service + systemd-tpm2-swtpm + Provide a fallback software TPM + + + + systemd-tpm2-swtpm.service + /usr/lib/systemd/systemd-tpm2-swtpm + + + + Description + + The systemd-tpm2-swtpm.service provides fallback software TPM functionality, + intended for use in environments where a discrete or firmware TPM ("hardware TPM") is not available. It is + pulled into the boot process by + systemd-tpm2-generator8 + if a hardware TPM is not available, and the system is configured to provide a software TPM in that case. + + Note that a software TPM provides only very weak security properties compared to a hardware TPM, + and hence should only be used as a fallback mechanism if a hardware TPM is not available but TPM + semantics are desired. This service ultimately wraps + swtpm8. + + If the boot secret /.extra/boot-secret (in the initrd) or + /run/systemd/stub/boot-secret (on the host) is available the software TPM NVRAM + storage is encrypted with this key. See + systemd-stub7 for + details. + + The TPM NVRAM storage is placed on the EFI System Partition as it needs to be accessible during + very early boot-up, in particular before the root file system is decrypted and mounted. + + + + See Also + + systemd1 + systemd-tpm2-generator8 + swtpm8 + systemd-stub7 + + + diff --git a/man/systemd-update-done.service.xml b/man/systemd-update-done.service.xml index d9d78262a142e..8bb92ca5b5043 100644 --- a/man/systemd-update-done.service.xml +++ b/man/systemd-update-done.service.xml @@ -79,6 +79,7 @@ + diff --git a/man/systemd-validatefs@.service.xml b/man/systemd-validatefs@.service.xml index 9597b2ce9dff6..85554a30deada 100644 --- a/man/systemd-validatefs@.service.xml +++ b/man/systemd-validatefs@.service.xml @@ -79,8 +79,8 @@ Options - The /usr/lib/systemd/system-validatefs executable may also be invoked from the - command line, where it expects a path to a mount and the following options: + The /usr/lib/systemd/systemd-validatefs executable may also be invoked from + the command line, where it expects a path to a mount and the following options: diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 0b4fef2314a6d..5c5ec4ccbcd55 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -125,6 +125,19 @@ + + + + Specifies the disk type to use for the root disk passed to . + Extra drives added via inherit this disk type unless overridden + with an explicit disk type prefix. Takes one of virtio-blk, + virtio-scsi, nvme, or scsi-cd. Defaults to + virtio-blk. When scsi-cd is specified, the disk is attached + as a read-only CD-ROM drive. + + + + @@ -154,10 +167,12 @@ - + - The amount of memory to start the virtual machine with. - Defaults to 2G. + The amount of memory to start the virtual machine with. Defaults to 2G. + If a maximum size is specified after a colon, memory hotplug is enabled with the given + upper limit. The number of hotplug slots can optionally be specified after a second colon + and defaults to 1. @@ -226,6 +241,34 @@ + + + + Takes an absolute path, or a relative path beginning with + ./. Specifies the path to an EFI NVRAM template file to copy and use as the + initial EFI variable NVRAM state. If not specified, the default NVRAM template from the firmware + definition is copied and used. + + + + + + + + Configures where to place the EFI variable NVRAM state. This takes an absolute file + system path to a regular file to persistently place the state in. If the file is missing it is + created as needed. If set to the special string auto a persistent path is + automatically derived from the VM image path or directory path, with the + .efinvramstate suffix appended. If set to the special string + off the EFI variable NVRAM state is only maintained transiently and flushed out + when the VM shuts down. Defaults to auto. + + If is specified, auto behaves like + off. + + + + @@ -287,14 +330,39 @@ - Takes an absolute path, or a relative path beginning with - ./. Specifies a JSON firmware definition file, which allows selecting the - firmware to boot in the VM. If not specified, a suitable firmware is automatically discovered. If the - special string list is specified lists all discovered firmwares. + Selects which firmware to use in the VM. Takes one of auto, + uefi, bios, none, an absolute path, or a + relative path beginning with ./. Defaults to auto, which + selects UEFI firmware unless specifies a non-PE kernel image, in which + case none is selected. uefi loads OVMF firmware (use a path + to a JSON firmware definition file to select a specific one). bios skips OVMF + loading and lets QEMU use its built-in BIOS (e.g. SeaBIOS on x86). none disables + firmware loading entirely and requires to be specified for direct kernel + boot. Booting a UKI requires uefi. If the special string list + is specified, all discovered firmware definition files are listed. If the special string + describe is specified, the UEFI firmware that would be selected (taking + into account) is printed and the program exits. If an empty + string is specified, the option is reset to its default. + + + + Takes a comma-delimited list of firmware feature strings. This option may be + specified multiple times, in which case the feature lists are combined. When specified, only + firmware definitions that have all the required features will be considered during automatic + firmware discovery. Features prefixed with ~ are excluded: firmware that has + such a feature will be skipped. If a feature appears in both the included and excluded lists, + inclusion takes priority. By default, firmware with the enrolled-keys + feature is excluded. If an empty string is passed, both the included and excluded feature lists + are reset. If the special string list is specified, lists all available + firmware features. + + + + @@ -308,11 +376,11 @@ - Configure whether to search for firmware which supports Secure Boot. - - If the option is not specified or set to , the first firmware detected - will be used. If the option is set to yes, then the first firmware with Secure Boot support will - be selected. If no is specified, then the first firmware without Secure Boot will be selected. + Configure whether to search for firmware which supports Secure Boot. Takes a + boolean or auto. Setting this to yes is equivalent to + and setting this to no is equivalent to + . Setting this to auto + removes secure-boot from both the included and excluded feature lists. @@ -428,13 +496,10 @@ Controls whether the virtual machine is registered with systemd-machined8. Takes a - boolean argument, which defaults to yes when running as root, and no when - running as a regular user. This ensures that the virtual machine is accessible via - machinectl1. - - Note: root privileges are required to use this option as registering with - systemd-machined8 - requires privileged D-Bus method calls. + boolean argument or auto, and defaults to auto. This ensures that the + virtual machine is accessible via + machinectl1. When set to + auto, registration is attempted but failures are ignored. @@ -489,11 +554,13 @@ - + Takes a disk image or block device on the host and supplies it to the virtual - machine as another drive. Optionally, the image format can be specified by appending a colon and - the format (raw or qcow2). Defaults to raw. + machine as another drive. Optionally, the image format and/or disk type can be specified by prefixing + the path with their values separated by colons. The format and disk type prefixes can appear in any + order. The format defaults to raw and the disk type defaults to the value of + (which itself defaults to virtio-blk). Note that qcow2 is only supported for regular files, not block devices. @@ -601,6 +668,21 @@ + + + + + + + These options configure the corresponding settings of + systemd-journal-remote8 + when forwarding journal entries from the VM. See + journal-remote.conf5 + for the descriptions of these settings. + + + + @@ -640,15 +722,31 @@ Configures how to set up the console of the VM. Takes one of interactive, read-only, native, - gui. Defaults to interactive. interactive - provides an interactive terminal interface to the VM. read-only is similar, but - is strictly read-only, i.e. does not accept any input from the user. native also - provides a TTY-based interface, but uses qemu native implementation (which means the qemu monitor - is available). gui shows the qemu graphical UI. + gui, headless. Defaults to interactive. + interactive provides an interactive terminal interface to the VM. + read-only is similar, but is strictly read-only, i.e. does not accept any input + from the user. native also provides a TTY-based interface, but uses qemu native + implementation (which means the qemu monitor is available). gui shows the qemu + graphical UI. headless runs the VM without any console, which is useful for + automated or scripted usage. + + + + Configures the transport to use for the VM console. Takes one of + virtio or serial. Defaults to virtio. + virtio uses a virtio-serial device, which appears as + /dev/hvc0 in the VM. serial uses a regular serial port, + which appears as /dev/ttyS0 (or /dev/ttyAMA0 on ARM) in the VM. This option only has an effect in + , , and + modes. + + + + @@ -685,6 +783,11 @@ embed a NUL byte). Note that the invoking shell might already apply unescaping once, hence this might require double escaping! + Credentials are preferably passed to the VM via SMBIOS Type 11 strings or QEMU fw_cfg files. + If neither mechanism is available, credentials are passed on the kernel command line using + systemd.set_credential_binary= which is not a confidential channel. Do not use + this for passing secrets to the VM in that case. + diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index e7d5e63c963de..809cc285fdce1 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -1538,6 +1538,9 @@ CapabilityBoundingSet=~CAP_B CAP_C DynamicUser= is set. This setting cannot ensure protection in all cases. In general it has the same limitations as ReadOnlyPaths=, see below. + Note that this setting provides no protection if home directories are placed at a non-standard + location, i.e. outside of the hierarchies listed above. + @@ -1770,6 +1773,15 @@ StateDirectory=aaa/bbb ccc tmpfs, then for system services the directories specified in RuntimeDirectory= are removed when the system is rebooted. + If DynamicUser= is used together with + RuntimeDirectoryPreserve= set to values other than , the logic + is slightly altered: the RuntimeDirectory= directories are created below + /run/private/, which is a host directory made inaccessible to unprivileged + users, which ensures that access to these directories cannot be gained through dynamic user ID + recycling. Symbolic links are created to hide this difference in behaviour. Both from the + perspective of the host and from inside the unit, the relevant directories hence always appear + directly below /run/. + @@ -4686,13 +4698,37 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX $MEMORY_PRESSURE_WRITE If memory pressure monitoring is enabled for this service unit, the path to watch - and the data to write into it. See Memory Pressure + and the data to write into it. See Resource Pressure Handling for details about these variables and the service protocol data they convey. + + $CPU_PRESSURE_WATCH + $CPU_PRESSURE_WRITE + + If CPU pressure monitoring is enabled for this service unit, the path to watch + and the data to write into it. See Resource Pressure + Handling for details about these variables and the service protocol data they + convey. + + + + + + $IO_PRESSURE_WATCH + $IO_PRESSURE_WRITE + + If IO pressure monitoring is enabled for this service unit, the path to watch + and the data to write into it. See Resource Pressure + Handling for details about these variables and the service protocol data they + convey. + + + + $FDSTORE diff --git a/man/systemd.link.xml b/man/systemd.link.xml index 602f19b60030c..d26431b2b2bbe 100644 --- a/man/systemd.link.xml +++ b/man/systemd.link.xml @@ -666,7 +666,7 @@ TransmitQueues= - Specifies the device's number of transmit queues. An integer in the range 1…4096. + Specifies the device's number of transmit queues. An integer in the range 1…16384. When unset, the kernel's default will be used. @@ -675,7 +675,7 @@ ReceiveQueues= - Specifies the device's number of receive queues. An integer in the range 1…4096. + Specifies the device's number of receive queues. An integer in the range 1…16384. When unset, the kernel's default will be used. diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml index 6a84b7a648cef..6879518b4b8a3 100644 --- a/man/systemd.netdev.xml +++ b/man/systemd.netdev.xml @@ -2683,7 +2683,10 @@ Ports=eth2 Table= - The numeric routing table identifier. This setting is compulsory. + The routing table identifier. Takes a route table name or number. Route table names + may be predefined or configured with RouteTable= in + networkd.conf5. + This setting is compulsory. diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 3b08a292e0df0..554d8da8ef606 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1086,6 +1086,19 @@ DuplicateAddressDetection=none + + IPv4SrcValidMark= + + Takes a boolean. When enabled, the packet's firewall mark (fwmark) is included in the + reverse path filter route lookup for source address validation on this interface. This is + particularly useful for policy routing setups where packets may arrive with source addresses + that are only valid in routing tables selected by their fwmark. When unset, the kernel's + default will be used. + + + + + IPv4ProxyARP= @@ -2382,9 +2395,12 @@ MultiPathRoute=2001:db8::1@eth0 When true (the default), the machine's hostname (or the value specified with Hostname=, described below) will be sent to the DHCP server. Note that the - hostname must consist only of 7-bit ASCII lower-case characters and no spaces or dots, and be - formatted as a valid DNS domain name. Otherwise, the hostname is not sent even if this option - is true. + hostname must consist only of 7-bit ASCII lower-case characters and no spaces, and be + formatted as a valid DNS domain name. A single-label hostname is sent as DHCP option 12 + (Host Name, RFC 2132); + a multi-label hostname (FQDN) is sent instead as DHCP option 81 (Client FQDN, + RFC 4702). + Otherwise, the hostname is not sent even if this option is true. @@ -2395,7 +2411,8 @@ MultiPathRoute=2001:db8::1@eth0 Use this value for the hostname which is sent to the DHCP server, instead of machine's hostname. Note that the specified hostname must consist only of 7-bit ASCII lower-case - characters and no spaces or dots, and be formatted as a valid DNS domain name. + characters and no spaces, and be formatted as a valid DNS domain name. Multi-label hostnames + (FQDNs) are acceptable; see SendHostname= above for details. @@ -6445,12 +6462,12 @@ ServerAddress=192.168.0.1/24 - [ModemManager] Section Options + [MobileNetwork] Section Options This section configures the default setting of the ModemManager integration. See for more information about ModemManager. - Regardless of the [ModemManager] section settings consider using the following for LTE modems (take into account + Regardless of the [MobileNetwork] section settings consider using the following for LTE modems (take into account that LTE modems do not typically support LLDP because LLDP is a Layer 2 protocol for Ethernet networks and an LTE modem connects to a cellular network, not a local Ethernet LAN): [Network] @@ -6460,75 +6477,89 @@ IPv6AcceptRA=no - The following options are available in the [ModemManager] section: + The following options are available in the [MobileNetwork] section: - SimpleConnectProperties= + APN= - Specifies the white-space separated list of simple connect properties used to connect a modem. See - for more - information about simple connect. If no properties provided then the connection is not initiated. - - - =NAME - An Access Point Name (APN) is the name of a gateway between a mobile network - (GSM, GPRS, 3G, 4G and 5G) and another computer network. Required in 3GPP. - - - + An Access Point Name (APN) is the name of a gateway between a mobile network + (GSM, GPRS, 3G, 4G and 5G) and another computer network. Required in 3GPP. + Defaults to unset and no attempt to establish the connection is made. - - =METHOD - Authentication method to use. Takes one of "none", "pap", "chap", "mschap", "mschapv2" or "eap". - Optional in 3GPP. + + + - - + + AllowedAuthenticationMechanisms= + + Authentication method to use. Specifies the white-space separated list of + none, pap, chap, + mschap, mschapv2, or eap + methods. Optional in 3GPP. Defaults to unset and an automatically picked + authentication method will be used. - - =NAME - User name (if any) required by the network. Optional in 3GPP. + + + - - + + User= + + User name (if any) required by the network. Optional in 3GPP. + Defaults to unset. - - =PASSWORD - Password (if any) required by the network. Optional in 3GPP. + + + - - + + Password= + + Password (if any) required by the network. Optional in 3GPP. + Defaults to unset. - - =TYPE - Addressing type. Takes one of "none", "ipv4", "ipv6", "ipv4v6" or "any". - Optional in 3GPP and CDMA. + + + - - + + IPFamily= + + Addressing type. Takes one of ipv4, ipv6, + both, or any. Optional in 3GPP and CDMA. + Defaults to unset and automatically selected. - - =BOOL - A boolean. When true, connection is allowed during roaming. When false, - connection is not allowed during roaming. Optional in 3GPP. + + + - - + + AllowRoaming= + + A boolean. When true, connection is allowed during roaming. When false, + connection is not allowed during roaming. + Optional in 3GPP. Defaults to yes. - - =PIN - SIM-PIN unlock code. + + + - - + + PIN= + + SIM-PIN unlock code. Defaults to unset. - - =ID - ETSI MCC-MNC of a network to force registration. + + + - - + + OperatorId= + + ETSI MCC-MNC of a network to force registration. Defaults to unset. + @@ -6896,14 +6927,23 @@ LLDP=no LinkLocalAddressing=no IPv6AcceptRA=no -[ModemManager] -SimpleConnectProperties=apn=internet pin=1111 +[MobileNetwork] +APN=internet +AllowedAuthenticationMechanisms=none pap chap +User=user +Password=pass +IPFamily=both +AllowRoaming=no +PIN=1111 +OperatorId=25503 RouteMetric=30 UseGateway=yes This connects a cellular modem to a broadband network matched with the network interface wwan0, - with APN name internet, SIM card pin unlock code 1111 and sets up a default - gateway with route metric of 30. + with APN name internet, allowed authentication none, pcap, or + chap, user name user, their password pass, allows both IPv4 and IPv6, + does not allow roaming, SIM card pin unlock code 1111, only allows connecting to operator with + MCC 25503, and sets up a default gateway with route metric of 30. diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml index bf9526df8069f..2927980685250 100644 --- a/man/systemd.nspawn.xml +++ b/man/systemd.nspawn.xml @@ -340,6 +340,18 @@ + + RestrictAddressFamilies= + + Restricts the socket address families accessible to the container. This is equivalent + to the command line switch, and takes the same list + parameter. See + systemd-nspawn1 for + details. + + + + LimitCPU= LimitFSIZE= diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml index 12a3c0e644eba..b5d559849dc3a 100644 --- a/man/systemd.resource-control.xml +++ b/man/systemd.resource-control.xml @@ -481,13 +481,16 @@ CPUWeight=20 DisableControllers=cpu / \ This setting controls the controller in the unified hierarchy. - Takes a boolean argument. When true, pages stored in the Zswap cache are permitted to be - written to the backing storage, false otherwise. Defaults to true. This allows disabling - writeback of swap pages for IO-intensive applications, while retaining the ability to store - compressed pages in Zswap. See the kernel's + Takes a boolean argument. Defaults to true if DefaultMemoryZSwapWriteback= + is not set. When true, pages stored in the Zswap cache are permitted to be + written to the backing storage, false otherwise. This allows disabling writeback of swap pages for + IO-intensive applications, while retaining the ability to store compressed pages in Zswap. See the kernel's Zswap documentation for more details. + The system default for this setting may be controlled with DefaultMemoryZSwapWriteback= + in systemd-system.conf5. + @@ -1625,7 +1628,7 @@ DeviceAllow=/dev/loop-control Note that services are free to use the two environment variables, but it is unproblematic if they ignore them. Memory pressure handling must be implemented individually in each service, and usually means different things for different software. For further details on memory pressure - handling see Memory Pressure Handling in + handling see Resource Pressure Handling in systemd. Services implemented using @@ -1654,6 +1657,104 @@ DeviceAllow=/dev/loop-control + + + CPUPressureWatch= + + Controls CPU pressure monitoring for invoked processes. Takes a boolean or one of + auto and skip. If no, tells the service not + to watch for CPU pressure events, by setting the $CPU_PRESSURE_WATCH + environment variable to the literal string /dev/null. If yes, + tells the service to watch for CPU pressure events. This ensures the + cpu.pressure cgroup attribute file is accessible for + reading and writing by the service's user. It then sets the $CPU_PRESSURE_WATCH + environment variable for processes invoked by the unit to the file system path to this file. The + threshold information configured with CPUPressureThresholdSec= is encoded in + the $CPU_PRESSURE_WRITE environment variable. If the auto + value is set the protocol is enabled if CPU resource controls are configured for the unit (e.g. because + CPUWeight= or CPUQuota= is set), and + disabled otherwise. If set to skip the logic is neither enabled, nor disabled and + the two environment variables are not set. + + Note that services are free to use the two environment variables, but it is unproblematic if + they ignore them. CPU pressure handling must be implemented individually in each service, and + usually means different things for different software. + + Services implemented using + sd-event3 may use + sd_event_add_cpu_pressure3 + to watch for and handle CPU pressure events. + + If not explicitly set, defaults to the DefaultCPUPressureWatch= setting in + systemd-system.conf5. + + + + + + CPUPressureThresholdSec= + + Sets the CPU pressure threshold time for CPU pressure monitor as configured via + CPUPressureWatch=. Specifies the maximum CPU stall time before a CPU + pressure event is signalled to the service, per 2s window. If not specified, defaults to the + DefaultCPUPressureThresholdSec= setting in + systemd-system.conf5 + (which in turn defaults to 200ms). The specified value expects a time unit such as + ms or μs, see + systemd.time7 for + details on the permitted syntax. + + + + + + IOPressureWatch= + + Controls IO pressure monitoring for invoked processes. Takes a boolean or one of + auto and skip. If no, tells the service not + to watch for IO pressure events, by setting the $IO_PRESSURE_WATCH + environment variable to the literal string /dev/null. If yes, + tells the service to watch for IO pressure events. This enables IO accounting for the + service, and ensures the io.pressure cgroup attribute file is accessible for + reading and writing by the service's user. It then sets the $IO_PRESSURE_WATCH + environment variable for processes invoked by the unit to the file system path to this file. The + threshold information configured with IOPressureThresholdSec= is encoded in + the $IO_PRESSURE_WRITE environment variable. If the auto + value is set the protocol is enabled if IO accounting is anyway enabled for the unit (e.g. because + IOWeight= or IODeviceWeight= is set), and + disabled otherwise. If set to skip the logic is neither enabled, nor disabled and + the two environment variables are not set. + + Note that services are free to use the two environment variables, but it is unproblematic if + they ignore them. IO pressure handling must be implemented individually in each service, and + usually means different things for different software. + + Services implemented using + sd-event3 may use + sd_event_add_io_pressure3 + to watch for and handle IO pressure events. + + If not explicitly set, defaults to the DefaultIOPressureWatch= setting in + systemd-system.conf5. + + + + + + IOPressureThresholdSec= + + Sets the IO pressure threshold time for IO pressure monitor as configured via + IOPressureWatch=. Specifies the maximum IO stall time before an IO + pressure event is signalled to the service, per 2s window. If not specified, defaults to the + DefaultIOPressureThresholdSec= setting in + systemd-system.conf5 + (which in turn defaults to 200ms). The specified value expects a time unit such as + ms or μs, see + systemd.time7 for + details on the permitted syntax. + + + Coredump Control diff --git a/man/systemd.rr.xml b/man/systemd.rr.xml new file mode 100644 index 0000000000000..d6718ccf48e8b --- /dev/null +++ b/man/systemd.rr.xml @@ -0,0 +1,95 @@ + + + + + + + + systemd.rr + systemd + + + + systemd.rr + 5 + + + + systemd.rr + Local static DNS resource record definitions + + + + + /etc/systemd/resolve/static.d/*.rr + /run/systemd/resolve/static.d/*.rr + /usr/local/lib/systemd/resolve/static.d/*.rr + /usr/lib/systemd/resolve/static.d/*.rr + + + + + Description + + *.rr files may be used to define resource record sets ("RRsets") that shall be + resolvable locally, similar in style to address records defined by /etc/hosts (see + hosts5 for + details). These files are read by + systemd-resolved.service8, + and are used to synthesize local responses to local queries matching the defined resource record set. + + These drop-in files are in JSON format. Each file may either contain a single top-level DNS RR + object, or an array of one or more DNS RR objects. Each RR object has at least a key + subobject consisting of a name string field and a type integer + field (which contains the RR type in numeric form). Depending on the chosen type the RR object also has + the following fields: + + + For A/AAAA RRs, the RR object should have an address field set to + either an IP address formatted as string, or an array consisting of 4 or 16 8-bit unsigned integers for + the IP address. + + For PTR/NS/CNAME/DNAME RRs, the RR object should have a name field + set to the name the record shall point to. + + + This JSON serialization of DNS RRs matches the one returned by resolvectl. + + Currently no other RR types are supported. + + + + Examples + + Simple A Record + To make local address lookups for foobar.example.com resolve to the + 192.168.100.1 IPv4 address, create + /run/systemd/resolve/static.d/foobar_example_com.rr: + + +{ + "key" : { + "type" : 1, + "name" : "foobar.example.com" + }, + "address" : [ 192, 168, 100, 1 ] +} + + + + + + See Also + + systemd1 + systemd-resolved.service8 + resolved.conf5 + hosts5 + resolvectl1 + + + + diff --git a/man/systemd.service.xml b/man/systemd.service.xml index 549f36af1db77..d4a1978523011 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -1250,9 +1250,10 @@ RestartMaxDelaySec=160s file descriptor store is automatically released when the service is stopped; if restart (the default) it is kept around as long as the unit is neither inactive nor failed, or a job is queued for the service, or the service is expected to be restarted. If - yes the file descriptor store is kept around until the unit is removed from - memory (i.e. is not referenced anymore and inactive). The latter is useful to keep entries in the - file descriptor store pinned until the service manager exits. + yes the file descriptor store is kept around and garbage collection of the unit + is disabled. The latter is useful to keep entries in the file descriptor store pinned until the unit + is removed, the service manager exits, or the file descriptors get EPOLLHUP or + EPOLLERR. Use systemctl clean --what=fdstore … to release the file descriptor store explicitly. diff --git a/man/systemd.special.xml b/man/systemd.special.xml index 447ec57bfd496..f6f35b861d01a 100644 --- a/man/systemd.special.xml +++ b/man/systemd.special.xml @@ -745,7 +745,7 @@ Before=sleep.target Type=oneshot RemainAfterExit=yes ExecStart=/usr/bin/some-before-command -ExecStop=/Usr/bin/some-after-command +ExecStop=/usr/bin/some-after-command [Install] WantedBy=sleep.target diff --git a/man/systemd.swap.xml b/man/systemd.swap.xml index 2b65ba68f3f6d..2dc98d3f5d9bb 100644 --- a/man/systemd.swap.xml +++ b/man/systemd.swap.xml @@ -90,9 +90,15 @@ The following dependencies are added unless DefaultDependencies=no is set: - Swap units automatically acquire a Conflicts= and a + Local swap units automatically acquire a Conflicts= and a Before= dependency on umount.target so that they are deactivated at shutdown as well as a Before=swap.target dependency. + + Network swap units (those with in their options) automatically acquire + After= dependencies on remote-fs-pre.target and + network.target, plus After= and Wants= dependencies + on network-online.target, and a Before= dependency on + remote-fs.target instead of swap.target. @@ -124,7 +130,8 @@ With , the swap unit will not be added as a dependency for - swap.target. This means that it will not + swap.target (or remote-fs.target for network swap devices, + see below). This means that it will not be activated automatically during boot, unless it is pulled in by some other unit. The option has the opposite meaning and is the default. @@ -138,8 +145,8 @@ With , the swap unit will be only wanted, not required by - swap.target. This means that the boot - will continue even if this swap device is not activated + swap.target (or remote-fs.target for network swap + devices). This means that the boot will continue even if this swap device is not activated successfully. @@ -167,6 +174,21 @@ + + + + + Marks this swap device as requiring network access. This is useful for swap on + network block devices (e.g. iSCSI). + + Network swap units are ordered between remote-fs-pre.target and + remote-fs.target, instead of being ordered before + swap.target. They also pull in network-online.target and + are ordered after it and network.target. + + + + diff --git a/man/systemd.system-credentials.xml b/man/systemd.system-credentials.xml index e3e2887207784..fb1377c560c75 100644 --- a/man/systemd.system-credentials.xml +++ b/man/systemd.system-credentials.xml @@ -44,7 +44,7 @@ firstboot.keymap - The console key mapping to set (e.g. de). Read by + The console key mapping to set (e.g. de). Read by systemd-firstboot1, and only honoured if no console keymap has been configured before. @@ -52,6 +52,20 @@ + + firstboot.hostname + + This credential specifies the static system hostname to set during first boot. The + user will not be prompted for the hostname. Note that this controls the static hostname, not the transient + hostname, and only has an effect on first boot, unlike system.hostname (see + below). Read by + systemd-firstboot1 + and only honoured if no static hostname has been configured before. + + + + + firstboot.locale firstboot.locale-messages @@ -398,9 +412,10 @@ system.hostname Accepts a (transient) hostname to configure during early boot. The static hostname specified - in /etc/hostname, if configured, takes precedence over this setting. - Interpreted by the service manager (PID 1). For details see - systemd1. + in /etc/hostname, if configured, takes precedence over this setting. + Interpreted by the service manager (PID 1). For details see + systemd1. Also + see firstboot.hostname above. @@ -565,6 +580,16 @@ + + + imds.* + + + Read by systemd-imdsd@.service8. + + + + diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 37022ecc1c3aa..47c30d869fcbe 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -1082,6 +1082,11 @@ resources, …) are flushed out immediately after the unit completed, except for what is stored in the logging subsystem. Defaults to . + Since v261, if FileDescriptorStorePreserve= is set to , + and the unit has file descriptors stored, garbage collection will be disabled until the unit is + removed, the service manager exits, or the file descriptors get EPOLLHUP or + EPOLLERR. + @@ -1605,6 +1610,10 @@ measured-uki Unified Kernel Image with PCR 11 Measurements, as per systemd-stub7. + + measured-os + OS PCR measurements enabled. This is typically equivalent to measured-uki, however may also be set explicitly via the systemd.tpm2_measured_os= kernel command line switch, see kernel-command-line7 for details. The various system services doing boot and runtime measurements are conditioned on this flag. + diff --git a/man/systemd.xml b/man/systemd.xml index 06d9102e475f1..30ae385029b10 100644 --- a/man/systemd.xml +++ b/man/systemd.xml @@ -1061,6 +1061,16 @@ + + + systemd.minimum_uptime_sec= + + Takes a time in seconds. Specifies the minimum uptime of the system before the system + shuts down. For more information see the MinimumUptimeSec= setting described in + systemd-system.conf5. + + + For other kernel command line parameters understood by diff --git a/man/userdbctl.xml b/man/userdbctl.xml index 4123190a458ec..ce8367d9a8d16 100644 --- a/man/userdbctl.xml +++ b/man/userdbctl.xml @@ -256,7 +256,7 @@ - + When used with the user or group command, read the user definition in JSON format from the specified file, instead of querying it from the diff --git a/man/varlinkctl.xml b/man/varlinkctl.xml index b96e35372fa9b..72f1983c9ef29 100644 --- a/man/varlinkctl.xml +++ b/man/varlinkctl.xml @@ -64,7 +64,7 @@ varlinkctl OPTIONS - --exec call + --exec call ADDRESS METHOD @@ -73,6 +73,14 @@ CMDLINE + + varlinkctl + OPTIONS + serve + METHOD + CMDLINE + + varlinkctl OPTIONS @@ -181,6 +189,28 @@ + + serve METHOD CMDLINE… + + Run a Varlink server that accepts protocol upgrade requests for the specified method + and connects the upgraded connection to the standard input and output of the specified command. This + can act as a server-side counterpart to call . + + The listening socket must be passed via socket activation (i.e. the + $LISTEN_FDS protocol), making this command suitable for use in socket-activated + service units. When a client calls the specified method with the upgrade flag, the server sends a + reply confirming the upgrade, then forks and executes the given command line with the upgraded + connection on its standard input and output. + + This effectively turns any command that speaks a protocol over standard input/output into a + Varlink service, discoverable via the service registry and authenticated via socket credentials. + Because each connection is handled by a forked child process, the service unit can apply systemd's + sandboxing options (such as ProtectSystem=, etc.) and does not operate in the + caller's environment. + + + + list-registry @@ -269,6 +299,30 @@ + + + + When used with call: request a protocol upgrade. The method call + is sent with the upgrade flag set. The service is expected to send a single + reply confirming the upgrade. After the reply, the Varlink protocol is no longer in effect on + the connection. + + If is not specified, varlinkctl acts as a + bidirectional proxy: data read from standard input is forwarded to the upgraded connection, and data + received from the connection is written to standard output. + + If is specified, the upgraded connection socket is placed on both + standard input and standard output of the invoked process. This is similar to the regular + behavior (without ), which places the method call + reply on standard input. The invoked process can thus simply read from and write to + stdin/stdout to communicate over the upgraded protocol. + + This option may not be combined with , , + , , or . + + + + @@ -509,6 +563,46 @@ method Extend( # varlinkctl call ssh-exec:somehost:systemd-creds org.varlink.service.GetInfo '{}' + + Serving a Sandboxed Decompressor via Protocol Upgrade + + The following socket and service units expose xz decompression as a Varlink + service. Clients connect and send compressed data over the upgraded connection, receiving decompressed + output in return. + + # /etc/systemd/system/varlink-decompress-xz.socket +[Socket] +ListenStream=/run/varlink/registry/com.example.Decompress.XZ + +[Install] +WantedBy=sockets.target + +# /etc/systemd/system/varlink-decompress-xz.service +[Service] +ExecStart=varlinkctl serve com.example.Decompress.XZ xz -d +DynamicUser=yes +PrivateNetwork=yes +ProtectSystem=strict +ProtectHome=yes +NoNewPrivileges=yes +SystemCallFilter=~@privileged @resources +MemoryMax=256M + + A client can then decompress data through this service: + + $ echo "hello" | xz | varlinkctl call --upgrade \ + unix:/run/varlink/registry/com.example.Decompress.XZ \ + com.example.Decompress.XZ '{}' +hello + + For quick testing without unit files, systemd-socket-activate can be used + to provide the listening socket: + + $ systemd-socket-activate -l /tmp/decompress.sock -- varlinkctl serve com.example.Decompress.XZ xz -d & +$ echo "hello" | xz | varlinkctl call --upgrade unix:/tmp/decompress.sock com.example.Decompress.XZ '{}' +hello + + diff --git a/man/version-info.xml b/man/version-info.xml index 54440febd0f2c..baff5df5f5852 100644 --- a/man/version-info.xml +++ b/man/version-info.xml @@ -84,4 +84,8 @@ Added in version 258. Added in version 259. Added in version 260. + Added in version 261. + Added in version 262. + Added in version 263. + Added in version 264. diff --git a/meson.build b/meson.build index a6ec8e11ac446..faf4209dfd1f9 100644 --- a/meson.build +++ b/meson.build @@ -291,6 +291,7 @@ conf.set_quoted('SYSTEMD_USERWORK_PATH', libexecdir / 'syst conf.set_quoted('SYSTEMD_MOUNTWORK_PATH', libexecdir / 'systemd-mountwork') conf.set_quoted('SYSTEMD_NSRESOURCEWORK_PATH', libexecdir / 'systemd-nsresourcework') conf.set_quoted('SYSTEMD_VERITYSETUP_PATH', libexecdir / 'systemd-veritysetup') +conf.set_quoted('SYSTEMD_CLONESETUP_PATH', bindir / 'systemd-clonesetup') conf.set_quoted('SYSTEM_CONFIG_UNIT_DIR', pkgsysconfdir / 'system') conf.set_quoted('SYSTEM_DATA_UNIT_DIR', systemunitdir) conf.set_quoted('SYSTEM_ENV_GENERATOR_DIR', systemenvgeneratordir) @@ -538,6 +539,26 @@ conf.set10('HAVE_WARNING_ZERO_LENGTH_BOUNDS', have) have = cc.has_argument('-Wzero-as-null-pointer-constant') conf.set10('HAVE_WARNING_ZERO_AS_NULL_POINTER_CONSTANT', have) +possible_c_attributes = [ + 'alloc_size', + 'fallthrough', + 'retain', +] + +foreach attr : possible_c_attributes + have = cc.has_function_attribute(attr) + conf.set10('HAVE_ATTRIBUTE_' + attr.to_upper(), have) +endforeach + +# TODO: drop this manual check when meson learns about this attribute +possible_c_attributes += ['no_reorder'] + +have = cc.compiles( + '__attribute__((__no_reorder__)) int x;', + args : '-Werror=attributes', + name : '__attribute__((__no_reorder__))') +conf.set10('HAVE_ATTRIBUTE_NO_REORDER', have) + ##################################################################### # compilation result tests @@ -585,6 +606,7 @@ foreach ident : [ ['fchmodat2', '''#include '''], # no known header declares fchmodat2 ['bpf', '''#include '''], # no known header declares bpf ['kcmp', '''#include '''], # no known header declares kcmp + ['kexec_file_load', '''#include '''], # no known header declares kexec_file_load ['keyctl', '''#include '''], # no known header declares keyctl ['add_key', '''#include '''], # no known header declares add_key ['request_key', '''#include '''], # no known header declares request_key @@ -895,6 +917,7 @@ foreach option : ['adm-gid', 'video-gid', 'wheel-gid', 'systemd-journal-gid', + 'systemd-imds-uid', 'systemd-network-uid', 'systemd-resolve-uid', 'systemd-timesync-uid'] @@ -1045,86 +1068,6 @@ libbpf = dependency('libbpf', libbpf_cflags = libbpf.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_LIBBPF', libbpf.found()) -if not libbpf.found() - conf.set10('BPF_FRAMEWORK', false) -else - clang_found = false - clang_supports_bpf = false - bpf_gcc_found = false - bpftool_strip = false - deps_found = false - - if bpf_compiler == 'clang' - # Support 'versioned' clang/llvm-strip binaries, as seen on Debian/Ubuntu - # (like clang-10/llvm-strip-10) - if meson.is_cross_build() or cc.get_id() != 'clang' or cc.cmd_array()[0].contains('afl-clang') or cc.cmd_array()[0].contains('hfuzz-clang') - r = find_program('clang', - required : bpf_framework, - version : '>= 10.0.0') - clang_found = r.found() - if clang_found - clang = r.full_path() - endif - else - clang_found = true - clang = cc.cmd_array() - endif - - if clang_found - # Check if 'clang -target bpf' is supported. - clang_supports_bpf = run_command(clang, '-target', 'bpf', '--print-supported-cpus', check : false).returncode() == 0 - endif - if bpf_framework.enabled() and not clang_supports_bpf - error('bpf-framework was enabled but clang does not support bpf') - endif - elif bpf_compiler == 'gcc' - bpf_gcc = find_program('bpf-gcc', - 'bpf-none-gcc', - 'bpf-unknown-none-gcc', - required : true, - version : '>= 13.1.0') - bpf_gcc_found = bpf_gcc.found() - endif - - if clang_supports_bpf or bpf_gcc_found - # Debian installs this in /usr/sbin/ which is not in $PATH. - # We check for 'bpftool' first, honouring $PATH, and in /usr/sbin/ for Debian. - # We use 'bpftool gen object' subcommand for bpftool strip, it was added by d80b2fcbe0a023619e0fc73112f2a02c2662f6ab (v5.13). - bpftool = find_program('bpftool', - '/usr/sbin/bpftool', - required : bpf_framework.enabled() and bpf_compiler == 'gcc', - version : bpf_compiler == 'gcc' ? '>= 7.0.0' : '>= 5.13.0') - - if bpftool.found() - bpftool_strip = true - deps_found = true - elif bpf_compiler == 'clang' - # We require the 'bpftool gen skeleton' subcommand, it was added by 985ead416df39d6fe8e89580cc1db6aa273e0175 (v5.6). - bpftool = find_program('bpftool', - '/usr/sbin/bpftool', - required : bpf_framework, - version : '>= 5.6.0') - endif - - # We use `llvm-strip` as a fallback if `bpftool gen object` strip support is not available. - if not bpftool_strip and bpftool.found() and clang_supports_bpf - if not meson.is_cross_build() - llvm_strip_bin = run_command(clang, '--print-prog-name', 'llvm-strip', - check : true).stdout().strip() - else - llvm_strip_bin = 'llvm-strip' - endif - llvm_strip = find_program(llvm_strip_bin, - required : bpf_framework, - version : '>= 10.0.0') - deps_found = llvm_strip.found() - endif - endif - - # Can build BPF program from source code in restricted C - conf.set10('BPF_FRAMEWORK', deps_found) -endif - libmount = dependency('mount', version : fuzzer_build ? '>= 0' : '>= 2.30', required : get_option('libmount')) @@ -1134,8 +1077,8 @@ libmount_cflags = libmount.partial_dependency(includes: true, compile_args: true libfdisk = dependency('fdisk', version : '>= 2.32', - disabler : true, required : get_option('fdisk')) +libfdisk_cflags = libfdisk.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_LIBFDISK', libfdisk.found()) # This prefers pwquality if both are enabled or auto. @@ -1239,6 +1182,7 @@ libmicrohttpd = dependency('libmicrohttpd', version : '>= 0.9.33', required : get_option('microhttpd')) conf.set10('HAVE_MICROHTTPD', libmicrohttpd.found()) +libmicrohttpd_cflags = libmicrohttpd.partial_dependency(includes: true, compile_args: true) libcryptsetup = get_option('libcryptsetup') libcryptsetup_plugins = get_option('libcryptsetup-plugins') @@ -1274,6 +1218,7 @@ endforeach libcurl = dependency('libcurl', version : '>= 7.32.0', required : get_option('libcurl')) +libcurl_cflags = libcurl.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_LIBCURL', libcurl.found()) conf.set10('CURL_NO_OLDIES', conf.get('BUILD_MODE_DEVELOPER') == 1) @@ -1309,10 +1254,12 @@ libgnutls = dependency('gnutls', version : '>= 3.1.4', required : get_option('gnutls')) conf.set10('HAVE_GNUTLS', libgnutls.found()) +libgnutls_cflags = libgnutls.partial_dependency(includes: true, compile_args: true) libopenssl = dependency('openssl', version : '>= 3.0.0', required : get_option('openssl')) +libopenssl_cflags = libopenssl.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_OPENSSL', libopenssl.found()) libp11kit = dependency('p11-kit-1', @@ -1352,6 +1299,7 @@ conf.set10('HAVE_DWFL_SET_SYSROOT', libz = dependency('zlib', required : get_option('zlib')) conf.set10('HAVE_ZLIB', libz.found()) +libz_cflags = libz.partial_dependency(includes: true, compile_args: true) feature = get_option('bzip2') libbzip2 = dependency('bzip2', @@ -1361,6 +1309,7 @@ if not libbzip2.found() libbzip2 = cc.find_library('bz2', required : feature) endif conf.set10('HAVE_BZIP2', libbzip2.found()) +libbzip2_cflags = libbzip2.partial_dependency(includes: true, compile_args: true) libxz = dependency('liblzma', required : get_option('xz')) @@ -1528,6 +1477,12 @@ conf.set('DEFAULT_DNSSEC_MODE', 'DNSSEC_' + default_dnssec.underscorify().to_upper()) conf.set_quoted('DEFAULT_DNSSEC_MODE_STR', default_dnssec) +have = get_option('imds').require( + conf.get('HAVE_LIBCURL') == 1, + error_message : 'curl required').allowed() +conf.set10('ENABLE_IMDS', have) +conf.set('IMDS_NETWORK_DEFAULT', 'IMDS_NETWORK_@0@'.format(get_option('imds-network')).to_upper()) + have = get_option('importd').require( conf.get('HAVE_LIBCURL') == 1 and conf.get('HAVE_OPENSSL') == 1 and @@ -1699,171 +1654,6 @@ endif ##################################################################### -if conf.get('BPF_FRAMEWORK') == 1 - bpf_clang_flags = [ - '-std=gnu17', - '-Wno-compare-distinct-pointer-types', - '-Wno-microsoft-anon-tag', - '-fms-extensions', - '-fno-stack-protector', - '-O2', - '-target', - 'bpf', - '-g', - '-c', - ] - - bpf_gcc_flags = [ - '-std=gnu17', - '-fms-extensions', - '-fno-stack-protector', - '-fno-ssa-phiopt', - '-O2', - '-mcpu=v3', - '-mco-re', - '-gbtf', - '-c', - ] - - # If c_args contains these flags copy them along with the values, in order to avoid breaking - # reproducible builds and other functionality - propagate_cflags = [ - '-ffile-prefix-map=', - '-fdebug-prefix-map=', - '-fmacro-prefix-map=', - '--sysroot=', - ] - - foreach opt : c_args - foreach flag : propagate_cflags - if opt.startswith(flag) - bpf_clang_flags += [opt] - bpf_gcc_flags += [opt] - break - endif - endforeach - endforeach - - # Generate defines that are appropriate to tell the compiler what architecture - # we're compiling for. By default we just map meson's cpu_family to ____. - # This dictionary contains the exceptions where this doesn't work. - # - # C.f. https://mesonbuild.com/Reference-tables.html#cpu-families - # and src/basic/missing_syscall_def.h. - - # Start with older ABI. When define is missing, we're likely targeting that. - ppc64_elf_version = '1' - - if host_machine.cpu_family() == 'ppc64' - # cc doesn't have to be bpf_compiler, but they should be targeting the same ABI - call_elf_value = cc.get_define('_CALL_ELF') - if call_elf_value != '' - ppc64_elf_version = call_elf_value - endif - endif - - cpu_arch_defines = { - 'ppc' : ['-D__powerpc__', '-D__TARGET_ARCH_powerpc'], - 'ppc64' : ['-D__powerpc64__', '-D__TARGET_ARCH_powerpc', '-D_CALL_ELF=' + ppc64_elf_version], - 'riscv32' : ['-D__riscv', '-D__riscv_xlen=32', '-D__TARGET_ARCH_riscv'], - 'riscv64' : ['-D__riscv', '-D__riscv_xlen=64', '-D__TARGET_ARCH_riscv'], - 'x86' : ['-D__i386__', '-D__TARGET_ARCH_x86'], - 's390x' : ['-D__s390__', '-D__s390x__', '-D__TARGET_ARCH_s390'], - - # For arm, assume hardware fp is available. - 'arm' : ['-D__arm__', '-D__ARM_PCS_VFP', '-D__TARGET_ARCH_arm'], - 'loongarch64' : ['-D__loongarch__', '-D__loongarch_grlen=64', '-D__TARGET_ARCH_loongarch'] - } - - bpf_arch_flags = cpu_arch_defines.get(host_machine.cpu_family(), - ['-D__@0@__'.format(host_machine.cpu_family())]) - if bpf_compiler == 'gcc' - bpf_arch_flags += ['-m' + host_machine.endian() + '-endian'] - endif - - libbpf_include_dir = libbpf.get_variable(pkgconfig : 'includedir') - - bpf_o_unstripped_cmd = [] - if bpf_compiler == 'clang' - bpf_o_unstripped_cmd += [ - clang, - bpf_clang_flags, - bpf_arch_flags, - ] - elif bpf_compiler == 'gcc' - bpf_o_unstripped_cmd += [ - bpf_gcc, - bpf_gcc_flags, - bpf_arch_flags, - ] - endif - - bpf_o_unstripped_cmd += ['-I.', '-include', 'config.h'] - - if cc.get_id() == 'gcc' or meson.is_cross_build() - if cc.get_id() != 'gcc' - warning('Cross compiler is not gcc. Guessing the target triplet for bpf likely fails.') - endif - target_triplet_cmd = run_command(cc.cmd_array(), '-print-multiarch', check: false) - else - # clang does not support -print-multiarch (D133170) and its -dump-machine - # does not match multiarch. Query gcc instead. - target_triplet_cmd = run_command('gcc', '-print-multiarch', check: false) - endif - if target_triplet_cmd.returncode() == 0 - sysroot = meson.get_external_property('sys_root', '/') - target_triplet = target_triplet_cmd.stdout().strip() - target_include_dir = sysroot / 'usr' / 'include' - target_triple_include_dir = target_include_dir / target_triplet - isystem_dir = '' - if fs.is_dir(target_triple_include_dir) - isystem_dir = target_triple_include_dir - elif fs.is_dir(target_include_dir) - isystem_dir = target_include_dir - endif - if isystem_dir != '' - bpf_o_unstripped_cmd += [ - '-isystem', isystem_dir - ] - endif - endif - - bpf_o_unstripped_cmd += [ - '-idirafter', - libbpf_include_dir, - '@INPUT@', - '-o', - '@OUTPUT@' - ] - - if bpftool_strip - bpf_o_cmd = [ - bpftool, - 'gen', - 'object', - '@OUTPUT@', - '@INPUT@' - ] - elif bpf_compiler == 'clang' - bpf_o_cmd = [ - llvm_strip, - '-g', - '@INPUT@', - '-o', - '@OUTPUT@' - ] - endif - - skel_h_cmd = [ - bpftool, - 'gen', - 'skeleton', - '@INPUT@' - ] -endif - -##################################################################### - efi_arch = { 'aarch64' : 'aa64', 'arm' : 'arm', @@ -1901,77 +1691,6 @@ conf.set10('ENABLE_UKIFY', want_ukify) ##################################################################### -use_provided_vmlinux_h = false -use_generated_vmlinux_h = false -provided_vmlinux_h_path = get_option('vmlinux-h-path') - -# For the more complex BPF programs we really want a vmlinux.h (which is arch -# specific, but only somewhat bound to kernel version). Ideally the kernel -# development headers would ship that, but right now they don't. Hence address -# this in two ways: -# -# 1. Provide a vmlinux.h at build time -# 2. Generate the file on the fly where possible (which requires /sys/ to be mounted) -# -# We generally prefer the former (to support reproducible builds), but will -# fallback to the latter. - -if conf.get('BPF_FRAMEWORK') == 1 - enable_vmlinux_h = get_option('vmlinux-h') - - if enable_vmlinux_h == 'auto' - if provided_vmlinux_h_path != '' - use_provided_vmlinux_h = true - elif fs.exists('/sys/kernel/btf/vmlinux') and \ - bpftool.found() and \ - (host_machine.cpu_family() == build_machine.cpu_family()) and \ - host_machine.cpu_family() in ['x86_64', 'aarch64'] - - # We will only generate a vmlinux.h from the running - # kernel if the host and build machine are of the same - # family. Also for now we focus on x86_64 and aarch64, - # since other archs don't seem to be ready yet. - - use_generated_vmlinux_h = true - endif - elif enable_vmlinux_h == 'provided' - use_provided_vmlinux_h = true - elif enable_vmlinux_h == 'generated' - if not fs.exists('/sys/kernel/btf/vmlinux') - error('BTF data from kernel not available (/sys/kernel/btf/vmlinux missing), cannot generate vmlinux.h, but was asked to.') - endif - if not bpftool.found() - error('bpftool not available, cannot generate vmlinux.h, but was asked to.') - endif - use_generated_vmlinux_h = true - endif -endif - -vmlinux_h_dependency = [] -if use_provided_vmlinux_h - if not fs.exists(provided_vmlinux_h_path) - error('Path to provided vmlinux.h does not exist.') - endif - bpf_o_unstripped_cmd += ['-I' + fs.parent(provided_vmlinux_h_path)] - message(f'Using provided @provided_vmlinux_h_path@') -elif use_generated_vmlinux_h - vmlinux_h_dependency = custom_target( - output: 'vmlinux.h', - command : [ bpftool, 'btf', 'dump', 'file', '/sys/kernel/btf/vmlinux', 'format', 'c' ], - capture : true) - - bpf_o_unstripped_cmd += ['-I' + fs.parent(vmlinux_h_dependency.full_path())] - message('Using generated @0@'.format(vmlinux_h_dependency.full_path())) -else - message('Using neither provided nor generated vmlinux.h, some features will not be available.') -endif - -conf.set10('HAVE_VMLINUX_H', use_provided_vmlinux_h or use_generated_vmlinux_h) - -conf.set10('ENABLE_SYSCTL_BPF', conf.get('HAVE_VMLINUX_H') == 1 and libbpf.version().version_compare('>= 0.7')) - -##################################################################### - version_tag = get_option('version-tag') if version_tag == '' version_tag = meson.project_version() @@ -1979,8 +1698,11 @@ endif conf.set_quoted('VERSION_TAG', version_tag) +generated_sources = [] + subdir('tools') subdir('src/version') +subdir('src/bpf') shared_lib_tag = get_option('shared-lib-tag') if shared_lib_tag == '' @@ -2020,7 +1742,6 @@ executables = [] executables_by_name = {} objects_by_name = {} fuzzer_exes = [] -generated_sources = [version_h, vmlinux_h_dependency] sources = [] # binaries that have --help and are intended for use by humans, @@ -2087,7 +1808,13 @@ libsystemd_includes = [basic_includes, include_directories( 'src/libsystemd/sd-resolve', 'src/libsystemd/sd-varlink')] -includes = [libsystemd_includes, include_directories('src/shared')] +includes = [ + libsystemd_includes, + include_directories( + 'src/shared', + 'src/bpf', + ), +] subdir('po') subdir('catalog') @@ -2349,6 +2076,7 @@ subdir('src/debug-generator') subdir('src/delta') subdir('src/detect-virt') subdir('src/dissect') +subdir('src/clonesetup') subdir('src/environment-d-generator') subdir('src/escape') subdir('src/factory-reset') @@ -2364,6 +2092,7 @@ subdir('src/hostname') subdir('src/hwdb') subdir('src/id128') subdir('src/import') +subdir('src/imds') # Note, we are not alphabetically here, since we want to use a variable from src/import/ here subdir('src/integritysetup') subdir('src/journal') subdir('src/journal-remote') @@ -2487,11 +2216,18 @@ foreach dict : executables exe_sources = dict.get('sources', []) + dict.get('extract', []) + foreach bpf_name : dict.get('bpf_programs', []) + if bpf_name in bpf_programs_by_name + exe_sources += bpf_programs_by_name[bpf_name] + endif + endforeach + kwargs = {} foreach key, val : dict if key in ['name', 'dbus', 'public', 'conditions', 'type', 'suite', 'timeout', 'parallel', 'objects', 'sources', 'extract', - 'include_directories', 'build_by_default', 'install'] + 'include_directories', 'build_by_default', 'install', + 'bpf_programs'] continue endif @@ -2509,7 +2245,7 @@ foreach dict : executables foreach val : dict.get('objects', []) obj = objects_by_name[val] - kwargs += { 'objects' : obj['objects'] } + kwargs += { 'objects' : kwargs.get('objects', []) + obj['objects'] } include_directories += obj['include_directories'] endforeach @@ -2777,7 +2513,7 @@ if install_tests install_subdir('mkosi', install_dir : testsdir, exclude_files : ['mkosi.local.conf', 'mkosi.key', 'mkosi.crt'], - exclude_directories : ['mkosi.local']) + exclude_directories : ['mkosi.local', 'mkosi.tools']) endif ############################################################ @@ -2866,7 +2602,13 @@ if git.found() 'ls-files', ':/*.[ch]', ':/*.cc', check : false) if all_files.returncode() == 0 - all_files = files(all_files.stdout().split()) + existing_files = [] + foreach f : all_files.stdout().split() + if fs.exists(f) + existing_files += f + endif + endforeach + all_files = files(existing_files) custom_target( output : 'tags', @@ -2953,6 +2695,34 @@ if meson.version().version_compare('>=1.4.0') endforeach endif +spatch = find_program('spatch', required : false) +if spatch.found() + coccinelle_exclude = [ + # libc/ has no assert() or systemd-headers so leave it + 'src/libc/', + # test/ has some deliberate wonky pointers, just leave excluded + 'src/test/', + ] + + coccinelle_src_dirs = run_command( + 'sh', '-c', 'printf "%s\n" src/*/', + check : true, + ).stdout().strip().split('\n') + + foreach dir : coccinelle_src_dirs + if dir not in coccinelle_exclude + test( + 'coccinelle-@0@'.format(fs.name(dir.strip('/'))), + check_coccinelle_sh, + args : [meson.project_source_root() / dir, + meson.project_source_root() / 'coccinelle'], + suite : 'coccinelle', + timeout : 120, + ) + endif + endforeach +endif + symbol_analysis_exes = [] foreach name, exe : executables_by_name symbol_analysis_exes += exe @@ -3069,7 +2839,8 @@ summary({ 'default user $PATH' : default_user_path != '' ? default_user_path : '(same as system services)', 'systemd service watchdog' : service_watchdog == '' ? 'disabled' : service_watchdog, 'time epoch' : f'@time_epoch@ (@alt_time_epoch@)', - 'TPM2 nvpcr base' : run_command(sh, '-c', 'printf 0x%x @0@'.format(get_option('tpm2-nvpcr-base')), check : true).stdout() + 'TPM2 nvpcr base' : run_command(sh, '-c', 'printf 0x%x @0@'.format(get_option('tpm2-nvpcr-base')), check : true).stdout(), + 'IMDS networking' : get_option('imds-network'), }) # TODO: @@ -3134,6 +2905,7 @@ foreach tuple : [ ['homed'], ['hostnamed'], ['hwdb'], + ['imds'], ['importd'], ['initrd'], ['kernel-install'], diff --git a/meson.version b/meson.version index b17ac6a71486d..ca05aea76d08c 100644 --- a/meson.version +++ b/meson.version @@ -1 +1 @@ -260~rc1 +261~devel diff --git a/meson_options.txt b/meson_options.txt index c1af7ce237492..d61afac519d84 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -142,6 +142,10 @@ option('timedated', type : 'boolean', description : 'install the systemd-timedated daemon') option('timesyncd', type : 'boolean', description : 'install the systemd-timesyncd daemon') +option('imds', type : 'feature', + description : 'install the systemd-imds stack') +option('imds-network', type : 'combo', choices : ['unlocked', 'locked'], + description : 'whether to default to locked/unlocked IMDS network mode') option('journal-storage-default', type : 'combo', choices : ['persistent', 'auto', 'volatile', 'none'], description : 'default storage mode for journald (main namespace)') option('remote', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' }, @@ -334,6 +338,8 @@ option('systemd-resolve-uid', type : 'integer', value : 0, description : 'soft-static allocation for the systemd-resolve user') option('systemd-timesync-uid', type : 'integer', value : 0, description : 'soft-static allocation for the systemd-timesync user') +option('systemd-imds-uid', type : 'integer', value : 0, + description : 'soft-static allocation for the systemd-imds user') option('dev-kvm-mode', type : 'string', value : '0666', description : '/dev/kvm access mode') diff --git a/mkosi/mkosi.conf b/mkosi/mkosi.conf index 80c4e59390c95..2fc087cb73f40 100644 --- a/mkosi/mkosi.conf +++ b/mkosi/mkosi.conf @@ -59,8 +59,7 @@ ExtraTrees= KernelInitrdModules=default -# Disable relabeling by default as it only matters for TEST-06-SELINUX, takes a non-trivial amount of time -# and results in lots of errors when building images as a regular user. +# Disable relabeling by default as TEST-06-SELINUX handles relabeling itself at runtime. SELinuxRelabel=no # Adding more kernel command line arguments is likely to hit the kernel command line limit (512 bytes) in @@ -90,7 +89,6 @@ Packages= attr bash-completion binutils - coreutils cpio curl diffutils @@ -103,7 +101,6 @@ Packages= gzip jq kbd - kexec-tools kmod less lsof @@ -123,6 +120,7 @@ Packages= sed socat strace + swtpm tar tree util-linux diff --git a/mkosi/mkosi.conf.d/arch/mkosi.conf b/mkosi/mkosi.conf.d/arch/mkosi.conf index 9bea621fcaa60..229cc6394b172 100644 --- a/mkosi/mkosi.conf.d/arch/mkosi.conf +++ b/mkosi/mkosi.conf.d/arch/mkosi.conf @@ -17,6 +17,7 @@ Packages= bind bpf btrfs-progs + coreutils cryptsetup dbus-broker dbus-broker-units @@ -54,3 +55,4 @@ Packages= tpm2-tools # kernel-bootcfg --add-uri= is just too useful virt-firmware + virtiofsd diff --git a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf index fbbc6a90bf91c..fc9ffd58c968b 100644 --- a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf +++ b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf @@ -23,6 +23,7 @@ VolatilePackages= Packages= bind-utils bpftool + coreutils cryptsetup device-mapper-event device-mapper-multipath @@ -42,6 +43,7 @@ Packages= kernel-core knot libcap-ng-utils + libmicrohttpd libucontext man-db nmap-ncat @@ -63,8 +65,10 @@ Packages= softhsm squashfs-tools stress-ng + swtpm-tools tpm2-tools veritysetup vim-common # kernel-bootcfg --add-uri= is just too useful virt-firmware + virtiofsd diff --git a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf index f024eae204d0f..8a4e534ddad60 100644 --- a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf +++ b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf @@ -71,6 +71,8 @@ Packages= softhsm2 squashfs-tools stress-ng + swtpm-tools tgt tpm2-tools tzdata + virtiofsd diff --git a/mkosi/mkosi.conf.d/debian/mkosi.conf b/mkosi/mkosi.conf.d/debian/mkosi.conf index c960a1b2ecd4e..f0ecec311a875 100644 --- a/mkosi/mkosi.conf.d/debian/mkosi.conf +++ b/mkosi/mkosi.conf.d/debian/mkosi.conf @@ -8,4 +8,5 @@ Release=testing [Content] Packages= + coreutils linux-perf diff --git a/mkosi/mkosi.conf.d/opensuse/mkosi.conf b/mkosi/mkosi.conf.d/opensuse/mkosi.conf index d01c6658c0ffd..5abebc04cf18b 100644 --- a/mkosi/mkosi.conf.d/opensuse/mkosi.conf +++ b/mkosi/mkosi.conf.d/opensuse/mkosi.conf @@ -35,6 +35,7 @@ Packages= bind-utils bpftool btrfs-progs + coreutils cryptsetup device-mapper dhcp-server @@ -56,6 +57,7 @@ Packages= libcap-progs libdw-devel libdw1 + libmicrohttpd12 libtss2-tcti-device0 libz1 man @@ -83,6 +85,7 @@ Packages= softhsm squashfs stress-ng + system-user-bin tgt timezone tpm2.0-tools @@ -90,5 +93,6 @@ Packages= veritysetup # kernel-bootcfg --add-uri= is just too useful virt-firmware + virtiofsd xz zypper diff --git a/mkosi/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf b/mkosi/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf new file mode 100644 index 0000000000000..86d4d1132b9ba --- /dev/null +++ b/mkosi/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# avoid pulling in the uutils package which is the default and is broken in several ways + +[TriggerMatch] +Distribution=ubuntu +Release=!jammy +Release=!noble + +[Content] +Packages= + coreutils-from-gnu + coreutils-from-uutils- + rust-coreutils- diff --git a/mkosi/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf b/mkosi/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf new file mode 100644 index 0000000000000..b680604f001d8 --- /dev/null +++ b/mkosi/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[TriggerMatch] +Distribution=ubuntu +Release=jammy +Release=noble + +[Content] +Packages= + coreutils diff --git a/mkosi/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset b/mkosi/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset index e87172ad86b2a..4423c3dabd7c2 100644 --- a/mkosi/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset +++ b/mkosi/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset @@ -9,9 +9,6 @@ disable dnsmasq.service disable isc-dhcp-server.service disable isc-dhcp-server6.service -# Pulled in via dracut-network by kexec-tools on Fedora. -disable NetworkManager* - # Make sure dbus-broker is started by default on Debian/Ubuntu. enable dbus-broker.service @@ -49,7 +46,7 @@ disable fstrim.timer disable raid-check.timer disable systemd-tmpfiles-clean.timer -# mkosi relabels the image itself so no need to do it on boot. +# TEST-06-SELINUX handles relabeling itself at runtime. disable selinux-autorelabel-mark.service enable coverage-forwarder.service diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/bpftool.conf b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/bpftool.conf new file mode 100644 index 0000000000000..df2010cee4f5c --- /dev/null +++ b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/bpftool.conf @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# bpftool was untangled in resolute + +[TriggerMatch] +Distribution=ubuntu +Release=!jammy +Release=!noble + +[Content] +Packages=bpftool diff --git a/mkosi/mkosi.images/minimal-0/mkosi.conf b/mkosi/mkosi.images/minimal-0/mkosi.conf index 0e897a53c2381..5d6717f897bd3 100644 --- a/mkosi/mkosi.images/minimal-0/mkosi.conf +++ b/mkosi/mkosi.images/minimal-0/mkosi.conf @@ -9,8 +9,6 @@ SplitArtifacts=yes [Build] Environment=SYSTEMD_REPART_OVERRIDE_FSTYPE=squashfs -Incremental=relaxed -CacheOnly=metadata [Content] BaseTrees=%O/minimal-base diff --git a/mkosi/mkosi.images/minimal-1/mkosi.conf b/mkosi/mkosi.images/minimal-1/mkosi.conf index 0e897a53c2381..5d6717f897bd3 100644 --- a/mkosi/mkosi.images/minimal-1/mkosi.conf +++ b/mkosi/mkosi.images/minimal-1/mkosi.conf @@ -9,8 +9,6 @@ SplitArtifacts=yes [Build] Environment=SYSTEMD_REPART_OVERRIDE_FSTYPE=squashfs -Incremental=relaxed -CacheOnly=metadata [Content] BaseTrees=%O/minimal-base diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf index 8e57cd032dfea..48b45b7a3197c 100644 --- a/mkosi/mkosi.images/minimal-base/mkosi.conf +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf @@ -5,7 +5,6 @@ Format=directory [Build] Environment=SYSTEMD_REQUIRED_DEPS_ONLY=1 -Incremental=relaxed [Content] Bootable=no @@ -15,7 +14,6 @@ CleanPackageMetadata=yes Packages= bash - coreutils grep socat util-linux diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/arch.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/arch.conf index 6d77d2305d13b..7add5d32f6cde 100644 --- a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/arch.conf +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/arch.conf @@ -6,10 +6,14 @@ Distribution=arch [Content] PrepareScripts=%D/mkosi/mkosi.conf.d/arch/systemd.prepare Packages= + coreutils inetutils iproute nmap +VolatilePackages= + systemd-libs + RemoveFiles= # Arch Linux doesn't split their gcc-libs package so we manually remove # unneeded stuff here to make sure it doesn't end up in the image. diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/centos-fedora.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/centos-fedora.conf index 53cc68d794768..6f08609d1b20a 100644 --- a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/centos-fedora.conf +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/centos-fedora.conf @@ -7,7 +7,11 @@ Distribution=|fedora [Content] PrepareScripts=%D/mkosi/mkosi.conf.d/centos-fedora/systemd.prepare Packages= + coreutils hostname iproute iproute-tc nmap-ncat + +VolatilePackages= + systemd-libs diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/debian-ubuntu.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/debian-ubuntu.conf index 8b148d8422151..acbcea7cd272a 100644 --- a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/debian-ubuntu.conf +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/debian-ubuntu.conf @@ -12,3 +12,7 @@ Packages= iproute2 mount ncat + +VolatilePackages= + libsystemd0 + libudev1 diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/debian.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/debian.conf new file mode 100644 index 0000000000000..eed9f5d6d78a4 --- /dev/null +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/debian.conf @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=debian + +[Content] +Packages= + coreutils diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/opensuse.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/opensuse.conf index 8b38a769a1eb3..87fa34715348d 100644 --- a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/opensuse.conf +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/opensuse.conf @@ -6,6 +6,7 @@ Distribution=opensuse [Content] PrepareScripts=%D/mkosi/mkosi.conf.d/opensuse/systemd.prepare Packages= + coreutils diffutils grep hostname @@ -15,3 +16,7 @@ Packages= patterns-base-minimal_base sed xz + +VolatilePackages= + libsystemd0 + libudev1 diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf new file mode 100644 index 0000000000000..b9fd7bcf34203 --- /dev/null +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=ubuntu diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf new file mode 100644 index 0000000000000..86d4d1132b9ba --- /dev/null +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# avoid pulling in the uutils package which is the default and is broken in several ways + +[TriggerMatch] +Distribution=ubuntu +Release=!jammy +Release=!noble + +[Content] +Packages= + coreutils-from-gnu + coreutils-from-uutils- + rust-coreutils- diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf new file mode 100644 index 0000000000000..b680604f001d8 --- /dev/null +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[TriggerMatch] +Distribution=ubuntu +Release=jammy +Release=noble + +[Content] +Packages= + coreutils diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf b/mkosi/mkosi.initrd.conf/mkosi.conf index 1c73f3a328440..de37e7c3c9769 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf @@ -12,8 +12,8 @@ Environment=SYSTEMD_REQUIRED_DEPS_ONLY=1 ExtraTrees=%D/mkosi/mkosi.extra.common Packages= - coreutils findutils grep sed + swtpm tar diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/arch.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/arch.conf index 909426a09cca6..72043184025e1 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf.d/arch.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/arch.conf @@ -7,6 +7,7 @@ Distribution=arch PrepareScripts=%D/mkosi/mkosi.conf.d/arch/systemd.prepare Packages= btrfs-progs + coreutils tpm2-tools VolatilePackages= diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf index 1a971625bfe7f..e753749dc443f 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf @@ -7,6 +7,9 @@ Distribution=|fedora [Content] PrepareScripts=%D/mkosi/mkosi.conf.d/centos-fedora/systemd.prepare Packages= + coreutils + policycoreutils + swtpm-tools tpm2-tools VolatilePackages= diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/debian-ubuntu.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/debian-ubuntu.conf index 7f2566e9938d2..1e5e8942373bd 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf.d/debian-ubuntu.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/debian-ubuntu.conf @@ -9,6 +9,7 @@ PrepareScripts=%D/mkosi/mkosi.conf.d/debian-ubuntu/systemd.prepare Packages= btrfs-progs tpm2-tools + swtpm-tools VolatilePackages= libsystemd-shared diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/debian.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/debian.conf new file mode 100644 index 0000000000000..eed9f5d6d78a4 --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/debian.conf @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=debian + +[Content] +Packages= + coreutils diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf index add9983ea9631..c30d970c85a2b 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf @@ -7,7 +7,9 @@ Distribution=opensuse PrepareScripts=%D/mkosi/mkosi.conf.d/opensuse/systemd.prepare Packages= btrfs-progs + coreutils kmod + policycoreutils tpm2.0-tools VolatilePackages= @@ -17,5 +19,5 @@ VolatilePackages= # Pull in systemd-container so that the import-generator is available systemd-container systemd-experimental - systemd-network + systemd-networkd udev diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf new file mode 100644 index 0000000000000..b9fd7bcf34203 --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=ubuntu diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf new file mode 100644 index 0000000000000..86d4d1132b9ba --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# avoid pulling in the uutils package which is the default and is broken in several ways + +[TriggerMatch] +Distribution=ubuntu +Release=!jammy +Release=!noble + +[Content] +Packages= + coreutils-from-gnu + coreutils-from-uutils- + rust-coreutils- diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf new file mode 100644 index 0000000000000..b680604f001d8 --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[TriggerMatch] +Distribution=ubuntu +Release=jammy +Release=noble + +[Content] +Packages= + coreutils diff --git a/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/initrd-selinux-relabel.service b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/initrd-selinux-relabel.service new file mode 100644 index 0000000000000..077b36900a2b5 --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/initrd-selinux-relabel.service @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Unit] +Description=Relabel /sysroot for SELinux + +DefaultDependencies=no +ConditionPathExists=/sysroot/etc/selinux/config +After=initrd-root-fs.target +After=initrd.target initrd-parse-etc.service remote-fs.target +Before=initrd-cleanup.service + +[Service] +Type=oneshot +ExecStart=sh -c '. /sysroot/etc/selinux/config && [ -n "$${SELINUXTYPE}" ] && setfiles -mFr /sysroot -T0 -c /sysroot/etc/selinux/$${SELINUXTYPE}/policy/policy.* /sysroot/etc/selinux/$${SELINUXTYPE}/contexts/files/file_contexts /sysroot' diff --git a/mkosi/mkosi.pkgenv/mkosi.conf.d/centos-fedora.conf b/mkosi/mkosi.pkgenv/mkosi.conf.d/centos-fedora.conf index 5bd63a6ce1f75..3ff941ad4f69f 100644 --- a/mkosi/mkosi.pkgenv/mkosi.conf.d/centos-fedora.conf +++ b/mkosi/mkosi.pkgenv/mkosi.conf.d/centos-fedora.conf @@ -9,5 +9,5 @@ Profiles=!hyperscale Environment= GIT_URL=https://src.fedoraproject.org/rpms/systemd.git GIT_BRANCH=rawhide - GIT_COMMIT=23a1c1fed99e152d9c498204175a7643371a822c + GIT_COMMIT=207e2d004468bf79a8bd78182d9b10956edf45c7 PKG_SUBDIR=fedora diff --git a/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf b/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf index 7675471d5738a..f46a0a0372322 100644 --- a/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf +++ b/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf @@ -9,5 +9,5 @@ Environment= GIT_URL=https://salsa.debian.org/systemd-team/systemd.git GIT_SUBDIR=debian GIT_BRANCH=debian/master - GIT_COMMIT=89a825b80ee85e58b530cd95438988a6fb3531a3 + GIT_COMMIT=94af257c72ac3e9bf20e324ff31c3bd5d8197f0e PKG_SUBDIR=debian diff --git a/mkosi/mkosi.postinst.chroot b/mkosi/mkosi.postinst.chroot index eb6d9170252a3..de67ddfdc85b4 100755 --- a/mkosi/mkosi.postinst.chroot +++ b/mkosi/mkosi.postinst.chroot @@ -27,8 +27,9 @@ mountpoint -q /etc/resolv.conf && umount /etc/resolv.conf rm -f /etc/resolv.conf for f in "$BUILDROOT"/usr/share/*.verity.sig; do - jq --join-output '.rootHash' "$f" >"${f%.verity.sig}.roothash" - jq --join-output '.signature' "$f" | base64 --decode >"${f%.verity.sig}.roothash.p7s" + # jq started refusing input with NUL bytes padding + strings "$f" | jq --join-output '.rootHash' >"${f%.verity.sig}.roothash" + strings "$f" | jq --join-output '.signature' | base64 --decode >"${f%.verity.sig}.roothash.p7s" done # We want /var/log/journal to be created on first boot so it can be created with the right chattr settings by diff --git a/mkosi/mkosi.sanitizers/mkosi.postinst b/mkosi/mkosi.sanitizers/mkosi.postinst index d4d00907ed07f..88ba2c2bc098e 100755 --- a/mkosi/mkosi.sanitizers/mkosi.postinst +++ b/mkosi/mkosi.sanitizers/mkosi.postinst @@ -42,7 +42,10 @@ fi wrap=( /usr/lib/polkit-1/polkitd + /usr/lib/systemd/tests/testdata/TEST-74-AUX-UTILS.units/proxy-echo.py /usr/libexec/polkit-1/polkitd + /usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-imds.py + /usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py agetty btrfs capsh @@ -81,6 +84,7 @@ wrap=( mdadm mkfs.btrfs mksquashfs + mount multipath multipathd nvme @@ -96,6 +100,7 @@ wrap=( su tar tgtd + umount unix_chkpwd useradd userdel diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf index a165ccb04a0cb..613d9d87d917f 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf @@ -7,7 +7,8 @@ Distribution=|ubuntu [Content] PrepareScripts=%D/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.prepare Packages= - clang-tools + clang-tidy + coccinelle lcov mypy shellcheck diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/fedora.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/fedora.conf index 7a9301c566cd1..e687fd788e266 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/fedora.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/fedora.conf @@ -13,4 +13,5 @@ Packages= musl-clang musl-gcc ruff + coccinelle shellcheck diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf index b698094618733..6f24649c54c6c 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf @@ -7,8 +7,10 @@ Distribution=opensuse PrepareScripts=%D/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.prepare Packages= clang-tools + coccinelle gh lcov + libtss2-tcti-device0 mypy python3-ruff rpm-build diff --git a/mkosi/mkosi.uki-profiles/profile1.conf b/mkosi/mkosi.uki-profiles/profile1.conf index 3dc39d2534b4d..e0508a0664356 100644 --- a/mkosi/mkosi.uki-profiles/profile1.conf +++ b/mkosi/mkosi.uki-profiles/profile1.conf @@ -3,5 +3,5 @@ [UKIProfile] Profile= ID=profile1 - TITLE=Profile Two + TITLE=Profile One Cmdline=testprofile1=1 diff --git a/po/LINGUAS b/po/LINGUAS index e520dec8b355d..283750d34ef3b 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -1,6 +1,8 @@ +ar be be@latin bg +bo ca cs da @@ -22,7 +24,11 @@ it ja ka kab +kk +km +kn ko +kw lt nl pa @@ -35,13 +41,10 @@ si sk sl sr +sr@latin sv tr +ug uk zh_CN zh_TW -kn -ar -km -kw -kk diff --git a/po/ar.po b/po/ar.po index f6c113f0e32b7..1c9882eaa322d 100644 --- a/po/ar.po +++ b/po/ar.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2026-02-26 13:58+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-04-11 19:58+0000\n" "Last-Translator: joo es \n" "Language-Team: Arabic \n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " "&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -938,8 +938,10 @@ msgid "DHCP server sends force renew message" msgstr "خادم DHCP يرسل رسالة تجديد إجبارية" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "الاستيثاق مطلوب للإرسال رسالة تجديد إجبارية." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "الاستيثاق مطلوب للإرسال رسالة تجديد إجبارية من خادم DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" diff --git a/po/be.po b/po/be.po index 1ef0948a26194..be6051d96a16d 100644 --- a/po/be.po +++ b/po/be.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2023-05-13 19:20+0000\n" "Last-Translator: Maksim Kliazovich \n" "Language-Team: Belarusian \n" "Language-Team: \n" @@ -1079,7 +1079,9 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:144 #, fuzzy -msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" "Nieabchodna aŭtentyfikacyja dlia ŭstaliavannia ŭsieahuĺnaha paviedamliennia" diff --git a/po/bg.po b/po/bg.po index 0cbfd23c35c4f..fb84c6c9d7cd3 100644 --- a/po/bg.po +++ b/po/bg.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: systemd main\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2025-02-11 01:17+0000\n" "Last-Translator: Alexander Shopov \n" "Language-Team: Bulgarian , 2026. +msgid "" +msgstr "" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-16 14:21+0000\n" +"Last-Translator: Dongshengyuan \n" +"Language-Team: Tibetan\n" +"Language: bo\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: manual\n" + +#: src/core/org.freedesktop.systemd1.policy.in:22 +msgid "Send passphrase back to system" +msgstr "གསང་ཚིག་རྒྱུད་ལམ་ལ་ཕྱིར་སྐྱེལ།" + +#: src/core/org.freedesktop.systemd1.policy.in:23 +msgid "" +"Authentication is required to send the entered passphrase back to the system." +msgstr "ནང་འཇུག་བྱས་པའི་གསང་ཚིག་རྒྱུད་ལམ་ལ་ཕྱིར་སྐྱེལ་བར་ར་སྤྲོད་དགོས།" + +#: src/core/org.freedesktop.systemd1.policy.in:33 +msgid "Manage system services or other units" +msgstr "རྒྱུད་ལམ་ཞབས་ཞུ་དང་སྡེ་ཚན་གཞན་དག་དོ་དམ་བྱེད།" + +#: src/core/org.freedesktop.systemd1.policy.in:34 +msgid "Authentication is required to manage system services or other units." +msgstr "རྒྱུད་ལམ་ཞབས་ཞུ་དང་སྡེ་ཚན་གཞན་དག་དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/core/org.freedesktop.systemd1.policy.in:43 +msgid "Manage system service or unit files" +msgstr "རྒྱུད་ལམ་ཞབས་ཞུའམ་སྡེ་ཚན་ཡིག་ཆ་དོ་དམ་བྱེད།" + +#: src/core/org.freedesktop.systemd1.policy.in:44 +msgid "Authentication is required to manage system service or unit files." +msgstr "རྒྱུད་ལམ་ཞབས་ཞུའམ་སྡེ་ཚན་ཡིག་ཆ་དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/core/org.freedesktop.systemd1.policy.in:54 +msgid "Set or unset system and service manager environment variables" +msgstr "རྒྱུད་ལམ་དང་ཞབས་ཞུ་དོ་དམ་པའི་ཁོར་ཡུག་འགྱུར་ཚད་སྒྲིག་གམ་སུབ།" + +#: src/core/org.freedesktop.systemd1.policy.in:55 +msgid "" +"Authentication is required to set or unset system and service manager " +"environment variables." +msgstr "རྒྱུད་ལམ་དང་ཞབས་ཞུ་དོ་དམ་པའི་ཁོར་ཡུག་འགྱུར་ཚད་སྒྲིག་པའམ་སུབ་པར་ར་སྤྲོད་དགོས།" + +#: src/core/org.freedesktop.systemd1.policy.in:64 +msgid "Reload the systemd state" +msgstr "systemd གནས་ཚུལ་བསྐྱར་འཇུག་བྱེད།" + +#: src/core/org.freedesktop.systemd1.policy.in:65 +msgid "Authentication is required to reload the systemd state." +msgstr "systemd གནས་ཚུལ་བསྐྱར་འཇུག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/core/org.freedesktop.systemd1.policy.in:74 +msgid "Dump the systemd state without rate limits" +msgstr "མྱུར་ཚད་ཚད་བཀག་མེད་པར systemd གནས་ཚུལ་ཕྱིར་འདོན།" + +#: src/core/org.freedesktop.systemd1.policy.in:75 +msgid "" +"Authentication is required to dump the systemd state without rate limits." +msgstr "མྱུར་ཚད་ཚད་བཀག་མེད་པར systemd གནས་ཚུལ་ཕྱིར་འདོན་པར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:13 +msgid "Create a home area" +msgstr "ཁྱིམ་ཁོངས་གསར་བཟོ།" + +#: src/home/org.freedesktop.home1.policy:14 +msgid "Authentication is required to create a user's home area." +msgstr "སྤྱོད་མཁན་གྱི་ཁྱིམ་ཁོངས་གསར་བཟོ་བར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:23 +msgid "Remove a home area" +msgstr "ཁྱིམ་ཁོངས་སྤོ་བ།" + +#: src/home/org.freedesktop.home1.policy:24 +msgid "Authentication is required to remove a user's home area." +msgstr "སྤྱོད་མཁན་གྱི་ཁྱིམ་ཁོངས་སྤོ་བར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:33 +msgid "Check credentials of a home area" +msgstr "ཁྱིམ་ཁོངས་ཀྱི་དཔང་ཡིག་ཞིབ་བཤེར།" + +#: src/home/org.freedesktop.home1.policy:34 +msgid "" +"Authentication is required to check credentials against a user's home area." +msgstr "སྤྱོད་མཁན་གྱི་ཁྱིམ་ཁོངས་ལ་བསྟུན་ནས་དཔང་ཡིག་ཞིབ་བཤེར་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:43 +msgid "Update a home area" +msgstr "ཁྱིམ་ཁོངས་གསར་བསྒྱུར།" + +#: src/home/org.freedesktop.home1.policy:44 +msgid "Authentication is required to update a user's home area." +msgstr "སྤྱོད་མཁན་གྱི་ཁྱིམ་ཁོངས་གསར་བསྒྱུར་བར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:53 +msgid "Update your home area" +msgstr "ཁྱེད་ཀྱི་ཁྱིམ་ཁོངས་གསར་བསྒྱུར།" + +#: src/home/org.freedesktop.home1.policy:54 +msgid "Authentication is required to update your home area." +msgstr "ཁྱེད་ཀྱི་ཁྱིམ་ཁོངས་གསར་བསྒྱུར་བར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:63 +msgid "Resize a home area" +msgstr "ཁྱིམ་ཁོངས་ཆེ་ཆུང་བསྒྱུར།" + +#: src/home/org.freedesktop.home1.policy:64 +msgid "Authentication is required to resize a user's home area." +msgstr "སྤྱོད་མཁན་གྱི་ཁྱིམ་ཁོངས་ཆེ་ཆུང་བསྒྱུར་བར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:73 +msgid "Change password of a home area" +msgstr "ཁྱིམ་ཁོངས་ཀྱི་གསང་ཚིག་བསྒྱུར།" + +#: src/home/org.freedesktop.home1.policy:74 +msgid "" +"Authentication is required to change the password of a user's home area." +msgstr "སྤྱོད་མཁན་གྱི་ཁྱིམ་ཁོངས་གསང་ཚིག་བསྒྱུར་བར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:83 +msgid "Activate a home area" +msgstr "ཁྱིམ་ཁོངས་སྐུལ་སློང་།" + +#: src/home/org.freedesktop.home1.policy:84 +msgid "Authentication is required to activate a user's home area." +msgstr "སྤྱོད་མཁན་གྱི་ཁྱིམ་ཁོངས་སྐུལ་སློང་བར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:93 +msgid "Manage Home Directory Signing Keys" +msgstr "ཁྱིམ་དཀར་ཆག་མིང་རྟགས་ལྡེ་མིག་དོ་དམ།" + +#: src/home/org.freedesktop.home1.policy:94 +msgid "Authentication is required to manage signing keys for home directories." +msgstr "ཁྱིམ་དཀར་ཆག་མིང་རྟགས་ལྡེ་མིག་དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/home/pam_systemd_home.c:330 +#, c-format +msgid "" +"Home of user %s is currently absent, please plug in the necessary storage " +"device or backing file system." +msgstr "སྤྱོད་མཁན %s ཡི་ཁྱིམ་དཀར་ཆག་ད་ལྟ་མེད། དགོས་ངེས་ཀྱི་གསོག་ཉར་སྒྲིག་ཆས་སམ་རྒྱབ་རྟེན་ཡིག་ཚགས་མ་ལག་སྦྲེལ་རོགས།" + +#: src/home/pam_systemd_home.c:335 +#, c-format +msgid "Too frequent login attempts for user %s, try again later." +msgstr "སྤྱོད་མཁན %s ཡི་ནང་འཛུལ་ཚོད་ལྟ་མང་དྲགས་པས་རྗེས་སུ་ཡང་བསྐྱར་ཚོད་ལྟ་བྱེད།" + +#: src/home/pam_systemd_home.c:347 +msgid "Password: " +msgstr "གསང་ཚིག: " + +#: src/home/pam_systemd_home.c:349 +#, c-format +msgid "Password incorrect or not sufficient for authentication of user %s." +msgstr "སྤྱོད་མཁན %s ཡི་གསང་ཚིག་ནོར་འཁྲུལ་ཡིན་པའམ་ར་སྤྲོད་ལ་མི་འདང་།" + +#: src/home/pam_systemd_home.c:350 +msgid "Sorry, try again: " +msgstr "དགོངས་དག ཡང་བསྐྱར་ཚོད་ལྟ: " + +#: src/home/pam_systemd_home.c:372 +msgid "Recovery key: " +msgstr "སླར་གསོ་ལྡེ་མིག: " + +#: src/home/pam_systemd_home.c:374 +#, c-format +msgid "" +"Password/recovery key incorrect or not sufficient for authentication of user " +"%s." +msgstr "སྤྱོད་མཁན %s ཡི་གསང་ཚིག/སླར་གསོ་ལྡེ་མིག་ནོར་འཁྲུལ་ཡིན་པའམ་ར་སྤྲོད་ལ་མི་འདང་།" + +#: src/home/pam_systemd_home.c:375 +msgid "Sorry, reenter recovery key: " +msgstr "དགོངས་དག སླར་གསོ་ལྡེ་མིག་ཡང་བསྐྱར་ནང་འཇུག: " + +#: src/home/pam_systemd_home.c:395 +#, c-format +msgid "Security token of user %s not inserted." +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས་མ་བཙུགས་པ།" + +#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +msgid "Try again with password: " +msgstr "གསང་ཚིག་སྤྱད་ནས་ཡང་བསྐྱར་ཚོད་ལྟ: " + +#: src/home/pam_systemd_home.c:398 +#, c-format +msgid "" +"Password incorrect or not sufficient, and configured security token of user " +"%s not inserted." +msgstr "གསང་ཚིག་ནོར་འཁྲུལ་ཡིན་པའམ་མི་འདང་། སྤྱོད་མཁན %s ཡི་སྒྲིག་བཀོད་བདེ་འཇགས་རྟགས་ཀྱང་མ་བཙུགས།" + +#: src/home/pam_systemd_home.c:418 +msgid "Security token PIN: " +msgstr "བདེ་འཇགས་རྟགས PIN: " + +#: src/home/pam_systemd_home.c:435 +#, c-format +msgid "Please authenticate physically on security token of user %s." +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས་སྟེང་ལུས་ངོས་ར་སྤྲོད་གནང་རོགས།" + +#: src/home/pam_systemd_home.c:446 +#, c-format +msgid "Please confirm presence on security token of user %s." +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས་སྟེང་ཡོད་པ་ངེས་གཏན་གནང་རོགས།" + +#: src/home/pam_systemd_home.c:457 +#, c-format +msgid "Please verify user on security token of user %s." +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས་སྟེང་སྤྱོད་མཁན་ཞིབ་བཤེར་གནང་རོགས།" + +#: src/home/pam_systemd_home.c:466 +msgid "" +"Security token PIN is locked, please unlock it first. (Hint: Removal and re-" +"insertion might suffice.)" +msgstr "བདེ་འཇགས་རྟགས PIN སྒོ་ལྕགས་བཀག་ཟིན། སྔོན་ལ་སྒྲོལ་རོགས། (ཁ་སྣོན: ཕྱིར་བཏོན་ནས་ཡང་བསྐྱར་བཙུགས་ན་འགྲིག་སྲིད།)" + +#: src/home/pam_systemd_home.c:474 +#, c-format +msgid "Security token PIN incorrect for user %s." +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས PIN ནོར་འཁྲུལ།" + +#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 +#: src/home/pam_systemd_home.c:513 +msgid "Sorry, retry security token PIN: " +msgstr "དགོངས་དག བདེ་འཇགས་རྟགས PIN ཡང་བསྐྱར་ཚོད་ལྟ: " + +#: src/home/pam_systemd_home.c:493 +#, c-format +msgid "Security token PIN of user %s incorrect (only a few tries left!)" +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས PIN ནོར་འཁྲུལ། (ཚོད་ལྟ་ཆེས་ཉུང་ཙམ་ལས་མེད!)" + +#: src/home/pam_systemd_home.c:512 +#, c-format +msgid "Security token PIN of user %s incorrect (only one try left!)" +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས PIN ནོར་འཁྲུལ། (ཚོད་ལྟ་གཅིག་ལས་མེད!)" + +#: src/home/pam_systemd_home.c:679 +#, c-format +msgid "Home of user %s is currently not active, please log in locally first." +msgstr "སྤྱོད་མཁན %s ཡི་ཁྱིམ་དཀར་ཆག་ད་ལྟ་སྐུལ་སློང་མེད། སྔོན་ལ་ས་གནས་ནས་ནང་འཛུལ་གནང་རོགས།" + +#: src/home/pam_systemd_home.c:681 +#, c-format +msgid "Home of user %s is currently locked, please unlock locally first." +msgstr "སྤྱོད་མཁན %s ཡི་ཁྱིམ་དཀར་ཆག་ད་ལྟ་སྒོ་ལྕགས་བཀག་ཡོད། སྔོན་ལ་ས་གནས་ནས་སྒྲོལ་རོགས།" + +#: src/home/pam_systemd_home.c:715 +#, c-format +msgid "Too many unsuccessful login attempts for user %s, refusing." +msgstr "སྤྱོད་མཁན %s ཡི་ནང་འཛུལ་མ་ལེགས་པའི་ཚོད་ལྟ་མང་དྲགས་པས་ངོས་ལེན་མི་བྱེད།" + +#: src/home/pam_systemd_home.c:1012 +msgid "User record is blocked, prohibiting access." +msgstr "སྤྱོད་མཁན་ཐོ་བཀག་ཟིན་པས་ལྟ་སྤྱོད་བཀག་ཡོད།" + +#: src/home/pam_systemd_home.c:1016 +msgid "User record is not valid yet, prohibiting access." +msgstr "སྤྱོད་མཁན་ཐོ་ད་དུང་ནུས་ལྡན་མིན་པས་ལྟ་སྤྱོད་བཀག་ཡོད།" + +#: src/home/pam_systemd_home.c:1020 +msgid "User record is not valid anymore, prohibiting access." +msgstr "སྤྱོད་མཁན་ཐོ་ད་ནས་ནུས་མེད་པས་ལྟ་སྤྱོད་བཀག་ཡོད།" + +#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +msgid "User record not valid, prohibiting access." +msgstr "སྤྱོད་མཁན་ཐོ་ནུས་མེད་པས་ལྟ་སྤྱོད་བཀག་ཡོད།" + +#: src/home/pam_systemd_home.c:1035 +#, c-format +msgid "Too many logins, try again in %s." +msgstr "ནང་འཛུལ་མང་དྲགས་པས %s རྗེས་སུ་ཡང་བསྐྱར་ཚོད་ལྟ་བྱེད།" + +#: src/home/pam_systemd_home.c:1046 +msgid "Password change required." +msgstr "གསང་ཚིག་བསྒྱུར་དགོས།" + +#: src/home/pam_systemd_home.c:1050 +msgid "Password expired, change required." +msgstr "གསང་ཚིག་དུས་ཡོལ་ཟིན་པས་བསྒྱུར་དགོས།" + +#: src/home/pam_systemd_home.c:1056 +msgid "Password is expired, but can't change, refusing login." +msgstr "གསང་ཚིག་དུས་ཡོལ་ཟིན་ཡང་བསྒྱུར་མི་ཐུབ་པས་ནང་འཛུལ་ཁས་མི་ལེན།" + +#: src/home/pam_systemd_home.c:1060 +msgid "Password will expire soon, please change." +msgstr "གསང་ཚིག་མི་རིང་བར་དུས་ཡོལ་འགྲོ་གི་ཡོད་པས་བསྒྱུར་རོགས།" + +#: src/hostname/org.freedesktop.hostname1.policy:20 +msgid "Set hostname" +msgstr "གཙོ་འཁོར་མིང་སྒྲིག" + +#: src/hostname/org.freedesktop.hostname1.policy:21 +msgid "Authentication is required to set the local hostname." +msgstr "ས་གནས་གཙོ་འཁོར་མིང་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/hostname/org.freedesktop.hostname1.policy:30 +msgid "Set static hostname" +msgstr "བརྟན་པོའི་གཙོ་འཁོར་མིང་སྒྲིག" + +#: src/hostname/org.freedesktop.hostname1.policy:31 +msgid "" +"Authentication is required to set the statically configured local hostname, " +"as well as the pretty hostname." +msgstr "བརྟན་པོར་སྒྲིག་པའི་ས་གནས་གཙོ་འཁོར་མིང་དང pretty hostname སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/hostname/org.freedesktop.hostname1.policy:41 +msgid "Set machine information" +msgstr "འཕྲུལ་ཆས་ཆ་འཕྲིན་སྒྲིག" + +#: src/hostname/org.freedesktop.hostname1.policy:42 +msgid "Authentication is required to set local machine information." +msgstr "ས་གནས་འཕྲུལ་ཆས་ཆ་འཕྲིན་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/hostname/org.freedesktop.hostname1.policy:51 +msgid "Get product UUID" +msgstr "ཐོན་རྫས UUID ལེན" + +#: src/hostname/org.freedesktop.hostname1.policy:52 +msgid "Authentication is required to get product UUID." +msgstr "ཐོན་རྫས UUID ལེན་པར་ར་སྤྲོད་དགོས།" + +#: src/hostname/org.freedesktop.hostname1.policy:61 +msgid "Get hardware serial number" +msgstr "སྲ་ཆས་རིམ་ཨང་ལེན" + +#: src/hostname/org.freedesktop.hostname1.policy:62 +msgid "Authentication is required to get hardware serial number." +msgstr "སྲ་ཆས་རིམ་ཨང་ལེན་པར་ར་སྤྲོད་དགོས།" + +#: src/hostname/org.freedesktop.hostname1.policy:71 +msgid "Get system description" +msgstr "རྒྱུད་ལམ་འགྲེལ་བཤད་ལེན" + +#: src/hostname/org.freedesktop.hostname1.policy:72 +msgid "Authentication is required to get system description." +msgstr "རྒྱུད་ལམ་འགྲེལ་བཤད་ལེན་པར་ར་སྤྲོད་དགོས།" + +#: src/import/org.freedesktop.import1.policy:22 +msgid "Import a disk image" +msgstr "ཌིསྐ་མཚོན་རིས་ནང་འདྲེན།" + +#: src/import/org.freedesktop.import1.policy:23 +msgid "Authentication is required to import an image." +msgstr "མཚོན་རིས་ནང་འདྲེན་པར་ར་སྤྲོད་དགོས།" + +#: src/import/org.freedesktop.import1.policy:32 +msgid "Export a disk image" +msgstr "ཌིསྐ་མཚོན་རིས་ཕྱིར་འདོན།" + +#: src/import/org.freedesktop.import1.policy:33 +msgid "Authentication is required to export disk image." +msgstr "ཌིསྐ་མཚོན་རིས་ཕྱིར་འདོན་པར་ར་སྤྲོད་དགོས།" + +#: src/import/org.freedesktop.import1.policy:42 +msgid "Download a disk image" +msgstr "ཌིསྐ་མཚོན་རིས་ཕབ་ལེན།" + +#: src/import/org.freedesktop.import1.policy:43 +msgid "Authentication is required to download a disk image." +msgstr "ཌིསྐ་མཚོན་རིས་ཕབ་ལེན་པར་ར་སྤྲོད་དགོས།" + +#: src/import/org.freedesktop.import1.policy:52 +msgid "Cancel transfer of a disk image" +msgstr "ཌིསྐ་མཚོན་རིས་བརྒྱུད་སྐྱེལ་མེད་པར་བཟོ" + +#: src/import/org.freedesktop.import1.policy:53 +msgid "" +"Authentication is required to cancel the ongoing transfer of a disk image." +msgstr "ད་ལྟ་བྱེད་བཞིན་པའི་ཌིསྐ་མཚོན་རིས་བརྒྱུད་སྐྱེལ་མེད་པར་བཟོ་བར་ར་སྤྲོད་དགོས།" + +#: src/locale/org.freedesktop.locale1.policy:22 +msgid "Set system locale" +msgstr "རྒྱུད་ལམ locale སྒྲིག" + +#: src/locale/org.freedesktop.locale1.policy:23 +msgid "Authentication is required to set the system locale." +msgstr "རྒྱུད་ལམ locale སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/locale/org.freedesktop.locale1.policy:33 +msgid "Set system keyboard settings" +msgstr "རྒྱུད་ལམ་མཐེབ་གཞོང་སྒྲིག་འགོད་སྒྲིག" + +#: src/locale/org.freedesktop.locale1.policy:34 +msgid "Authentication is required to set the system keyboard settings." +msgstr "རྒྱུད་ལམ་མཐེབ་གཞོང་སྒྲིག་འགོད་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:22 +msgid "Allow applications to inhibit system shutdown" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་སྒོ་རྒྱག་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:23 +msgid "" +"Authentication is required for an application to inhibit system shutdown." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་སྒོ་རྒྱག་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:33 +msgid "Allow applications to delay system shutdown" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་སྒོ་རྒྱག་ཕྱིར་འགྱངས་བྱེད་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:34 +msgid "Authentication is required for an application to delay system shutdown." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་སྒོ་རྒྱག་ཕྱིར་འགྱངས་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:44 +msgid "Allow applications to inhibit system sleep" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་གཉིད་ཁུག་པ་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:45 +msgid "Authentication is required for an application to inhibit system sleep." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་གཉིད་ཁུག་པ་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:55 +msgid "Allow applications to delay system sleep" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་གཉིད་ཁུག་པ་ཕྱིར་འགྱངས་བྱེད་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:56 +msgid "Authentication is required for an application to delay system sleep." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་གཉིད་ཁུག་པ་ཕྱིར་འགྱངས་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:65 +msgid "Allow applications to inhibit automatic system suspend" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་རང་འགུལ་འགེལ་འཇོག་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:66 +msgid "" +"Authentication is required for an application to inhibit automatic system " +"suspend." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་རང་འགུལ་འགེལ་འཇོག་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:75 +msgid "Allow applications to inhibit system handling of the power key" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་གློག་སྒོ་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:76 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the power key." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་གློག་སྒོ་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:86 +msgid "Allow applications to inhibit system handling of the suspend key" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་འགེལ་འཇོག་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:87 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the suspend key." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་འགེལ་འཇོག་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:97 +msgid "Allow applications to inhibit system handling of the hibernate key" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་ཉལ་ཉིན་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:98 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the hibernate key." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་ཉལ་ཉིན་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:107 +msgid "Allow applications to inhibit system handling of the lid switch" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་ལཔ་ཊོཔ་ཁ་ལེབ་སྒྱུར་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:108 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the lid switch." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་ལཔ་ཊོཔ་ཁ་ལེབ་སྒྱུར་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:117 +msgid "Allow applications to inhibit system handling of the reboot key" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་བསྐྱར་འགོ་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:118 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the reboot key." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་བསྐྱར་འགོ་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:128 +msgid "Allow non-logged-in user to run programs" +msgstr "ནང་འཛུལ་མ་བྱས་པའི་སྤྱོད་མཁན་ལ་ལས་རིམ་འགྲོ་བར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:129 +msgid "Explicit request is required to run programs as a non-logged-in user." +msgstr "ནང་འཛུལ་མ་བྱས་པའི་སྤྱོད་མཁན་དབང་གིས་ལས་རིམ་འགྲོ་བར་གསལ་བཤད་ཀྱི་ཞུ་བ་དགོས།" + +#: src/login/org.freedesktop.login1.policy:138 +msgid "Allow non-logged-in users to run programs" +msgstr "ནང་འཛུལ་མ་བྱས་པའི་སྤྱོད་མཁན་ཚོར་ལས་རིམ་འགྲོ་བར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:139 +msgid "Authentication is required to run programs as a non-logged-in user." +msgstr "ནང་འཛུལ་མ་བྱས་པའི་སྤྱོད་མཁན་དབང་གིས་ལས་རིམ་འགྲོ་བར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:148 +msgid "Allow attaching devices to seats" +msgstr "སྒྲིག་ཆས seat ལ་སྦྲེལ་བར་ཆོག" + +# Pay attention to the concept of "seat". +# +# To fully understand the meaning, please refer to session management in old ConsoleKit and new systemd-logind. +#: src/login/org.freedesktop.login1.policy:149 +msgid "Authentication is required to attach a device to a seat." +msgstr "སྒྲིག་ཆས seat ལ་སྦྲེལ་བར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:159 +msgid "Flush device to seat attachments" +msgstr "སྒྲིག་ཆས་དང seat སྦྲེལ་བ་བསྐྱར་སྒྲིག" + +#: src/login/org.freedesktop.login1.policy:160 +msgid "Authentication is required to reset how devices are attached to seats." +msgstr "སྒྲིག་ཆས seat ལ་སྦྲེལ་ཚུལ་བསྐྱར་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:169 +msgid "Power off the system" +msgstr "རྒྱུད་ལམ་སྒོ་རྒྱག" + +#: src/login/org.freedesktop.login1.policy:170 +msgid "Authentication is required to power off the system." +msgstr "རྒྱུད་ལམ་སྒོ་རྒྱག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:180 +msgid "Power off the system while other users are logged in" +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་སྒོ་རྒྱག" + +#: src/login/org.freedesktop.login1.policy:181 +msgid "" +"Authentication is required to power off the system while other users are " +"logged in." +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་སྒོ་རྒྱག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:191 +msgid "Power off the system while an application is inhibiting this" +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་སྒོ་རྒྱག" + +#: src/login/org.freedesktop.login1.policy:192 +msgid "" +"Authentication is required to power off the system while an application is " +"inhibiting this." +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་སྒོ་རྒྱག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:202 +msgid "Reboot the system" +msgstr "རྒྱུད་ལམ་བསྐྱར་འགོ" + +#: src/login/org.freedesktop.login1.policy:203 +msgid "Authentication is required to reboot the system." +msgstr "རྒྱུད་ལམ་བསྐྱར་འགོ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:213 +msgid "Reboot the system while other users are logged in" +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་བསྐྱར་འགོ" + +#: src/login/org.freedesktop.login1.policy:214 +msgid "" +"Authentication is required to reboot the system while other users are logged " +"in." +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་བསྐྱར་འགོ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:224 +msgid "Reboot the system while an application is inhibiting this" +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་བསྐྱར་འགོ" + +#: src/login/org.freedesktop.login1.policy:225 +msgid "" +"Authentication is required to reboot the system while an application is " +"inhibiting this." +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་བསྐྱར་འགོ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:235 +msgid "Halt the system" +msgstr "རྒྱུད་ལམ་མཚམས་འཇོག" + +#: src/login/org.freedesktop.login1.policy:236 +msgid "Authentication is required to halt the system." +msgstr "རྒྱུད་ལམ་མཚམས་འཇོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:246 +msgid "Halt the system while other users are logged in" +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་མཚམས་འཇོག" + +#: src/login/org.freedesktop.login1.policy:247 +msgid "" +"Authentication is required to halt the system while other users are logged " +"in." +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་མཚམས་འཇོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:257 +msgid "Halt the system while an application is inhibiting this" +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་མཚམས་འཇོག" + +#: src/login/org.freedesktop.login1.policy:258 +msgid "" +"Authentication is required to halt the system while an application is " +"inhibiting this." +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་མཚམས་འཇོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:268 +msgid "Suspend the system" +msgstr "རྒྱུད་ལམ་འགེལ་འཇོག" + +#: src/login/org.freedesktop.login1.policy:269 +msgid "Authentication is required to suspend the system." +msgstr "རྒྱུད་ལམ་འགེལ་འཇོག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:278 +msgid "Suspend the system while other users are logged in" +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་འགེལ་འཇོག" + +#: src/login/org.freedesktop.login1.policy:279 +msgid "" +"Authentication is required to suspend the system while other users are " +"logged in." +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་འགེལ་འཇོག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:289 +msgid "Suspend the system while an application is inhibiting this" +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་འགེལ་འཇོག" + +#: src/login/org.freedesktop.login1.policy:290 +msgid "" +"Authentication is required to suspend the system while an application is " +"inhibiting this." +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་འགེལ་འཇོག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:300 +msgid "Hibernate the system" +msgstr "རྒྱུད་ལམ་ཉལ་ཉིན" + +#: src/login/org.freedesktop.login1.policy:301 +msgid "Authentication is required to hibernate the system." +msgstr "རྒྱུད་ལམ་ཉལ་ཉིན་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:310 +msgid "Hibernate the system while other users are logged in" +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་ཉལ་ཉིན" + +#: src/login/org.freedesktop.login1.policy:311 +msgid "" +"Authentication is required to hibernate the system while other users are " +"logged in." +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་ཉལ་ཉིན་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:321 +msgid "Hibernate the system while an application is inhibiting this" +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་ཉལ་ཉིན" + +#: src/login/org.freedesktop.login1.policy:322 +msgid "" +"Authentication is required to hibernate the system while an application is " +"inhibiting this." +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་ཉལ་ཉིན་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:332 +msgid "Manage active sessions, users and seats" +msgstr "སྐུལ་སློང་ཅན་གྱི་གླེང་མོལ་(session) སྤྱོད་མཁན་དང seat དོ་དམ" + +#: src/login/org.freedesktop.login1.policy:333 +msgid "Authentication is required to manage active sessions, users and seats." +msgstr "སྐུལ་སློང་ཅན་གྱི session སྤྱོད་མཁན་དང seat དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:342 +msgid "Lock or unlock active sessions" +msgstr "སྐུལ་སློང་ཅན་གྱི session སྒོ་ལྕགས་བཀག་གམ་སྒྲོལ" + +#: src/login/org.freedesktop.login1.policy:343 +msgid "Authentication is required to lock or unlock active sessions." +msgstr "སྐུལ་སློང་ཅན་གྱི session སྒོ་ལྕགས་བཀག་གམ་སྒྲོལ་བར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:352 +msgid "Set the reboot \"reason\" in the kernel" +msgstr "kernel ནང་བསྐྱར་འགོའི \"reason\" སྒྲིག" + +#: src/login/org.freedesktop.login1.policy:353 +msgid "Authentication is required to set the reboot \"reason\" in the kernel." +msgstr "kernel ནང་བསྐྱར་འགོའི \"reason\" སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:363 +msgid "Indicate to the firmware to boot to setup interface" +msgstr "firmware ལ་སྒྲིག་འགོད་འཆར་ངོས་སུ་འགོ་ཚུགས་དགོས་ཞེས་བརྡ་སྟོན" + +#: src/login/org.freedesktop.login1.policy:364 +msgid "" +"Authentication is required to indicate to the firmware to boot to setup " +"interface." +msgstr "firmware ལ་སྒྲིག་འགོད་འཆར་ངོས་སུ་འགོ་ཚུགས་དགོས་ཞེས་བརྡ་སྟོན་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:374 +msgid "Indicate to the boot loader to boot to the boot loader menu" +msgstr "boot loader ལ་ boot loader དཀར་ཆག་ཏུ་འགོ་ཚུགས་དགོས་ཞེས་བརྡ་སྟོན" + +#: src/login/org.freedesktop.login1.policy:375 +msgid "" +"Authentication is required to indicate to the boot loader to boot to the " +"boot loader menu." +msgstr "boot loader ལ་ boot loader དཀར་ཆག་ཏུ་འགོ་ཚུགས་དགོས་ཞེས་བརྡ་སྟོན་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:385 +msgid "Indicate to the boot loader to boot a specific entry" +msgstr "boot loader ལ་དམིགས་བསལ་གྱི་འཇུག་ཚན་ཞིག་ཏུ་འགོ་ཚུགས་དགོས་ཞེས་བརྡ་སྟོན" + +#: src/login/org.freedesktop.login1.policy:386 +msgid "" +"Authentication is required to indicate to the boot loader to boot into a " +"specific boot loader entry." +msgstr "boot loader ལ་དམིགས་བསལ་གྱི boot loader འཇུག་ཚན་ཏུ་འགོ་ཚུགས་དགོས་ཞེས་བརྡ་སྟོན་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:396 +msgid "Set a wall message" +msgstr "wall འཕྲིན་སྒྲིག" + +#: src/login/org.freedesktop.login1.policy:397 +msgid "Authentication is required to set a wall message." +msgstr "wall འཕྲིན་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:406 +msgid "Change Session" +msgstr "Session བསྒྱུར" + +#: src/login/org.freedesktop.login1.policy:407 +msgid "Authentication is required to change the virtual terminal." +msgstr "virtual terminal བསྒྱུར་བར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:22 +msgid "Log into a local container" +msgstr "ས་གནས container དུ་ནང་འཛུལ" + +#: src/machine/org.freedesktop.machine1.policy:23 +msgid "Authentication is required to log into a local container." +msgstr "ས་གནས container དུ་ནང་འཛུལ་བར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:32 +msgid "Log into the local host" +msgstr "ས་གནས་གཙོ་འཁོར་ལ་ནང་འཛུལ" + +#: src/machine/org.freedesktop.machine1.policy:33 +msgid "Authentication is required to log into the local host." +msgstr "ས་གནས་གཙོ་འཁོར་ལ་ནང་འཛུལ་བར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:42 +msgid "Acquire a shell in a local container" +msgstr "ས་གནས container ནང shell ཞིག་ཐོབ" + +#: src/machine/org.freedesktop.machine1.policy:43 +msgid "Authentication is required to acquire a shell in a local container." +msgstr "ས་གནས container ནང shell ཐོབ་པར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:53 +msgid "Acquire a shell on the local host" +msgstr "ས་གནས་གཙོ་འཁོར་སྟེང shell ཞིག་ཐོབ" + +#: src/machine/org.freedesktop.machine1.policy:54 +msgid "Authentication is required to acquire a shell on the local host." +msgstr "ས་གནས་གཙོ་འཁོར་སྟེང shell ཐོབ་པར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:64 +msgid "Acquire a pseudo TTY in a local container" +msgstr "ས་གནས container ནང pseudo TTY ཞིག་ཐོབ" + +#: src/machine/org.freedesktop.machine1.policy:65 +msgid "" +"Authentication is required to acquire a pseudo TTY in a local container." +msgstr "ས་གནས container ནང pseudo TTY ཐོབ་པར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:74 +msgid "Acquire a pseudo TTY on the local host" +msgstr "ས་གནས་གཙོ་འཁོར་སྟེང pseudo TTY ཞིག་ཐོབ" + +#: src/machine/org.freedesktop.machine1.policy:75 +msgid "Authentication is required to acquire a pseudo TTY on the local host." +msgstr "ས་གནས་གཙོ་འཁོར་སྟེང pseudo TTY ཐོབ་པར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:84 +msgid "Manage local virtual machines and containers" +msgstr "ས་གནས virtual machine དང container དོ་དམ" + +#: src/machine/org.freedesktop.machine1.policy:85 +msgid "" +"Authentication is required to manage local virtual machines and containers." +msgstr "ས་གནས virtual machine དང container དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:95 +msgid "Create a local virtual machine or container" +msgstr "ས་གནས virtual machine ཡང་ན container གསར་བཟོ" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "ས་གནས virtual machine ཡང་ན container གསར་བཟོ་བར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:106 +msgid "Register a local virtual machine or container" +msgstr "ས་གནས virtual machine ཡང་ན container ཐོ་འགོད" + +#: src/machine/org.freedesktop.machine1.policy:107 +msgid "" +"Authentication is required to register a local virtual machine or container." +msgstr "ས་གནས virtual machine ཡང་ན container ཐོ་འགོད་པར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:116 +msgid "Manage local virtual machine and container images" +msgstr "ས་གནས virtual machine དང container མཚོན་རིས་དོ་དམ" + +#: src/machine/org.freedesktop.machine1.policy:117 +msgid "" +"Authentication is required to manage local virtual machine and container " +"images." +msgstr "ས་གནས virtual machine དང container མཚོན་རིས་དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:22 +msgid "Set NTP servers" +msgstr "NTP ཞབས་ཞུ་ཆས་སྒྲིག" + +#: src/network/org.freedesktop.network1.policy:23 +msgid "Authentication is required to set NTP servers." +msgstr "NTP ཞབས་ཞུ་ཆས་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:33 +#: src/resolve/org.freedesktop.resolve1.policy:44 +msgid "Set DNS servers" +msgstr "DNS ཞབས་ཞུ་ཆས་སྒྲིག" + +#: src/network/org.freedesktop.network1.policy:34 +#: src/resolve/org.freedesktop.resolve1.policy:45 +msgid "Authentication is required to set DNS servers." +msgstr "DNS ཞབས་ཞུ་ཆས་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:44 +#: src/resolve/org.freedesktop.resolve1.policy:55 +msgid "Set domains" +msgstr "domain སྒྲིག" + +#: src/network/org.freedesktop.network1.policy:45 +#: src/resolve/org.freedesktop.resolve1.policy:56 +msgid "Authentication is required to set domains." +msgstr "domain སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:55 +#: src/resolve/org.freedesktop.resolve1.policy:66 +msgid "Set default route" +msgstr "སྔོན་སྒྲིག route སྒྲིག" + +#: src/network/org.freedesktop.network1.policy:56 +#: src/resolve/org.freedesktop.resolve1.policy:67 +msgid "Authentication is required to set default route." +msgstr "སྔོན་སྒྲིག route སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:66 +#: src/resolve/org.freedesktop.resolve1.policy:77 +msgid "Enable/disable LLMNR" +msgstr "LLMNR སྒོ་འབྱེད/ཁ་རྒྱག" + +#: src/network/org.freedesktop.network1.policy:67 +#: src/resolve/org.freedesktop.resolve1.policy:78 +msgid "Authentication is required to enable or disable LLMNR." +msgstr "LLMNR སྒོ་འབྱེད་དམ་ཁ་རྒྱག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:77 +#: src/resolve/org.freedesktop.resolve1.policy:88 +msgid "Enable/disable multicast DNS" +msgstr "multicast DNS སྒོ་འབྱེད/ཁ་རྒྱག" + +#: src/network/org.freedesktop.network1.policy:78 +#: src/resolve/org.freedesktop.resolve1.policy:89 +msgid "Authentication is required to enable or disable multicast DNS." +msgstr "multicast DNS སྒོ་འབྱེད་དམ་ཁ་རྒྱག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:88 +#: src/resolve/org.freedesktop.resolve1.policy:99 +msgid "Enable/disable DNS over TLS" +msgstr "DNS over TLS སྒོ་འབྱེད/ཁ་རྒྱག" + +#: src/network/org.freedesktop.network1.policy:89 +#: src/resolve/org.freedesktop.resolve1.policy:100 +msgid "Authentication is required to enable or disable DNS over TLS." +msgstr "DNS over TLS སྒོ་འབྱེད་དམ་ཁ་རྒྱག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:99 +#: src/resolve/org.freedesktop.resolve1.policy:110 +msgid "Enable/disable DNSSEC" +msgstr "DNSSEC སྒོ་འབྱེད/ཁ་རྒྱག" + +#: src/network/org.freedesktop.network1.policy:100 +#: src/resolve/org.freedesktop.resolve1.policy:111 +msgid "Authentication is required to enable or disable DNSSEC." +msgstr "DNSSEC སྒོ་འབྱེད་དམ་ཁ་རྒྱག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:110 +#: src/resolve/org.freedesktop.resolve1.policy:121 +msgid "Set DNSSEC Negative Trust Anchors" +msgstr "DNSSEC Negative Trust Anchors སྒྲིག" + +#: src/network/org.freedesktop.network1.policy:111 +#: src/resolve/org.freedesktop.resolve1.policy:122 +msgid "Authentication is required to set DNSSEC Negative Trust Anchors." +msgstr "DNSSEC Negative Trust Anchors སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:121 +msgid "Revert NTP settings" +msgstr "NTP སྒྲིག་འགོད་སླར་གསོ" + +#: src/network/org.freedesktop.network1.policy:122 +msgid "Authentication is required to reset NTP settings." +msgstr "NTP སྒྲིག་འགོད་བསྐྱར་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:132 +msgid "Revert DNS settings" +msgstr "DNS སྒྲིག་འགོད་སླར་གསོ" + +#: src/network/org.freedesktop.network1.policy:133 +msgid "Authentication is required to reset DNS settings." +msgstr "DNS སྒྲིག་འགོད་བསྐྱར་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:143 +msgid "DHCP server sends force renew message" +msgstr "DHCP ཞབས་ཞུ་ཆས་ཀྱིས force renew འཕྲིན་གཏོང" + +#: src/network/org.freedesktop.network1.policy:144 +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "DHCP ཞབས་ཞུ་ཆས་ནས force renew འཕྲིན་གཏོང་བར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:154 +msgid "Renew dynamic addresses" +msgstr "dynamic གནས་ཡུལ་སླར་གསོ" + +#: src/network/org.freedesktop.network1.policy:155 +msgid "Authentication is required to renew dynamic addresses." +msgstr "dynamic གནས་ཡུལ་སླར་གསོ་བར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:165 +msgid "Reload network settings" +msgstr "དྲ་རྒྱའི་སྒྲིག་འགོད་བསྐྱར་འཇུག" + +#: src/network/org.freedesktop.network1.policy:166 +msgid "Authentication is required to reload network settings." +msgstr "དྲ་རྒྱའི་སྒྲིག་འགོད་བསྐྱར་འཇུག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:176 +msgid "Reconfigure network interface" +msgstr "དྲ་རྒྱའི་མཐུད་ཁ་བསྐྱར་སྒྲིག" + +#: src/network/org.freedesktop.network1.policy:177 +msgid "Authentication is required to reconfigure network interface." +msgstr "དྲ་རྒྱའི་མཐུད་ཁ་བསྐྱར་སྒྲིག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:187 +msgid "Specify whether persistent storage for systemd-networkd is available" +msgstr "systemd-networkd ཡི་བརྟན་པོའི་གསོག་ཉར་སྤྱོད་ཆོག་མིན་སྟོན" + +#: src/network/org.freedesktop.network1.policy:188 +msgid "" +"Authentication is required to specify whether persistent storage for systemd-" +"networkd is available." +msgstr "systemd-networkd ཡི་བརྟན་པོའི་གསོག་ཉར་སྤྱོད་ཆོག་མིན་སྟོན་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:198 +msgid "Manage network links" +msgstr "དྲ་རྒྱའི་སྦྲེལ་ལམ་དོ་དམ" + +#: src/network/org.freedesktop.network1.policy:199 +msgid "Authentication is required to manage network links." +msgstr "དྲ་རྒྱའི་སྦྲེལ་ལམ་དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/portable/org.freedesktop.portable1.policy:13 +msgid "Inspect a portable service image" +msgstr "portable service image ཞིབ་བཤེར" + +#: src/portable/org.freedesktop.portable1.policy:14 +msgid "Authentication is required to inspect a portable service image." +msgstr "portable service image ཞིབ་བཤེར་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/portable/org.freedesktop.portable1.policy:23 +msgid "Attach or detach a portable service image" +msgstr "portable service image སྦྲེལ་བའམ་བཀོལ་འཐེན" + +#: src/portable/org.freedesktop.portable1.policy:24 +msgid "" +"Authentication is required to attach or detach a portable service image." +msgstr "portable service image སྦྲེལ་བའམ་བཀོལ་འཐེན་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/portable/org.freedesktop.portable1.policy:34 +msgid "Delete or modify portable service image" +msgstr "portable service image བསུབ་པའམ་བཟོ་བཅོས" + +#: src/portable/org.freedesktop.portable1.policy:35 +msgid "" +"Authentication is required to delete or modify a portable service image." +msgstr "portable service image བསུབ་པའམ་བཟོ་བཅོས་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:22 +msgid "Register a DNS-SD service" +msgstr "DNS-SD ཞབས་ཞུ་ཐོ་འགོད" + +#: src/resolve/org.freedesktop.resolve1.policy:23 +msgid "Authentication is required to register a DNS-SD service." +msgstr "DNS-SD ཞབས་ཞུ་ཐོ་འགོད་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:33 +msgid "Unregister a DNS-SD service" +msgstr "DNS-SD ཞབས་ཞུ་ཐོ་འགོད་མེད་པར་བཟོ" + +#: src/resolve/org.freedesktop.resolve1.policy:34 +msgid "Authentication is required to unregister a DNS-SD service." +msgstr "DNS-SD ཞབས་ཞུ་ཐོ་འགོད་མེད་པར་བཟོ་བར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:132 +msgid "Revert name resolution settings" +msgstr "མིང་འགྲེལ་སྒྲིག་འགོད་སླར་གསོ" + +#: src/resolve/org.freedesktop.resolve1.policy:133 +msgid "Authentication is required to reset name resolution settings." +msgstr "མིང་འགྲེལ་སྒྲིག་འགོད་བསྐྱར་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:143 +msgid "Subscribe query results" +msgstr "འཚོལ་ཞིབ་འབྲས་བུ་མངགས་ཉན" + +#: src/resolve/org.freedesktop.resolve1.policy:144 +msgid "Authentication is required to subscribe query results." +msgstr "འཚོལ་ཞིབ་འབྲས་བུ་མངགས་ཉན་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:154 +msgid "Subscribe to DNS configuration" +msgstr "DNS སྒྲིག་འགོད་མངགས་ཉན" + +#: src/resolve/org.freedesktop.resolve1.policy:155 +msgid "Authentication is required to subscribe to DNS configuration." +msgstr "DNS སྒྲིག་འགོད་མངགས་ཉན་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:165 +msgid "Dump cache" +msgstr "cache ཕྱིར་འདོན" + +#: src/resolve/org.freedesktop.resolve1.policy:166 +msgid "Authentication is required to dump cache." +msgstr "cache ཕྱིར་འདོན་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:176 +msgid "Dump server state" +msgstr "server གནས་ཚུལ་ཕྱིར་འདོན" + +#: src/resolve/org.freedesktop.resolve1.policy:177 +msgid "Authentication is required to dump server state." +msgstr "server གནས་ཚུལ་ཕྱིར་འདོན་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:187 +msgid "Dump statistics" +msgstr "གྲངས་ཚད་ཕྱིར་འདོན" + +#: src/resolve/org.freedesktop.resolve1.policy:188 +msgid "Authentication is required to dump statistics." +msgstr "གྲངས་ཚད་ཕྱིར་འདོན་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:198 +msgid "Reset statistics" +msgstr "གྲངས་ཚད་བསྐྱར་སྒྲིག" + +#: src/resolve/org.freedesktop.resolve1.policy:199 +msgid "Authentication is required to reset statistics." +msgstr "གྲངས་ཚད་བསྐྱར་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +msgid "Check for system updates" +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་ཞིབ་བཤེར" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +msgid "Authentication is required to check for system updates." +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་ཞིབ་བཤེར་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +msgid "Install system updates" +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་སྒྲིག་འཇུག" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +msgid "Authentication is required to install system updates." +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་སྒྲིག་འཇུག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +msgid "Install specific system version" +msgstr "དམིགས་བསལ་རྒྱུད་ལམ་པར་གཞི་སྒྲིག་འཇུག" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +msgid "" +"Authentication is required to update the system to a specific (possibly old) " +"version." +msgstr "རྒྱུད་ལམ་དེ་དམིགས་བསལ་(རྙིང་པ་ཡིན་སྲིད་པའི) པར་གཞིར་གསར་སྒྱུར་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 +msgid "Cleanup old system updates" +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་རྙིང་པ་གཙང་སེལ" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 +msgid "Authentication is required to cleanup old system updates." +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་རྙིང་པ་གཙང་སེལ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +msgid "Manage optional features" +msgstr "འདེམས་ཆོག་པའི་ལས་འགན་དོ་དམ" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +msgid "Authentication is required to manage optional features." +msgstr "འདེམས་ཆོག་པའི་ལས་འགན་དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/timedate/org.freedesktop.timedate1.policy:22 +msgid "Set system time" +msgstr "རྒྱུད་ལམ་དུས་ཚོད་སྒྲིག" + +#: src/timedate/org.freedesktop.timedate1.policy:23 +msgid "Authentication is required to set the system time." +msgstr "རྒྱུད་ལམ་དུས་ཚོད་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/timedate/org.freedesktop.timedate1.policy:33 +msgid "Set system timezone" +msgstr "རྒྱུད་ལམ་དུས་ཁུལ་སྒྲིག" + +#: src/timedate/org.freedesktop.timedate1.policy:34 +msgid "Authentication is required to set the system timezone." +msgstr "རྒྱུད་ལམ་དུས་ཁུལ་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/timedate/org.freedesktop.timedate1.policy:43 +msgid "Set RTC to local timezone or UTC" +msgstr "RTC ས་གནས་དུས་ཁུལ་ཡང་ན UTC ལ་སྒྲིག" + +#: src/timedate/org.freedesktop.timedate1.policy:44 +msgid "" +"Authentication is required to control whether the RTC stores the local or " +"UTC time." +msgstr "RTC ཡིས་ས་གནས་དུས་ཚོད་དམ UTC ཉར་ཚགས་བྱེད་མིན་ཚོད་འཛིན་པར་ར་སྤྲོད་དགོས།" + +#: src/timedate/org.freedesktop.timedate1.policy:53 +msgid "Turn network time synchronization on or off" +msgstr "དྲ་རྒྱའི་དུས་ཚོད་མཉམ་བགྲོད་སྒོ་འབྱེད་དམ་ཁ་རྒྱག" + +#: src/timedate/org.freedesktop.timedate1.policy:54 +msgid "" +"Authentication is required to control whether network time synchronization " +"shall be enabled." +msgstr "དྲ་རྒྱའི་དུས་ཚོད་མཉམ་བགྲོད་སྒོ་འབྱེད་མིན་ཚོད་འཛིན་པར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:372 +msgid "Authentication is required to start '$(unit)'." +msgstr "'$(unit)' འགོ་སློང་བར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:373 +msgid "Authentication is required to stop '$(unit)'." +msgstr "'$(unit)' མཚམས་འཇོག་པར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:374 +msgid "Authentication is required to reload '$(unit)'." +msgstr "'$(unit)' བསྐྱར་འཇུག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:375 src/core/dbus-unit.c:376 +msgid "Authentication is required to restart '$(unit)'." +msgstr "'$(unit)' བསྐྱར་འགོ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:568 +msgid "" +"Authentication is required to send a UNIX signal to the processes of '$" +"(unit)'." +msgstr "UNIX རྟགས་རྒྱ་དེ '$(unit)' གི་བརྒྱུད་རིམ་ལ་གཏོང་བར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:621 +msgid "" +"Authentication is required to send a UNIX signal to the processes of " +"subgroup of '$(unit)'." +msgstr "UNIX རྟགས་རྒྱ་དེ '$(unit)' གི subgroup བརྒྱུད་རིམ་ལ་གཏོང་བར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:649 +msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." +msgstr "'$(unit)' གི \"failed\" གནས་ཚུལ་བསྐྱར་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:679 +msgid "Authentication is required to set properties on '$(unit)'." +msgstr "'$(unit)' སྟེང་གི་གཏོགས་གཤིས་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:776 +msgid "" +"Authentication is required to delete files and directories associated with '$" +"(unit)'." +msgstr "'$(unit)' དང་འབྲེལ་བའི་ཡིག་ཆ་དང་དཀར་ཆག་བསུབ་པར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:813 +msgid "" +"Authentication is required to freeze or thaw the processes of '$(unit)' unit." +msgstr "'$(unit)' unit གི་བརྒྱུད་རིམ་འཁྱགས་བཅུག་གམ་གྲོལ་བར་ར་སྤྲོད་དགོས།" diff --git a/po/ca.po b/po/ca.po index 19beafda1142c..01e66821c3f78 100644 --- a/po/ca.po +++ b/po/ca.po @@ -3,12 +3,12 @@ # Catalan translation for systemd. # Walter Garcia-Fontes , 2016. # Robert Antoni Buj Gelonch , 2018. #zanata -# naly zzwd , 2025. +# naly zzwd , 2025, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-07-28 17:25+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-10 15:58+0000\n" "Last-Translator: naly zzwd \n" "Language-Team: Catalan \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.12.2\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1059,8 +1059,12 @@ msgid "DHCP server sends force renew message" msgstr "El servidor DHCP envia un missatge de renovació forçada" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "Es requereix autenticació per enviar el missatge de renovació forçada." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"Es requereix autenticació per enviar un missatge de renovació forçada des " +"del servidor DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1103,12 +1107,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Gestiona els enllaços de xarxa" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" -"Es requereix autenticació per tornar a carregar la configuració de xarxa." +msgstr "Es requereix autenticació per gestionar els enllaços de xarxa." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" diff --git a/po/cs.po b/po/cs.po index ec34fc5307167..5023b457b3db6 100644 --- a/po/cs.po +++ b/po/cs.po @@ -3,14 +3,14 @@ # Czech translation for systemd. # # Daniel Rusek , 2022, 2023, 2025. -# Pavel Borecki , 2023, 2024, 2025. +# Pavel Borecki , 2023, 2024, 2025, 2026. # Jan Kalabza , 2025. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-12-05 15:00+0000\n" -"Last-Translator: Daniel Rusek \n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-27 12:58+0000\n" +"Last-Translator: Pavel Borecki \n" "Language-Team: Czech \n" "Language: cs\n" @@ -19,7 +19,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" -"X-Generator: Weblate 5.14.3\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1013,8 +1013,12 @@ msgid "DHCP server sends force renew message" msgstr "DHCP server posílá zprávu vynuceného obnovení" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "Pro poslání zprávy vynuceného obnovení je vyžadováno ověření." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"Pro poslání zprávy z DHCP serveru o vynuceného obnovení je vyžadováno " +"ověření." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1054,11 +1058,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Spravovat síťové linky" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "Pro správu síťových linek je zapotřebí ověření se." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" diff --git a/po/da.po b/po/da.po index 9fa704a372e9d..26b541a829204 100644 --- a/po/da.po +++ b/po/da.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2021-06-02 16:03+0000\n" "Last-Translator: scootergrisen \n" "Language-Team: Danish , 2024. # Weblate Translation Memory , 2024, 2025. # Anselm Schueler , 2024. -# Marcel Leismann , 2025. +# Marcel Leismann , 2025, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2026-02-22 23:58+0000\n" -"Last-Translator: Ettore Atalan \n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-10 15:58+0000\n" +"Last-Translator: Marcel Leismann \n" "Language-Team: German \n" "Language: de\n" @@ -25,7 +25,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.16\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1098,9 +1098,12 @@ msgid "DHCP server sends force renew message" msgstr "Der DHCP-Server sendet Nachricht zum erzwungenen Erneuern" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" -"Legitimierung ist zum Versenden einer Zwangserneuerungsnachricht notwendig." +"Legitimierung ist zum Versenden einer Zwangserneuerungsnachricht vom DHCP " +"Server notwendig." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" diff --git a/po/el.po b/po/el.po index daaabfc354542..c7b321f942d13 100644 --- a/po/el.po +++ b/po/el.po @@ -9,9 +9,9 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2026-02-27 16:58+0000\n" -"Last-Translator: Efstathios Iosifidis \n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-03 08:58+0000\n" +"Last-Translator: Jim Spentzos \n" "Language-Team: Greek \n" "Language: el\n" @@ -338,7 +338,8 @@ msgstr "Ο κωδικός πρόσβασης έληξε, απαιτείται α #: src/home/pam_systemd_home.c:1056 msgid "Password is expired, but can't change, refusing login." -msgstr "Ο κωδικός πρόσβασης έληξε αλλά δεν μπορεί να αλλαχθεί, άρνηση σύνδεσης." +msgstr "" +"Ο κωδικός πρόσβασης έληξε αλλά δεν μπορεί να αλλαχθεί, άρνηση σύνδεσης." #: src/home/pam_systemd_home.c:1060 msgid "Password will expire soon, please change." @@ -350,7 +351,8 @@ msgstr "Ορισμός ονόματος οικοδεσπότη" #: src/hostname/org.freedesktop.hostname1.policy:21 msgid "Authentication is required to set the local hostname." -msgstr "Απαιτείται ταυτοποίηση για τον ορισμό του τοπικού ονόματος οικοδεσπότη." +msgstr "" +"Απαιτείται ταυτοποίηση για τον ορισμό του τοπικού ονόματος οικοδεσπότη." #: src/hostname/org.freedesktop.hostname1.policy:30 msgid "Set static hostname" @@ -475,7 +477,8 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:44 msgid "Allow applications to inhibit system sleep" -msgstr "Να επιτρέπεται στις εφαρμογές να εμποδίζουν την αναστολή του συστήματος" +msgstr "" +"Να επιτρέπεται στις εφαρμογές να εμποδίζουν την αναστολή του συστήματος" #: src/login/org.freedesktop.login1.policy:45 msgid "Authentication is required for an application to inhibit system sleep." @@ -807,8 +810,7 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:363 msgid "Indicate to the firmware to boot to setup interface" -msgstr "" -"Υπόδειξη στο υλικολογισμικό για εκκίνηση στη διεπαφή ρυθμίσεων (BIOS/UEFI)" +msgstr "Υπόδειξη στο υλικολογισμικό για εκκίνηση στο περιβάλλον ρυθμίσεων" #: src/login/org.freedesktop.login1.policy:364 msgid "" @@ -865,7 +867,8 @@ msgstr "Σύνδεση σε τοπικό εμπορευματοκιβώτιο (c #: src/machine/org.freedesktop.machine1.policy:23 msgid "Authentication is required to log into a local container." -msgstr "Απαιτείται ταυτοποίηση για τη σύνδεση σε ένα τοπικό εμπορευματοκιβώτιο." +msgstr "" +"Απαιτείται ταυτοποίηση για τη σύνδεση σε ένα τοπικό εμπορευματοκιβώτιο." #: src/machine/org.freedesktop.machine1.policy:32 msgid "Log into the local host" @@ -1005,7 +1008,8 @@ msgstr "Ενεργοποίηση/απενεργοποίηση LLMNR" #: src/network/org.freedesktop.network1.policy:67 #: src/resolve/org.freedesktop.resolve1.policy:78 msgid "Authentication is required to enable or disable LLMNR." -msgstr "Απαιτείται ταυτοποίηση για την ενεργοποίηση ή απενεργοποίηση του LLMNR." +msgstr "" +"Απαιτείται ταυτοποίηση για την ενεργοποίηση ή απενεργοποίηση του LLMNR." #: src/network/org.freedesktop.network1.policy:77 #: src/resolve/org.freedesktop.resolve1.policy:88 @@ -1074,7 +1078,11 @@ msgid "DHCP server sends force renew message" msgstr "Ο διακομιστής DHCP στέλνει μήνυμα αναγκαστικής ανανέωσης" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +#, fuzzy +#| msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" "Απαιτείται ταυτοποίηση για την αποστολή μηνύματος αναγκαστικής ανανέωσης." diff --git a/po/es.po b/po/es.po index 70e0eda54a345..6e6304658278f 100644 --- a/po/es.po +++ b/po/es.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2025-11-29 03:49+0000\n" "Last-Translator: \"Fco. Javier F. Serrador\" \n" "Language-Team: Spanish \n" @@ -987,7 +987,11 @@ msgid "DHCP server sends force renew message" msgstr "DHCP server saadab sunduuendamise sõnumi" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +#, fuzzy +#| msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "Autentimine on vajalik, et saata sunduuendamis sõnumi." #: src/network/org.freedesktop.network1.policy:154 diff --git a/po/eu.po b/po/eu.po index 87bfef96df9ca..d292240069525 100644 --- a/po/eu.po +++ b/po/eu.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2023-06-03 15:48+0000\n" "Last-Translator: Asier Sarasua Garmendia \n" "Language-Team: Basque , 2021, 2022, 2023. +# Jan Kuparinen , 2021, 2022, 2023, 2026. # Ricky Tigg , 2022, 2024, 2025. # Jiri Grönroos , 2024. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-09-04 00:52+0000\n" -"Last-Translator: Ricky Tigg \n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-18 18:58+0000\n" +"Last-Translator: Jan Kuparinen \n" "Language-Team: Finnish \n" "Language: fi\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.13\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1022,8 +1022,12 @@ msgid "DHCP server sends force renew message" msgstr "DHCP-palvelin lähettää pakota uusiminen-viestin" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "Todennus vaaditaan pakota uusiminen-viestin lähettämiseksi." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"Todennus vaaditaan pakotetun uudistamisviestin lähettämiseen DHCP-" +"palvelimelta." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1063,11 +1067,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Hallitse verkkoyhteyksiä" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "Verkkolinkkien hallintaan vaaditaan todennus." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" diff --git a/po/fr.po b/po/fr.po index f4757167cb665..daa25ef8af250 100644 --- a/po/fr.po +++ b/po/fr.po @@ -11,8 +11,8 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2026-02-27 16:58+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-07 01:58+0000\n" "Last-Translator: Léane GRASSER \n" "Language-Team: French \n" @@ -21,7 +21,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1089,10 +1089,12 @@ msgid "DHCP server sends force renew message" msgstr "Envoi par le serveur DHCP d'un message de renouvellement forcé" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" "Une authentification est requise pour envoyer un message de renouvellement " -"forcé." +"forcé à partir du serveur DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" diff --git a/po/gl.po b/po/gl.po index e6fa18890fcd9..98aa9b7d6a8f0 100644 --- a/po/gl.po +++ b/po/gl.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2023-04-14 18:20+0000\n" "Last-Translator: Fran Diéguez \n" "Language-Team: Galician \n" "Language-Team: Hebrew \n" @@ -16,7 +16,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n == 1) ? 0 : ((n == 2) ? 1 : ((n > 10 && " "n % 10 == 0) ? 2 : 3));\n" -"X-Generator: Weblate 5.16\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -938,8 +938,10 @@ msgid "DHCP server sends force renew message" msgstr "שרת DHCP שולח הודעת אילוץ חידוש" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "נדרש אימות כדי לשלוח הודעת אילוץ חידוש." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "נדרש אימות כדי לשלוח הודעת אילוץ חידוש משרת ה־DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -977,11 +979,11 @@ msgstr "נדרש אימות כדי לציין האם אחסון קבוע זמי #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "ניהול קישורי רשת" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "נדרש אימות כדי לנהל קישורי רשת." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" diff --git a/po/hi.po b/po/hi.po index 8bad4eaf04e54..1faf0db456f3f 100644 --- a/po/hi.po +++ b/po/hi.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2024-06-03 00:35+0000\n" "Last-Translator: Scrambled 777 \n" "Language-Team: Hindi \n" "Language-Team: Croatian , 2015, 2016. # Balázs Úr , 2016. -# Balázs Meskó , 2022. +# Balázs Meskó , 2022, 2026. # Balázs Úr , 2023. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2023-09-27 01:36+0000\n" -"Last-Translator: Balázs Úr \n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-04-03 19:58+0000\n" +"Last-Translator: Balázs Meskó \n" "Language-Team: Hungarian \n" +"systemd/main/hu/>\n" "Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.0.2\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -30,7 +30,7 @@ msgstr "Jelmondat visszaküldése a rendszernek" msgid "" "Authentication is required to send the entered passphrase back to the system." msgstr "" -"Hitelesítés szükséges a megadott jelmondatnak a rendszernek való " +"Hitelesítés szükséges a megadott jelmondat a rendszernek való " "visszaküldéséhez." #: src/core/org.freedesktop.systemd1.policy.in:33 @@ -120,12 +120,10 @@ msgid "Authentication is required to update a user's home area." msgstr "Hitelesítés szükséges a felhasználó saját területének frissítéséhez." #: src/home/org.freedesktop.home1.policy:53 -#, fuzzy msgid "Update your home area" msgstr "Saját terület frissítése" #: src/home/org.freedesktop.home1.policy:54 -#, fuzzy msgid "Authentication is required to update your home area." msgstr "Hitelesítés szükséges a felhasználó saját területének frissítéséhez." @@ -149,24 +147,20 @@ msgstr "" "megváltoztatásához." #: src/home/org.freedesktop.home1.policy:83 -#, fuzzy msgid "Activate a home area" -msgstr "Saját terület létrehozása" +msgstr "Saját terület aktiválása" #: src/home/org.freedesktop.home1.policy:84 -#, fuzzy msgid "Authentication is required to activate a user's home area." -msgstr "Hitelesítés szükséges a felhasználó saját területének létrehozásához." +msgstr "Hitelesítés szükséges a felhasználó saját területének aktiválásához." #: src/home/org.freedesktop.home1.policy:93 msgid "Manage Home Directory Signing Keys" -msgstr "" +msgstr "Saját könyvtár aláírókulcsainak kezelése" #: src/home/org.freedesktop.home1.policy:94 -#, fuzzy msgid "Authentication is required to manage signing keys for home directories." -msgstr "" -"Hitelesítés szükséges a rendszerszolgáltatások vagy más egységek kezeléséhez." +msgstr "Hitelesítés szükséges a saját könyvtárak aláírókulcsainak kezeléséhez." #: src/home/pam_systemd_home.c:330 #, c-format @@ -280,7 +274,7 @@ msgstr "" #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" -"%s felhasználó biztonsági tokenjének PIN-kódja helytelen (már egy " +"%s felhasználó biztonsági tokenjének PIN-kódja helytelen (még egy " "próbálkozás maradt!)" #: src/home/pam_systemd_home.c:679 @@ -394,46 +388,37 @@ msgid "Authentication is required to get system description." msgstr "Hitelesítés szükséges a rendszer leírásának lekéréséhez." #: src/import/org.freedesktop.import1.policy:22 -#, fuzzy msgid "Import a disk image" -msgstr "VM vagy konténer lemezképének importálása" +msgstr "Lemezkép importálása" #: src/import/org.freedesktop.import1.policy:23 -#, fuzzy msgid "Authentication is required to import an image." -msgstr "Hitelesítés szükséges a VM vagy konténer lemezképének importálásához." +msgstr "Hitelesítés szükséges a lemezkép importálásához." #: src/import/org.freedesktop.import1.policy:32 -#, fuzzy msgid "Export a disk image" -msgstr "VM vagy konténer lemezképének exportálása" +msgstr "Lemezkép exportálása" #: src/import/org.freedesktop.import1.policy:33 -#, fuzzy msgid "Authentication is required to export disk image." -msgstr "Hitelesítés szükséges a VM vagy konténer lemezképének exportálásához." +msgstr "Hitelesítés szükséges a lemezkép exportálásához." #: src/import/org.freedesktop.import1.policy:42 -#, fuzzy msgid "Download a disk image" -msgstr "VM vagy konténer lemezképének letöltése" +msgstr "Lemezkép letöltése" #: src/import/org.freedesktop.import1.policy:43 -#, fuzzy msgid "Authentication is required to download a disk image." -msgstr "Hitelesítés szükséges a VM vagy konténer lemezképének letöltéséhez." +msgstr "Hitelesítés szükséges a lemezkép letöltéséhez." #: src/import/org.freedesktop.import1.policy:52 msgid "Cancel transfer of a disk image" -msgstr "" +msgstr "Lemezkép átvitelének megszakítása" #: src/import/org.freedesktop.import1.policy:53 -#, fuzzy msgid "" "Authentication is required to cancel the ongoing transfer of a disk image." -msgstr "" -"Hitelesítés szükséges a felhasználó saját területe jelszavának " -"megváltoztatásához." +msgstr "Hitelesítés szükséges a lemezkép futó átvitelének megszakításához." #: src/locale/org.freedesktop.locale1.policy:22 msgid "Set system locale" @@ -489,7 +474,7 @@ msgstr "Az alkalmazások késleltethetik a rendszer altatását" #: src/login/org.freedesktop.login1.policy:56 msgid "Authentication is required for an application to delay system sleep." msgstr "" -"Hitelesítés szükséges egy alkalmazás számára a rendszeraltatás " +"Hitelesítés szükséges egy alkalmazás számára a rendszer altatásának " "késleltetéséhez." #: src/login/org.freedesktop.login1.policy:65 @@ -501,8 +486,8 @@ msgid "" "Authentication is required for an application to inhibit automatic system " "suspend." msgstr "" -"Hitelesítés szükséges egy alkalmazás számára az automatikus " -"rendszerfelfüggesztés meggátlásához." +"Hitelesítés szükséges egy alkalmazás számára a rendszer automatikus " +"felfüggesztésének meggátlásához." #: src/login/org.freedesktop.login1.policy:75 msgid "Allow applications to inhibit system handling of the power key" @@ -514,8 +499,8 @@ msgid "" "Authentication is required for an application to inhibit system handling of " "the power key." msgstr "" -"Hitelesítés szükséges egy alkalmazás számára a bekapcsoló gomb rendszer " -"általi kezelésének meggátlásához." +"Hitelesítés szükséges, hogy egy alkalmazás számára a bekapcsoló gomb " +"rendszer általi kezelésének meggátlásához." #: src/login/org.freedesktop.login1.policy:86 msgid "Allow applications to inhibit system handling of the suspend key" @@ -849,7 +834,6 @@ msgid "Set a wall message" msgstr "Falüzenet beállítása" #: src/login/org.freedesktop.login1.policy:397 -#, fuzzy msgid "Authentication is required to set a wall message." msgstr "Hitelesítés szükséges a falüzenet beállításához." @@ -922,26 +906,24 @@ msgid "" msgstr "Hitelesítés szükséges helyi virtuális gépek és konténerek kezeléséhez." #: src/machine/org.freedesktop.machine1.policy:95 -#, fuzzy msgid "Create a local virtual machine or container" -msgstr "Helyi virtuális gépek és konténerek kezelése" +msgstr "Helyi virtuális gép vagy konténer létrehozása" #: src/machine/org.freedesktop.machine1.policy:96 -#, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." -msgstr "Hitelesítés szükséges helyi virtuális gépek és konténerek kezeléséhez." +msgstr "" +"Hitelesítés szükséges a helyi virtuális gép vagy konténer létrehozásához." #: src/machine/org.freedesktop.machine1.policy:106 -#, fuzzy msgid "Register a local virtual machine or container" -msgstr "Helyi virtuális gépek és konténerek kezelése" +msgstr "Helyi virtuális gép vagy konténer regisztrálása" #: src/machine/org.freedesktop.machine1.policy:107 -#, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." -msgstr "Hitelesítés szükséges helyi virtuális gépek és konténerek kezeléséhez." +msgstr "" +"Hitelesítés szükséges a helyi virtuális gép vagy konténer regisztrálásához." #: src/machine/org.freedesktop.machine1.policy:116 msgid "Manage local virtual machine and container images" @@ -1066,8 +1048,12 @@ msgid "DHCP server sends force renew message" msgstr "A DHCP-kiszolgáló kényszerített megújítási üzenetet küld" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "Hitelesítés szükséges a kényszerített megújítási üzenetet küldéséhez." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"Hitelesítés szükséges a kényszerített megújítási üzenetet küldéséhez a DHCP " +"kiszolgálótól." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1095,21 +1081,23 @@ msgstr "Hitelesítés szükséges a hálózati csatoló újrakonfigurálásához #: src/network/org.freedesktop.network1.policy:187 msgid "Specify whether persistent storage for systemd-networkd is available" -msgstr "" +msgstr "Adja meg, hogy érhető-e el tartós tároló a systemd-networkd számára" #: src/network/org.freedesktop.network1.policy:188 msgid "" "Authentication is required to specify whether persistent storage for systemd-" "networkd is available." msgstr "" +"Hitelesítés szükséges annak a beállításához, hogy érhető-e el tartós tároló " +"a systemd-networkd számára." #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Hálózati csatolók kezelése" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "Hitelesítés szükséges a hálózati csatolók kezeléséhez." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" @@ -1147,7 +1135,6 @@ msgid "Register a DNS-SD service" msgstr "DNS-SD szolgáltatás regisztrálása" #: src/resolve/org.freedesktop.resolve1.policy:23 -#, fuzzy msgid "Authentication is required to register a DNS-SD service." msgstr "Hitelesítés szükséges a DNS-SD szolgáltatás regisztrálásához." @@ -1156,7 +1143,6 @@ msgid "Unregister a DNS-SD service" msgstr "DNS-SD szolgáltatás regisztrációjának törlése" #: src/resolve/org.freedesktop.resolve1.policy:34 -#, fuzzy msgid "Authentication is required to unregister a DNS-SD service." msgstr "" "Hitelesítés szükséges a DNS-SD szolgáltatás regisztrációjának törléséhez." @@ -1171,106 +1157,95 @@ msgstr "Hitelesítés szükséges a névfeloldási beállítások visszaállít #: src/resolve/org.freedesktop.resolve1.policy:143 msgid "Subscribe query results" -msgstr "" +msgstr "Feliratkozás lekérdezési találatokra" #: src/resolve/org.freedesktop.resolve1.policy:144 -#, fuzzy msgid "Authentication is required to subscribe query results." -msgstr "Hitelesítés szükséges a rendszer felfüggesztéséhez." +msgstr "Hitelesítés szükséges a lekérdezési találatokra való feliratkozáshoz." #: src/resolve/org.freedesktop.resolve1.policy:154 msgid "Subscribe to DNS configuration" -msgstr "" +msgstr "Feliratkozás a DNS beállításokra" #: src/resolve/org.freedesktop.resolve1.policy:155 -#, fuzzy msgid "Authentication is required to subscribe to DNS configuration." -msgstr "Hitelesítés szükséges a rendszer felfüggesztéséhez." +msgstr "Hitelesítés szükséges a DNS beállításokra való feliratkozáshoz." #: src/resolve/org.freedesktop.resolve1.policy:165 msgid "Dump cache" -msgstr "" +msgstr "Gyorsítótár eldboása" #: src/resolve/org.freedesktop.resolve1.policy:166 -#, fuzzy msgid "Authentication is required to dump cache." -msgstr "Hitelesítés szükséges a tartományok beállításához." +msgstr "Hitelesítés szükséges a gyorsítótár eldobásához." #: src/resolve/org.freedesktop.resolve1.policy:176 msgid "Dump server state" -msgstr "" +msgstr "Kiszolgálóállapot eldobása" #: src/resolve/org.freedesktop.resolve1.policy:177 -#, fuzzy msgid "Authentication is required to dump server state." -msgstr "Hitelesítés szükséges az NTP-kiszolgálók beállításához." +msgstr "Hitelesítés szükséges a kiszolgálóállapot eldobásához." #: src/resolve/org.freedesktop.resolve1.policy:187 msgid "Dump statistics" -msgstr "" +msgstr "Statisztikák eldobása" #: src/resolve/org.freedesktop.resolve1.policy:188 -#, fuzzy msgid "Authentication is required to dump statistics." -msgstr "Hitelesítés szükséges a tartományok beállításához." +msgstr "Hitelesítés szükséges a statisztikák eldobásához." #: src/resolve/org.freedesktop.resolve1.policy:198 msgid "Reset statistics" -msgstr "" +msgstr "Statisztikák visszaállítása" #: src/resolve/org.freedesktop.resolve1.policy:199 -#, fuzzy msgid "Authentication is required to reset statistics." -msgstr "Hitelesítés szükséges az NTP-beállítások visszaállításához." +msgstr "Hitelesítés szükséges a statisztikák visszaállításához." #: src/sysupdate/org.freedesktop.sysupdate1.policy:35 msgid "Check for system updates" -msgstr "" +msgstr "Rendszerfrissítések keresése" #: src/sysupdate/org.freedesktop.sysupdate1.policy:36 -#, fuzzy msgid "Authentication is required to check for system updates." -msgstr "Hitelesítés szükséges a rendszeridő beállításához." +msgstr "Hitelesítés szükséges a rendszerfrissítések kereséséhez." #: src/sysupdate/org.freedesktop.sysupdate1.policy:45 msgid "Install system updates" -msgstr "" +msgstr "Rendszerfrissítések telepítése" #: src/sysupdate/org.freedesktop.sysupdate1.policy:46 -#, fuzzy msgid "Authentication is required to install system updates." -msgstr "Hitelesítés szükséges a rendszeridő beállításához." +msgstr "Hitelesítés szükséges a rendszerfrissítések telepítéséhez." #: src/sysupdate/org.freedesktop.sysupdate1.policy:55 msgid "Install specific system version" -msgstr "" +msgstr "Konkrét rendszerverzió telepítése" #: src/sysupdate/org.freedesktop.sysupdate1.policy:56 -#, fuzzy msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." -msgstr "Hitelesítés szükséges a rendszer időzónájának beállításához." +msgstr "" +"Hitelesítés szükséges a rendszer egy konkrét (esetleg régi) verzióra történő " +"frissítéséhez." #: src/sysupdate/org.freedesktop.sysupdate1.policy:65 msgid "Cleanup old system updates" -msgstr "" +msgstr "Régi rendszerfrissítések eltakarítása" #: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy msgid "Authentication is required to cleanup old system updates." -msgstr "Hitelesítés szükséges a rendszeridő beállításához." +msgstr "Hitelesítés szükséges a régi rendszerfrissítések eltakarításához." #: src/sysupdate/org.freedesktop.sysupdate1.policy:75 msgid "Manage optional features" -msgstr "" +msgstr "Választható funkciók kezelése" #: src/sysupdate/org.freedesktop.sysupdate1.policy:76 -#, fuzzy msgid "Authentication is required to manage optional features." -msgstr "" -"Hitelesítés szükséges az aktív munkamenetek, felhasználók és munkaállomások " -"kezeléséhez." +msgstr "Hitelesítés szükséges a választható funkciók kezeléséhez." #: src/timedate/org.freedesktop.timedate1.policy:22 msgid "Set system time" @@ -1337,13 +1312,12 @@ msgstr "" "küldéséhez." #: src/core/dbus-unit.c:621 -#, fuzzy msgid "" "Authentication is required to send a UNIX signal to the processes of " "subgroup of '$(unit)'." msgstr "" -"Hitelesítés szükséges a(z) „$(unit)” folyamatainak történő UNIX szignál " -"küldéséhez." +"Hitelesítés szükséges a(z) „$(unit)” alcsoport folyamatainak történő UNIX " +"szignál küldéséhez." #: src/core/dbus-unit.c:649 msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." diff --git a/po/ia.po b/po/ia.po index 222a9c98308bf..76a44872965c0 100644 --- a/po/ia.po +++ b/po/ia.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2025-02-17 18:20+0000\n" "Last-Translator: Emilio Sepulveda \n" "Language-Team: Interlingua , 2014, 2021, 2022, 2024, 2025. +# Arif Budiman , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-07-28 17:25+0000\n" -"Last-Translator: Andika Triwidada \n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-23 06:58+0000\n" +"Last-Translator: Arif Budiman \n" "Language-Team: Indonesian \n" "Language: id\n" @@ -15,7 +16,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.12.2\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1014,8 +1015,12 @@ msgid "DHCP server sends force renew message" msgstr "Server HDCP mengirim pesan paksa pembaruan ulang" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "Otentikasi diperlukan untuk mengirim pesan paksa pembaruan ulang." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"Otentikasi diperlukan untuk mengirim pesan paksa pembaruan ulang dari server " +"DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1055,11 +1060,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Kelola koneksi jaringan" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "Otentikasi diperlukan untuk mengelola koneksi jaringan." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" diff --git a/po/it.po b/po/it.po index 45e9763326b4b..848dcee155c32 100644 --- a/po/it.po +++ b/po/it.po @@ -3,15 +3,15 @@ # Italian translation for systemd package # Traduzione in italiano per il pacchetto systemd # Daniele Medri , 2013-2024. -# Salvatore Cocuzza , 2024. +# Salvatore Cocuzza , 2024, 2026. # Nathan , 2025. msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-08-06 15:08+0000\n" -"Last-Translator: Nathan \n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-05 22:10+0000\n" +"Last-Translator: Salvatore Cocuzza \n" "Language-Team: Italian \n" "Language: it\n" @@ -19,7 +19,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.12.2\n" +"X-Generator: Weblate 5.16.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1052,8 +1052,12 @@ msgid "DHCP server sends force renew message" msgstr "Il server DHCP invia messaggi di rinnovo forzato" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "Autenticazione richiesta per inviare messaggi di rinnovo forzato." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"Per inviare un messaggio di rinnovo forzato dal server DHCP è richiesta " +"l'autenticazione." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1094,11 +1098,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Gestire i collegamenti di rete" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "Per gestire i collegamenti di rete è richiesta l'autenticazione." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" diff --git a/po/ja.po b/po/ja.po index bd58347ec0821..28bf9ae0f4f61 100644 --- a/po/ja.po +++ b/po/ja.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2025-03-17 03:11+0000\n" "Last-Translator: Y T \n" "Language-Team: Japanese \n" "Language-Team: Georgian \n" @@ -14,7 +14,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1013,8 +1013,12 @@ msgid "DHCP server sends force renew message" msgstr "DHCP სერვერმა ნაძალადევი განახლების შეტყობინება გამოაგზავნა" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "ნაძალადევი განახლების შეტყობინების გასაგზავნად საჭიროა ავთენტიკაცია." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"DHCP სერვერიდან ნაძალადევი განახლების შეტყობინების გასაგზავნად საჭიროა " +"ავთენტიკაცია." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" diff --git a/po/kab.po b/po/kab.po index 0b7002d48fe91..5f907a00884a0 100644 --- a/po/kab.po +++ b/po/kab.po @@ -4,13 +4,13 @@ # Slimane Selyan Amiri , 2021. # ButterflyOfFire , 2024. # ButterflyOfFire , 2025. +# Massii Aqvayli , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-08-05 01:30+0000\n" -"Last-Translator: ButterflyOfFire " -"\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-25 21:58+0000\n" +"Last-Translator: Massii Aqvayli \n" "Language-Team: Kabyle \n" "Language: kab\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 5.12.2\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -27,50 +27,52 @@ msgstr "Azen tafyirt n uɛeddi ɣer unagraw" #: src/core/org.freedesktop.systemd1.policy.in:23 msgid "" "Authentication is required to send the entered passphrase back to the system." -msgstr "" +msgstr "Asesteb yettwasra i tuzzna n tefyirt n uɛeddi i teskecmeḍ ɣer unagraw." #: src/core/org.freedesktop.systemd1.policy.in:33 msgid "Manage system services or other units" -msgstr "" +msgstr "Sefrek imeẓla n unagraw neɣ iferdisen-nniḍen" #: src/core/org.freedesktop.systemd1.policy.in:34 msgid "Authentication is required to manage system services or other units." -msgstr "" +msgstr "Asesteb yettwasra i usefrek n imeẓla n unagraw neɣ iferdisen-nniḍen." #: src/core/org.freedesktop.systemd1.policy.in:43 msgid "Manage system service or unit files" -msgstr "" +msgstr "Sefrek ifuyla n umeẓlu neɣ aferdis unagraw" #: src/core/org.freedesktop.systemd1.policy.in:44 msgid "Authentication is required to manage system service or unit files." -msgstr "" +msgstr "Asesteb yettwasra i usefrek n umeẓlu unagraw neɣ ifuyla n uferdis." #: src/core/org.freedesktop.systemd1.policy.in:54 msgid "Set or unset system and service manager environment variables" -msgstr "" +msgstr "Sbadu neɣ kkes imuttiyen n twennaṭ seg umsefrak n unagraw akked imeẓla" #: src/core/org.freedesktop.systemd1.policy.in:55 msgid "" "Authentication is required to set or unset system and service manager " "environment variables." msgstr "" +"Ilaq usesteb i usbadu neɣ tukksa n yimuttiyen n twennaṭ seg umsefrak n " +"unagraw akked imeẓla." #: src/core/org.freedesktop.systemd1.policy.in:64 msgid "Reload the systemd state" -msgstr "" +msgstr "Ales asali n waddad n systemd" #: src/core/org.freedesktop.systemd1.policy.in:65 msgid "Authentication is required to reload the systemd state." -msgstr "" +msgstr "Asesteb yettwasra i wallus usali n waddad n unagraw." #: src/core/org.freedesktop.systemd1.policy.in:74 msgid "Dump the systemd state without rate limits" -msgstr "" +msgstr "Silem addad n systemd war tilisa n watug" #: src/core/org.freedesktop.systemd1.policy.in:75 msgid "" "Authentication is required to dump the systemd state without rate limits." -msgstr "" +msgstr "Asesteb yesra i usillem n waddad n systemd war tilisa n watug." #: src/home/org.freedesktop.home1.policy:13 msgid "Create a home area" @@ -78,32 +80,34 @@ msgstr "Rnu tmennaḍt agejdan" #: src/home/org.freedesktop.home1.policy:14 msgid "Authentication is required to create a user's home area." -msgstr "" +msgstr "Asesteb yettwasra i tmerna n temnaḍt tagejdant n useqdac." #: src/home/org.freedesktop.home1.policy:23 msgid "Remove a home area" -msgstr "" +msgstr "Kkes tamnaḍt tagejdant" #: src/home/org.freedesktop.home1.policy:24 msgid "Authentication is required to remove a user's home area." -msgstr "" +msgstr "Asesteb yettwasra i tukksa n temnaḍt tagejdant n useqdac." #: src/home/org.freedesktop.home1.policy:33 msgid "Check credentials of a home area" -msgstr "" +msgstr "Selken talɣut n usesteb n temnaḍt tagejdant" #: src/home/org.freedesktop.home1.policy:34 msgid "" "Authentication is required to check credentials against a user's home area." msgstr "" +"Asesteb yettwasra i uselken n talɣut n usesteb deg temnaḍt tagejdant n " +"useqdac." #: src/home/org.freedesktop.home1.policy:43 msgid "Update a home area" -msgstr "" +msgstr "Leqqem tamnaḍt tagejdant" #: src/home/org.freedesktop.home1.policy:44 msgid "Authentication is required to update a user's home area." -msgstr "" +msgstr "Asesteb yettwasera i uleqqem n tamnaḍt tagejdant n useqdac." #: src/home/org.freedesktop.home1.policy:53 msgid "Update your home area" @@ -111,24 +115,25 @@ msgstr "Mucced tamnaḍt-ik·im tagejdant" #: src/home/org.freedesktop.home1.policy:54 msgid "Authentication is required to update your home area." -msgstr "" +msgstr "Asesteb yettwasera i uleqqem n tamnaḍt tagejdant." #: src/home/org.freedesktop.home1.policy:63 msgid "Resize a home area" -msgstr "" +msgstr "Ales tiddi n temnaḍt n tagejdant" #: src/home/org.freedesktop.home1.policy:64 msgid "Authentication is required to resize a user's home area." -msgstr "" +msgstr "Asesteb yettwasera i wales tiddi n temnaḍt tagejdant n useqdac." #: src/home/org.freedesktop.home1.policy:73 msgid "Change password of a home area" -msgstr "" +msgstr "Snifel awal n uɛeddi n temnaḍt tagejdant" #: src/home/org.freedesktop.home1.policy:74 msgid "" "Authentication is required to change the password of a user's home area." msgstr "" +"Asesteb yettwasera i usnifel n wawal n uɛeddi n temnaḍt tagejdant n useqdac." #: src/home/org.freedesktop.home1.policy:83 msgid "Activate a home area" @@ -136,15 +141,15 @@ msgstr "Rmed tamnaḍṭ-ik·im tagejdant" #: src/home/org.freedesktop.home1.policy:84 msgid "Authentication is required to activate a user's home area." -msgstr "" +msgstr "Asesteb yettwasera i wesermed n temnaḍt tagejdant n useqdac." #: src/home/org.freedesktop.home1.policy:93 msgid "Manage Home Directory Signing Keys" -msgstr "" +msgstr "Sefrek tisura uzmul n ukaram agejdan" #: src/home/org.freedesktop.home1.policy:94 msgid "Authentication is required to manage signing keys for home directories." -msgstr "" +msgstr "Asesteb yettwasera i usefrek n tisura uzmul n ikaramen igejdanen." #: src/home/pam_systemd_home.c:330 #, c-format @@ -152,11 +157,13 @@ msgid "" "Home of user %s is currently absent, please plug in the necessary storage " "device or backing file system." msgstr "" +"Agejdan n useqdac %s ulac-it akka tura, ttxil-k·m, qqen ibenk n usekles " +"ilaqen neɣ anagraw n ufaylu i yellan deg-s." #: src/home/pam_systemd_home.c:335 #, c-format msgid "Too frequent login attempts for user %s, try again later." -msgstr "" +msgstr "Ddeqs n uɛraḍ n tuqqna ɣer useqdac %s, ɛreḍ tikelt nniḍen ticki." #: src/home/pam_systemd_home.c:347 msgid "Password: " @@ -165,15 +172,15 @@ msgstr "Awal n uɛeddi: " #: src/home/pam_systemd_home.c:349 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." -msgstr "" +msgstr "Awal n uɛeddi d armeɣtu neɣ ur yekfa ara i usesteb n useqdac %s." #: src/home/pam_systemd_home.c:350 msgid "Sorry, try again: " -msgstr "" +msgstr "Suref-aɣ, ɛreḍ tikkelt nniḍen: " #: src/home/pam_systemd_home.c:372 msgid "Recovery key: " -msgstr "" +msgstr "Tasarut n tririt: " #: src/home/pam_systemd_home.c:374 #, c-format @@ -181,15 +188,17 @@ msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" +"Awal n uɛeddi/tasarut n tririt d armeɣtu neɣ ur yekfa ara i usesteb n " +"useqdac %s." #: src/home/pam_systemd_home.c:375 msgid "Sorry, reenter recovery key: " -msgstr "" +msgstr "Suref-aɣ, sekcem tikelt nniḍen tasarutt n tririt: " #: src/home/pam_systemd_home.c:395 #, c-format msgid "Security token of user %s not inserted." -msgstr "" +msgstr "Tasarutt n tɣellist n useqdac %s ur tettwasekcem ara." #: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 msgid "Try again with password: " @@ -201,87 +210,99 @@ msgid "" "Password incorrect or not sufficient, and configured security token of user " "%s not inserted." msgstr "" +"Awal n uɛeddi d armeɣtu neɣ ur yekfa ara, u tasarut n tasarutt n tɣellist n " +"useqdac %s ur tettwasekcam ara." #: src/home/pam_systemd_home.c:418 msgid "Security token PIN: " -msgstr "" +msgstr "PIN n tsarut n tɣellist: " #: src/home/pam_systemd_home.c:435 #, c-format msgid "Please authenticate physically on security token of user %s." -msgstr "" +msgstr "Ma ulac aɣilif, sesteb s useqdec n tsarut n tɣellist n useqdac %s." #: src/home/pam_systemd_home.c:446 #, c-format msgid "Please confirm presence on security token of user %s." -msgstr "" +msgstr "Ma ulac aɣilif, sentem tilin-ik·im ɣef tsarut n tɣellist useqdac %s." #: src/home/pam_systemd_home.c:457 #, c-format msgid "Please verify user on security token of user %s." -msgstr "" +msgstr "Ma ulac aɣilif, sentem aseqdac ɣef tsarutt n tɣellist n useqdac %s." #: src/home/pam_systemd_home.c:466 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" msgstr "" +"Tangalt PIN n tsarut n tɣellist tettusekkeṛ, ttxil-k·m kkes-as asekkeṛ deg " +"tazwara. (Amatar: Tukksa akked walus n taguri yezmer ad d-yekfu.)" #: src/home/pam_systemd_home.c:474 #, c-format msgid "Security token PIN incorrect for user %s." -msgstr "" +msgstr "Tangalt PIN n tsarutt n tɣellist mačči d tameɣtut i useqdac %s." #: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 #: src/home/pam_systemd_home.c:513 msgid "Sorry, retry security token PIN: " -msgstr "" +msgstr "Suref-aɣ, sekcem tikelt nniḍen tangalt PIN n tsarut n tɣellist: " #: src/home/pam_systemd_home.c:493 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" +"Tangalt PIN n tsarut n tɣellist n useqdac %s d tarmeɣtut (kra n yineɛruḍen " +"kan i d-yegran!)" #: src/home/pam_systemd_home.c:512 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" +"Tangalt PIN n tsarut n tɣellist n useqdac %s d tarmeɣtut (yiwen kan n uɛraḍ " +"i d-yeqqimen!)" #: src/home/pam_systemd_home.c:679 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" +"Akaram agejdan n useqdac %s ur yermid ara akka tura, ttxil-k·m qqen s wudem " +"adigan deg tazwara." #: src/home/pam_systemd_home.c:681 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" +"Akaram agejdan n useqdac %s isekkeṛ akka tura, ttxil-k·m kkes asekkeṛ s " +"wudem adigan deg tazwara." #: src/home/pam_systemd_home.c:715 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." -msgstr "" +msgstr "Ddeqs n uɛraḍ n tuqqna ur neddi ara i useqdac %s, yugi." #: src/home/pam_systemd_home.c:1012 msgid "User record is blocked, prohibiting access." -msgstr "" +msgstr "Yewḥel usekles n useqdac, yegdel anekcum." #: src/home/pam_systemd_home.c:1016 msgid "User record is not valid yet, prohibiting access." -msgstr "" +msgstr "Asekles n useqdac mačči d ameɣtu akka tura, anekcum yettwagdel." #: src/home/pam_systemd_home.c:1020 msgid "User record is not valid anymore, prohibiting access." -msgstr "" +msgstr "Asekles n useqdac ur d-yiqqim ara d ameɣtu, anekcum yettwagdel." #: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 msgid "User record not valid, prohibiting access." -msgstr "" +msgstr "Asekles n useqdac mačči d ameɣtu, anekcum yettwagdel." #: src/home/pam_systemd_home.c:1035 #, c-format msgid "Too many logins, try again in %s." -msgstr "" +msgstr "Ddeqs n tuqqniwin, ɛreḍ tikelt nniḍen di %s." #: src/home/pam_systemd_home.c:1046 msgid "Password change required." @@ -289,15 +310,15 @@ msgstr "Asnifel n wawal n uɛeddi yettwasra." #: src/home/pam_systemd_home.c:1050 msgid "Password expired, change required." -msgstr "" +msgstr "Awal n uɛeddi yemmut, asnifel yettwasra." #: src/home/pam_systemd_home.c:1056 msgid "Password is expired, but can't change, refusing login." -msgstr "" +msgstr "Awal n uɛeddi yemmut, yerna ur izmir ara ad ibeddel, tuqqna tettwagi." #: src/home/pam_systemd_home.c:1060 msgid "Password will expire soon, please change." -msgstr "" +msgstr "Ur yettɛeṭṭil ara ad yemmet wawal n uɛeddi, ma ulac aɣilif, snifel-it." #: src/hostname/org.freedesktop.hostname1.policy:20 msgid "Set hostname" @@ -305,11 +326,11 @@ msgstr "Sbadu isem n usenneftaɣ" #: src/hostname/org.freedesktop.hostname1.policy:21 msgid "Authentication is required to set the local hostname." -msgstr "" +msgstr "Asesteb yettwasra i usbadu n yisem n usenneftaɣ adigan." #: src/hostname/org.freedesktop.hostname1.policy:30 msgid "Set static hostname" -msgstr "" +msgstr "Sbadu isem n usenneftaɣ udmis" #: src/hostname/org.freedesktop.hostname1.policy:31 msgid "" @@ -323,64 +344,64 @@ msgstr "Sbadu talɣut n tmacint" #: src/hostname/org.freedesktop.hostname1.policy:42 msgid "Authentication is required to set local machine information." -msgstr "" +msgstr "Asesteb yettwasra i usbadu n telɣut n tmacint tadigant." #: src/hostname/org.freedesktop.hostname1.policy:51 msgid "Get product UUID" -msgstr "" +msgstr "Awi-d UUID n ufaris" #: src/hostname/org.freedesktop.hostname1.policy:52 msgid "Authentication is required to get product UUID." -msgstr "" +msgstr "Asesteb yettwasra i wawway n UUID n ufaris." #: src/hostname/org.freedesktop.hostname1.policy:61 msgid "Get hardware serial number" -msgstr "" +msgstr "Awi-d uṭṭun n umazrar n warrum" #: src/hostname/org.freedesktop.hostname1.policy:62 msgid "Authentication is required to get hardware serial number." -msgstr "" +msgstr "Asesteb yettwasra akken ad tawiḍ uṭṭun n umazrar n warrum." #: src/hostname/org.freedesktop.hostname1.policy:71 msgid "Get system description" -msgstr "" +msgstr "Awi-d aglam n unagraw" #: src/hostname/org.freedesktop.hostname1.policy:72 msgid "Authentication is required to get system description." -msgstr "" +msgstr "Asesteb yettwasra i wawway n uglam n unagraw." #: src/import/org.freedesktop.import1.policy:22 msgid "Import a disk image" -msgstr "" +msgstr "Kter tugna n uḍebsi" #: src/import/org.freedesktop.import1.policy:23 msgid "Authentication is required to import an image." -msgstr "" +msgstr "Asesteb yettwasra i ukter n tugna n uḍebsi." #: src/import/org.freedesktop.import1.policy:32 msgid "Export a disk image" -msgstr "" +msgstr "Sifeḍ tugna n uḍebsi" #: src/import/org.freedesktop.import1.policy:33 msgid "Authentication is required to export disk image." -msgstr "" +msgstr "Asesteb yettwasra i wesifeḍ n tugna n uḍebsi." #: src/import/org.freedesktop.import1.policy:42 msgid "Download a disk image" -msgstr "" +msgstr "Sider tugna n uḍebsi" #: src/import/org.freedesktop.import1.policy:43 msgid "Authentication is required to download a disk image." -msgstr "" +msgstr "Asesteb yettwasra i usider n tugna n uḍebsi." #: src/import/org.freedesktop.import1.policy:52 msgid "Cancel transfer of a disk image" -msgstr "" +msgstr "Sefsex asiweḍ n tugna n uḍebsi" #: src/import/org.freedesktop.import1.policy:53 msgid "" "Authentication is required to cancel the ongoing transfer of a disk image." -msgstr "" +msgstr "Asesteb yettwasra i wessefsex n usiweḍ itteddun n tugna n uḍebsi." #: src/locale/org.freedesktop.locale1.policy:22 msgid "Set system locale" @@ -388,32 +409,34 @@ msgstr "Sbedd tutlayt n unagraw" #: src/locale/org.freedesktop.locale1.policy:23 msgid "Authentication is required to set the system locale." -msgstr "" +msgstr "Asesteb yettwasra i usbadu n tutlayt tadigant n unagraw." #: src/locale/org.freedesktop.locale1.policy:33 msgid "Set system keyboard settings" -msgstr "" +msgstr "Sbadu iɣewwaṛen n unasiw n unagraw" #: src/locale/org.freedesktop.locale1.policy:34 msgid "Authentication is required to set the system keyboard settings." -msgstr "" +msgstr "Asesteb yettwasra i usbadu n yiɣewwaṛen n unasiw n unagraw." #: src/login/org.freedesktop.login1.policy:22 msgid "Allow applications to inhibit system shutdown" -msgstr "" +msgstr "Sireg isnasen ad sḥebsen asexsi n unagraw" #: src/login/org.freedesktop.login1.policy:23 msgid "" "Authentication is required for an application to inhibit system shutdown." msgstr "" +"Asesteb yettwasra akken ad isireg asnas ad yezmer i useḥbes n usexsi n " +"unagraw." #: src/login/org.freedesktop.login1.policy:33 msgid "Allow applications to delay system shutdown" -msgstr "" +msgstr "Sireg i yisnasen ad izmiren ad smezgren asexsi n unagraw" #: src/login/org.freedesktop.login1.policy:34 msgid "Authentication is required for an application to delay system shutdown." -msgstr "" +msgstr "Asesteb yettwasra i usnas akken ad yesmezger asexsi n unagraw." #: src/login/org.freedesktop.login1.policy:44 msgid "Allow applications to inhibit system sleep" @@ -493,7 +516,7 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:128 msgid "Allow non-logged-in user to run programs" -msgstr "" +msgstr "Sireg aseqdac aruqqin i uselkem n wahilen" #: src/login/org.freedesktop.login1.policy:129 msgid "Explicit request is required to run programs as a non-logged-in user." @@ -501,7 +524,7 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:138 msgid "Allow non-logged-in users to run programs" -msgstr "" +msgstr "Sireg iseqdacen ur yeqqinen ara i wakken ad slekmen ahilen" #: src/login/org.freedesktop.login1.policy:139 msgid "Authentication is required to run programs as a non-logged-in user." @@ -880,22 +903,22 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:88 #: src/resolve/org.freedesktop.resolve1.policy:99 msgid "Enable/disable DNS over TLS" -msgstr "" +msgstr "Sermed/sens DNS ɣef TLS" #: src/network/org.freedesktop.network1.policy:89 #: src/resolve/org.freedesktop.resolve1.policy:100 msgid "Authentication is required to enable or disable DNS over TLS." -msgstr "" +msgstr "Asesteb yettwasra akken ad tremdeḍ neɣ ad tsenseḍ DNS ɣef TLS." #: src/network/org.freedesktop.network1.policy:99 #: src/resolve/org.freedesktop.resolve1.policy:110 msgid "Enable/disable DNSSEC" -msgstr "" +msgstr "Sermed/Sens DNSSEC" #: src/network/org.freedesktop.network1.policy:100 #: src/resolve/org.freedesktop.resolve1.policy:111 msgid "Authentication is required to enable or disable DNSSEC." -msgstr "" +msgstr "Asesteb yettwasra akken ad tremdeḍ neɣ ad tsenseḍ DNSSEC." #: src/network/org.freedesktop.network1.policy:110 #: src/resolve/org.freedesktop.resolve1.policy:121 @@ -928,7 +951,9 @@ msgid "DHCP server sends force renew message" msgstr "" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" #: src/network/org.freedesktop.network1.policy:154 diff --git a/po/kk.po b/po/kk.po index 7d6e4d3455db4..4ba1dd5f4dae3 100644 --- a/po/kk.po +++ b/po/kk.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2026-02-26 13:58+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-07 01:58+0000\n" "Last-Translator: Baurzhan Muftakhidinov \n" "Language-Team: Kazakh \n" @@ -16,7 +16,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1017,8 +1017,12 @@ msgid "DHCP server sends force renew message" msgstr "DHCP сервері мәжбүрлі жаңарту хабарламасын жібереді" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "Мәжбүрлі жаңарту хабарламасын жіберу үшін аутентификация қажет." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"DHCP серверінен мәжбүрлі жаңарту хабарламасын жіберу үшін аутентификация " +"қажет." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" diff --git a/po/km.po b/po/km.po index 2091fe42aab23..46adcc03b5c6e 100644 --- a/po/km.po +++ b/po/km.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2025-09-28 10:07+0000\n" "Last-Translator: kanitha chim \n" "Language-Team: Khmer (Central) \n" "Language-Team: Korean \n" @@ -18,7 +18,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" "X-Poedit-SourceCharset: UTF-8\n" #: src/core/org.freedesktop.systemd1.policy.in:22 @@ -966,8 +966,10 @@ msgid "DHCP server sends force renew message" msgstr "DHCP 서버에서 새 메시지 강제 전송" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "강제로 새 메시지를 보내려면 인증이 필요합니다." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "DHCP 서버에서 강제로 새 메시지를 보내려면 인증이 필요합니다." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" diff --git a/po/kw.po b/po/kw.po index fb973409da4d6..cb6d3d36147c7 100644 --- a/po/kw.po +++ b/po/kw.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -930,7 +930,9 @@ msgid "DHCP server sends force renew message" msgstr "" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" #: src/network/org.freedesktop.network1.policy:154 diff --git a/po/lt.po b/po/lt.po index 49b3e0e026898..3299932a3efbf 100644 --- a/po/lt.po +++ b/po/lt.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2025-02-28 08:38+0000\n" "Last-Translator: Justinas Kairys \n" "Language-Team: Lithuanian \n" "Language-Team: Dutch , 2020, 2021, 2023. -# A S Alam , 2023, 2024. +# A S Alam , 2023, 2024, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2024-01-16 14:35+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-18 18:58+0000\n" "Last-Translator: A S Alam \n" "Language-Team: Punjabi \n" +"main/pa/>\n" "Language: pa\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 5.3.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -65,10 +65,9 @@ msgid "Dump the systemd state without rate limits" msgstr "" #: src/core/org.freedesktop.systemd1.policy.in:75 -#, fuzzy msgid "" "Authentication is required to dump the systemd state without rate limits." -msgstr "systemd ਹਾਲਤ ਲੋਡ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "ਬਿਨਾਂ ਰੇਟ ਲਿਮਟ ਦੇ systemd ਹਾਲਤ ਲੋਡ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/home/org.freedesktop.home1.policy:13 msgid "Create a home area" @@ -88,7 +87,7 @@ msgstr "ਵਰਤੋਂਕਾਰ ਦੇ ਹੋਮ ਖੇਤਰ ਨੂੰ ਹਟ #: src/home/org.freedesktop.home1.policy:33 msgid "Check credentials of a home area" -msgstr "" +msgstr "ਹੋਮ ਖੇਤਰ ਲਈ ਸਨਦਾਂ ਦੀ ਜਾਂਚ ਕਰੋ" #: src/home/org.freedesktop.home1.policy:34 msgid "" @@ -104,14 +103,12 @@ msgid "Authentication is required to update a user's home area." msgstr "ਵਰਤੋਂਕਾਰ ਦੇ ਹੋਮ ਖੇਤਰ ਨੂੰ ਅੱਪਡੇਟ ਕਰਨ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/home/org.freedesktop.home1.policy:53 -#, fuzzy msgid "Update your home area" -msgstr "ਹੋਮ ਖੇਤਰ ਨੂੰ ਅੱਪਡੇਟ ਕਰੋ" +msgstr "ਆਪਣੇ ਹੋਮ ਖੇਤਰ ਨੂੰ ਅੱਪਡੇਟ ਕਰੋ" #: src/home/org.freedesktop.home1.policy:54 -#, fuzzy msgid "Authentication is required to update your home area." -msgstr "ਵਰਤੋਂਕਾਰ ਦੇ ਹੋਮ ਖੇਤਰ ਨੂੰ ਅੱਪਡੇਟ ਕਰਨ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "ਤੁਹਾਡੇ ਹੋਮ ਖੇਤਰ ਨੂੰ ਅੱਪਡੇਟ ਕਰਨ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/home/org.freedesktop.home1.policy:63 msgid "Resize a home area" @@ -131,23 +128,21 @@ msgid "" msgstr "ਵਰਤੋਂਕਾਰ ਦੇ ਹੋਮ ਖੇਤਰ ਲਈ ਪਾਸਵਰਡ ਨੂੰ ਬਦਲਣ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/home/org.freedesktop.home1.policy:83 -#, fuzzy msgid "Activate a home area" -msgstr "ਹੋਮ ਖੇਤਰ ਬਣਾਓ" +msgstr "ਹੋਮ ਖੇਤਰ ਸਰਗਰਮ ਕਰੋ" #: src/home/org.freedesktop.home1.policy:84 -#, fuzzy msgid "Authentication is required to activate a user's home area." -msgstr "ਵਰਤੋਂਕਾਰ ਦੇ ਹੋਮ ਖੇਤਰ ਨੂੰ ਬਣਾਉਣ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "ਵਰਤੋਂਕਾਰ ਦੇ ਹੋਮ ਖੇਤਰ ਨੂੰ ਸਰਗਰਮ ਕਰਨ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/home/org.freedesktop.home1.policy:93 msgid "Manage Home Directory Signing Keys" msgstr "" #: src/home/org.freedesktop.home1.policy:94 -#, fuzzy msgid "Authentication is required to manage signing keys for home directories." -msgstr "ਸਿਸਟਮ ਸੇਵਾਵਾਂ ਜਾਂ ਹੋਰ ਇਕਾਈਆਂ ਦੇ ਇੰਤਜਾਮ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "" +"ਘਰ ਡਾਇਰੈਕਟਰੀਆਂ ਲਈ ਸਾਈਨ ਕਰਨ ਵਾਲੀਆਂ ਕੁੰਜੀਆਂ ਦੇ ਇੰਤਜ਼ਾਮ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/home/pam_systemd_home.c:330 #, c-format @@ -355,40 +350,36 @@ msgstr "ਸਿਸਟਮ ਜਾਣਕਾਰੀ ਲੈਣ ਲਈ ਪਰਮਾਣ #: src/import/org.freedesktop.import1.policy:22 msgid "Import a disk image" -msgstr "" +msgstr "ਡਿਸਕ ਈਮੇਜ਼ ਨੂੰ ਇੰਪੋਰਟ ਕਰੋ" #: src/import/org.freedesktop.import1.policy:23 -#, fuzzy msgid "Authentication is required to import an image." -msgstr "'$(unit)' ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "ਈਮੇਲ ਨੂੰ ਇੰਪੋਰਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/import/org.freedesktop.import1.policy:32 msgid "Export a disk image" -msgstr "" +msgstr "ਡਿਸਕ ਈਮੇਜ਼ ਨੂੰ ਐਕਸਪੋਰਟ ਕਰੋ" #: src/import/org.freedesktop.import1.policy:33 -#, fuzzy msgid "Authentication is required to export disk image." -msgstr "ਸਿਸਟਮ ਟਾਈਮ ਸੈੱਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "ਡਿਸਕ ਈਮੇਜ਼ ਨੂੰ ਐਕਸਪੋਰਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/import/org.freedesktop.import1.policy:42 msgid "Download a disk image" -msgstr "" +msgstr "ਡਿਸਕ ਈਮੇਜ਼ ਨੂੰ ਡਾਊਨਲੋਡ ਕਰੋ" #: src/import/org.freedesktop.import1.policy:43 -#, fuzzy msgid "Authentication is required to download a disk image." -msgstr "systemd ਹਾਲਤ ਲੋਡ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "ਡਿਸਕ ਈਮੇਜ਼ ਨੂੰ ਡਾਊਨਲੋਡ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/import/org.freedesktop.import1.policy:52 msgid "Cancel transfer of a disk image" -msgstr "" +msgstr "ਡਿਸਕ ਈਮੇਜ਼ ਟਰਾਂਸਫਰ ਨੂੰ ਰੱਦ ਕਰੋ" #: src/import/org.freedesktop.import1.policy:53 -#, fuzzy msgid "" "Authentication is required to cancel the ongoing transfer of a disk image." -msgstr "ਵਰਤੋਂਕਾਰ ਦੇ ਹੋਮ ਖੇਤਰ ਲਈ ਪਾਸਵਰਡ ਨੂੰ ਬਦਲਣ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "ਚੱਲ ਰਹੇ ਡਿਸਕ ਈਮੇਜ਼ ਟਰਾਂਸਫਰ ਨੂੰ ਰੱਦ ਕਰਨ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/locale/org.freedesktop.locale1.policy:22 msgid "Set system locale" @@ -538,7 +529,7 @@ msgstr "ਸਿਸਟਮ ਨੂੰ ਬੰਦ ਕਰੋ" #: src/login/org.freedesktop.login1.policy:170 msgid "Authentication is required to power off the system." -msgstr "" +msgstr "ਸਿਸਟਮ ਨੂੰ ਬੰਦ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/login/org.freedesktop.login1.policy:180 msgid "Power off the system while other users are logged in" @@ -566,17 +557,18 @@ msgstr "ਸਿਸਟਮ ਨੂੰ ਮੁੜ ਚਾਲੂ ਕਰੋ" #: src/login/org.freedesktop.login1.policy:203 msgid "Authentication is required to reboot the system." -msgstr "" +msgstr "ਸਿਸਟਮ ਨੂੰ ਮੁੜ-ਚਾਲੂ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/login/org.freedesktop.login1.policy:213 msgid "Reboot the system while other users are logged in" -msgstr "" +msgstr "ਜਦੋਂ ਹੋਰ ਵਰਤੋਂਕਾਰ ਲਾਗਇਨ ਕੀਤੇ ਹੋਣ ਤਾਂ ਵੀ ਸਿਸਟਮ ਨੂੰ ਮੁੜ-ਚਾਲੂ ਕਰੋ" #: src/login/org.freedesktop.login1.policy:214 msgid "" "Authentication is required to reboot the system while other users are logged " "in." msgstr "" +"ਜਦੋਂ ਹੋਰ ਵਰਤੋਂਕਾਰ ਲਾਗਇਨ ਕੀਤੇ ਹੋਣ ਤਾਂ ਸਿਸਟਮ ਨੂੰ ਮੁੜ-ਚਾਲੂ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/login/org.freedesktop.login1.policy:224 msgid "Reboot the system while an application is inhibiting this" @@ -594,7 +586,7 @@ msgstr "ਸਿਸਟਮ ਨੂੰ ਰੋਕ ਦਿਓ" #: src/login/org.freedesktop.login1.policy:236 msgid "Authentication is required to halt the system." -msgstr "" +msgstr "ਸਿਸਟਮ ਨੂੰ ਰੋਕਣ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/login/org.freedesktop.login1.policy:246 msgid "Halt the system while other users are logged in" @@ -941,8 +933,14 @@ msgid "DHCP server sends force renew message" msgstr "" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "" +#, fuzzy +#| msgid "" +#| "Authentication is required to send the entered passphrase back to the " +#| "system." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "ਦਿੱਤਾ ਵਾਕ ਸਿਸਟਮ ਨੂੰ ਵਾਪਸ ਭੇਜਣ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" diff --git a/po/pl.po b/po/pl.po index b8775fd31f736..913de0d7a73fb 100644 --- a/po/pl.po +++ b/po/pl.po @@ -3,12 +3,14 @@ # Polish translation for systemd. # # Piotr Drąg , 2023, 2024, 2025. +# Marek Adamski , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-07-24 14:54+0000\n" -"Last-Translator: Piotr Drąg \n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-04-03 19:58+0000\n" +"Last-Translator: Marek Adamski " +"\n" "Language-Team: Polish \n" "Language: pl\n" @@ -17,7 +19,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 5.12.2\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1052,9 +1054,12 @@ msgid "DHCP server sends force renew message" msgstr "Serwer DHCP wysyła komunikat wymuszonego odnowienia" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" -"Wymagane jest uwierzytelnienie, aby wysłać komunikat wymuszonego odnowienia." +"Wymagane jest uwierzytelnienie, aby wysłać komunikat wymuszonego odnowienia z" +" serwera DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1098,11 +1103,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Zarządzanie łączami sieciowymi" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "Wymagane jest uwierzytelnienie, aby zarządzać łączami sieciowymi." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" diff --git a/po/pt.po b/po/pt.po index 1ed4c425cdcfb..8d6e42865e353 100644 --- a/po/pt.po +++ b/po/pt.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2026-02-26 13:58+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-05 22:10+0000\n" "Last-Translator: Américo Monteiro \n" "Language-Team: Portuguese \n" @@ -1047,8 +1047,12 @@ msgid "DHCP server sends force renew message" msgstr "Servidor DHCP envia mensagem de renovação forçada" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "É necessária autenticação para enviar mensagem de renovação forçada." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"É necessária autenticação para enviar uma mensagem de renovação forçada a " +"partir do servidor DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" diff --git a/po/pt_BR.po b/po/pt_BR.po index fdb791df1d9cd..4fba725bb92e5 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -3,17 +3,18 @@ # Brazilian Portuguese translation for systemd. # Enrico Nicoletto , 2014. # Filipe Brandenburger , 2018. -# Rafael Fontenelle , 2015-2020, 2025. +# Rafael Fontenelle , 2015-2020, 2025, 2026. # Gustavo Costa , 2021. # Tiago Rocha Cunha , 2024. # Gabriel Elyas , 2024. # Fábio Rodrigues Ribeiro , 2024. # "Geraldo S. Simião Kutz" , 2024. +# Weblate Translation Memory , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-10-29 18:54+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-04-22 10:58+0000\n" "Last-Translator: Rafael Fontenelle \n" "Language-Team: Portuguese (Brazil) \n" @@ -22,7 +23,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 5.13.3\n" +"X-Generator: Weblate 5.17\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1051,8 +1052,12 @@ msgid "DHCP server sends force renew message" msgstr "Servidor DHCP envia mensagem de renovação forçada" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "É necessária autenticação para enviar mensagem de renovação forçada." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"É necessária autenticação para enviar uma mensagem de renovação forçada a " +"partir do servidor DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1094,11 +1099,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Gerenciar conexões de rede" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "A autenticação é necessária para gerenciar conexões de rede." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" diff --git a/po/ro.po b/po/ro.po index 552dca7e3f239..876dc27773b09 100644 --- a/po/ro.po +++ b/po/ro.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2021-01-12 17:36+0000\n" "Last-Translator: Vlad \n" "Language-Team: Romanian , 2022. # Andrei Stepanov , 2023. # "Sergey A." , 2023, 2024. -# "Sergey A." , 2024, 2025. +# "Sergey A." , 2024, 2025, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-09-03 09:14+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-12 13:58+0000\n" "Last-Translator: \"Sergey A.\" \n" "Language-Team: Russian \n" @@ -24,7 +24,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 5.13\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1109,10 +1109,12 @@ msgid "DHCP server sends force renew message" msgstr "Сервер DHCP посылает сообщение о принудительном обновлении" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" -"Чтобы отправить сообщение о принудительном обновлении, необходимо пройти " -"аутентификацию." +"Чтобы отправить сообщение от сервера DHCP о принудительном обновлении, " +"необходимо пройти аутентификацию." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1128,9 +1130,7 @@ msgstr "Перечитать настройки сети" #: src/network/org.freedesktop.network1.policy:166 msgid "Authentication is required to reload network settings." -msgstr "" -"Чтобы заставить systemd перечитать настройки сети, необходимо пройти " -"аутентификацию." +msgstr "Чтобы перечитать настройки сети, необходимо пройти аутентификацию." #: src/network/org.freedesktop.network1.policy:176 msgid "Reconfigure network interface" @@ -1158,13 +1158,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Управление сетевыми ссылками" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" -"Чтобы заставить systemd перечитать настройки сети, необходимо пройти " -"аутентификацию." +msgstr "Для управления сетевыми ссылками, необходимо пройти аутентификацию." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" diff --git a/po/si.po b/po/si.po index f9279183656ac..fe767e7433549 100644 --- a/po/si.po +++ b/po/si.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2021-08-19 07:04+0000\n" "Last-Translator: Hela Basa \n" "Language-Team: Sinhala \n" "Language-Team: Slovak , 2024, 2025. +# Martin Srebotnjak , 2024, 2025, 2026. # Weblate Translation Memory , 2024. msgid "" msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-12-09 15:58+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-08 23:58+0000\n" "Last-Translator: Martin Srebotnjak \n" "Language-Team: Slovenian \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || " "n%100==4 ? 2 : 3;\n" -"X-Generator: Weblate 5.14.3\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1067,10 +1067,12 @@ msgid "DHCP server sends force renew message" msgstr "Strežnik DHCP pošlje sporočilo o prisilnem podaljšanju" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" "Preverjanje pristnosti je potrebno za pošiljanje sporočila o prisilnem " -"podaljšanju." +"podaljšanju s strežnika DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1113,7 +1115,7 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Upravljaj omrežne povezave" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." diff --git a/po/sr.po b/po/sr.po index 770b0c192d500..7ae48d423dba7 100644 --- a/po/sr.po +++ b/po/sr.po @@ -2,12 +2,15 @@ # # Serbian translation of systemd. # Frantisek Sumsal , 2021. +# Марко Костић , 2026 +# msgid "" msgstr "" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2021-02-23 22:40+0000\n" -"Last-Translator: Frantisek Sumsal \n" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://github.com/systemd/systemd/issues\n" +"POT-Creation-Date: 2026-03-20 14:34+0000\n" +"PO-Revision-Date: 2026-04-12 18:20+0200\n" +"Last-Translator: Марко Костић \n" "Language-Team: Serbian \n" "Language: sr\n" @@ -16,7 +19,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 4.4.2\n" +"X-Generator: Poedit 3.9\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -71,259 +74,100 @@ msgstr "" #: src/core/org.freedesktop.systemd1.policy.in:74 msgid "Dump the systemd state without rate limits" -msgstr "" +msgstr "Испиши стање системд-а без ограничења протока" #: src/core/org.freedesktop.systemd1.policy.in:75 -#, fuzzy msgid "" "Authentication is required to dump the systemd state without rate limits." msgstr "" -"Потребно је да се идентификујете да бисте поново учитали стање систем-деа." +"Потребно је потврђивање идентитета за исписивање стања системде-а без " +"ограничења протока." #: src/home/org.freedesktop.home1.policy:13 msgid "Create a home area" -msgstr "" +msgstr "Направи лични простор" #: src/home/org.freedesktop.home1.policy:14 -#, fuzzy msgid "Authentication is required to create a user's home area." msgstr "" -"Потребно је да се идентификујете да бисте поново учитали стање систем-деа." +"Потребно је потврђивање идентитета за прављење личног простора корисника." #: src/home/org.freedesktop.home1.policy:23 msgid "Remove a home area" -msgstr "" +msgstr "Уклони лични простор" #: src/home/org.freedesktop.home1.policy:24 -#, fuzzy msgid "Authentication is required to remove a user's home area." msgstr "" -"Потребно је да се идентификујете да бисте поново учитали стање систем-деа." +"Потребно је потврђивање идентитета за уклањање личног простора корисника." #: src/home/org.freedesktop.home1.policy:33 msgid "Check credentials of a home area" -msgstr "" +msgstr "Провери акредитиве личног простора" #: src/home/org.freedesktop.home1.policy:34 -#, fuzzy msgid "" "Authentication is required to check credentials against a user's home area." msgstr "" -"Потребно је да се идентификујете да бисте управљали покренутим сесијама, " -"корисницима и седиштима." +"Потребно је потврђивање идентитета за проверу акредитива личног простора " +"корисника." #: src/home/org.freedesktop.home1.policy:43 msgid "Update a home area" -msgstr "" +msgstr "Ажурирај лични простор" #: src/home/org.freedesktop.home1.policy:44 -#, fuzzy msgid "Authentication is required to update a user's home area." -msgstr "Потребно је да се идентификујете да бисте закачили уређај на седиште." +msgstr "" +"Потребно је потврђивање идентитета за ажурирање личног простора корисника." #: src/home/org.freedesktop.home1.policy:53 msgid "Update your home area" -msgstr "" +msgstr "Ажурирај свој лични простор" #: src/home/org.freedesktop.home1.policy:54 -#, fuzzy msgid "Authentication is required to update your home area." -msgstr "Потребно је да се идентификујете да бисте закачили уређај на седиште." +msgstr "Потребно је потврђивање идентитета за ажурирање свог личног простора." #: src/home/org.freedesktop.home1.policy:63 msgid "Resize a home area" -msgstr "" +msgstr "Промени величину личног простора" #: src/home/org.freedesktop.home1.policy:64 -#, fuzzy msgid "Authentication is required to resize a user's home area." -msgstr "Потребно је да се идентификујете да бисте поставили зидну поруку" +msgstr "" +"Потребно је потврђивање идентитета за промену величине личног простора " +"корисника." #: src/home/org.freedesktop.home1.policy:73 msgid "Change password of a home area" -msgstr "" +msgstr "Промени лозинку личног простора" #: src/home/org.freedesktop.home1.policy:74 -#, fuzzy msgid "" "Authentication is required to change the password of a user's home area." msgstr "" -"Потребно је да се идентификујете да бисте управљали покренутим сесијама, " -"корисницима и седиштима." +"Потребно је потврђивање идентитета за промену лозинке личног простора " +"корисника." #: src/home/org.freedesktop.home1.policy:83 msgid "Activate a home area" -msgstr "" +msgstr "Активирај лични простор" #: src/home/org.freedesktop.home1.policy:84 -#, fuzzy msgid "Authentication is required to activate a user's home area." msgstr "" -"Потребно је да се идентификујете да бисте поново учитали стање систем-деа." +"Потребно је потврђивање идентитета за активирање личног простора корисника." #: src/home/org.freedesktop.home1.policy:93 msgid "Manage Home Directory Signing Keys" -msgstr "" +msgstr "Управљај кључевима за потписивање личног директоријума" #: src/home/org.freedesktop.home1.policy:94 -#, fuzzy msgid "Authentication is required to manage signing keys for home directories." msgstr "" -"Потребно је да се идентификујете да бисте управљали системским услугама или " -"другим јединицама." - -#: src/home/pam_systemd_home.c:330 -#, c-format -msgid "" -"Home of user %s is currently absent, please plug in the necessary storage " -"device or backing file system." -msgstr "" - -#: src/home/pam_systemd_home.c:335 -#, c-format -msgid "Too frequent login attempts for user %s, try again later." -msgstr "" - -#: src/home/pam_systemd_home.c:347 -msgid "Password: " -msgstr "" - -#: src/home/pam_systemd_home.c:349 -#, c-format -msgid "Password incorrect or not sufficient for authentication of user %s." -msgstr "" - -#: src/home/pam_systemd_home.c:350 -msgid "Sorry, try again: " -msgstr "" - -#: src/home/pam_systemd_home.c:372 -msgid "Recovery key: " -msgstr "" - -#: src/home/pam_systemd_home.c:374 -#, c-format -msgid "" -"Password/recovery key incorrect or not sufficient for authentication of user " -"%s." -msgstr "" - -#: src/home/pam_systemd_home.c:375 -msgid "Sorry, reenter recovery key: " -msgstr "" - -#: src/home/pam_systemd_home.c:395 -#, c-format -msgid "Security token of user %s not inserted." -msgstr "" - -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 -msgid "Try again with password: " -msgstr "" - -#: src/home/pam_systemd_home.c:398 -#, c-format -msgid "" -"Password incorrect or not sufficient, and configured security token of user " -"%s not inserted." -msgstr "" - -#: src/home/pam_systemd_home.c:418 -msgid "Security token PIN: " -msgstr "" - -#: src/home/pam_systemd_home.c:435 -#, c-format -msgid "Please authenticate physically on security token of user %s." -msgstr "" - -#: src/home/pam_systemd_home.c:446 -#, c-format -msgid "Please confirm presence on security token of user %s." -msgstr "" - -#: src/home/pam_systemd_home.c:457 -#, c-format -msgid "Please verify user on security token of user %s." -msgstr "" - -#: src/home/pam_systemd_home.c:466 -msgid "" -"Security token PIN is locked, please unlock it first. (Hint: Removal and re-" -"insertion might suffice.)" -msgstr "" - -#: src/home/pam_systemd_home.c:474 -#, c-format -msgid "Security token PIN incorrect for user %s." -msgstr "" - -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 -msgid "Sorry, retry security token PIN: " -msgstr "" - -#: src/home/pam_systemd_home.c:493 -#, c-format -msgid "Security token PIN of user %s incorrect (only a few tries left!)" -msgstr "" - -#: src/home/pam_systemd_home.c:512 -#, c-format -msgid "Security token PIN of user %s incorrect (only one try left!)" -msgstr "" - -#: src/home/pam_systemd_home.c:679 -#, c-format -msgid "Home of user %s is currently not active, please log in locally first." -msgstr "" - -#: src/home/pam_systemd_home.c:681 -#, c-format -msgid "Home of user %s is currently locked, please unlock locally first." -msgstr "" - -#: src/home/pam_systemd_home.c:715 -#, c-format -msgid "Too many unsuccessful login attempts for user %s, refusing." -msgstr "" - -#: src/home/pam_systemd_home.c:1012 -msgid "User record is blocked, prohibiting access." -msgstr "" - -#: src/home/pam_systemd_home.c:1016 -msgid "User record is not valid yet, prohibiting access." -msgstr "" - -#: src/home/pam_systemd_home.c:1020 -msgid "User record is not valid anymore, prohibiting access." -msgstr "" - -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 -msgid "User record not valid, prohibiting access." -msgstr "" - -#: src/home/pam_systemd_home.c:1035 -#, c-format -msgid "Too many logins, try again in %s." -msgstr "" - -#: src/home/pam_systemd_home.c:1046 -msgid "Password change required." -msgstr "" - -#: src/home/pam_systemd_home.c:1050 -msgid "Password expired, change required." -msgstr "" - -#: src/home/pam_systemd_home.c:1056 -msgid "Password is expired, but can't change, refusing login." -msgstr "" - -#: src/home/pam_systemd_home.c:1060 -msgid "Password will expire soon, please change." -msgstr "" +"Потребно је потврђивање идентитета за управљање кључевима за потписивање " +"личних директоријума." #: src/hostname/org.freedesktop.hostname1.policy:20 msgid "Set hostname" @@ -357,80 +201,63 @@ msgstr "" #: src/hostname/org.freedesktop.hostname1.policy:51 msgid "Get product UUID" -msgstr "" +msgstr "Добави УУИД производа" #: src/hostname/org.freedesktop.hostname1.policy:52 -#, fuzzy msgid "Authentication is required to get product UUID." -msgstr "Потребно је да се идентификујете да бисте поново учитали „$(unit)“." +msgstr "Потребно је потврђивање идентитета за добављање УУИД-а производа." #: src/hostname/org.freedesktop.hostname1.policy:61 msgid "Get hardware serial number" -msgstr "" +msgstr "Добави серијски број хардвера" #: src/hostname/org.freedesktop.hostname1.policy:62 -#, fuzzy msgid "Authentication is required to get hardware serial number." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "" +"Потребно је потврђивање идентитета за добављање серијског броја хардвера." #: src/hostname/org.freedesktop.hostname1.policy:71 -#, fuzzy msgid "Get system description" -msgstr "Постави системску временску зону" +msgstr "Добави опис система" #: src/hostname/org.freedesktop.hostname1.policy:72 -#, fuzzy msgid "Authentication is required to get system description." -msgstr "" -"Потребно је да се идентификујете да бисте поставили системску временску зону." +msgstr "Потребно је потврђивање идентитета за добављање описа система." #: src/import/org.freedesktop.import1.policy:22 -#, fuzzy msgid "Import a disk image" -msgstr "Увези ВМ или слику контејнера" +msgstr "Увези одраз диска" #: src/import/org.freedesktop.import1.policy:23 -#, fuzzy msgid "Authentication is required to import an image." -msgstr "" -"Потребно је да се идентификујете да бисте увезли виртуелну машину или слику " -"контејнера" +msgstr "Потребно је потврђивање идентитета за увоз одраза." #: src/import/org.freedesktop.import1.policy:32 -#, fuzzy msgid "Export a disk image" -msgstr "Извези ВМ или слику контејнера" +msgstr "Извези одраз диска" #: src/import/org.freedesktop.import1.policy:33 -#, fuzzy msgid "Authentication is required to export disk image." -msgstr "" -"Потребно је да се идентификујете да бисте извезли виртуелну машину или слику " -"контејнера" +msgstr "Потребно је потврђивање идентитета за извоз одраза диска." #: src/import/org.freedesktop.import1.policy:42 -#, fuzzy msgid "Download a disk image" -msgstr "Преузми ВМ или слику контејнера" +msgstr "Преузми одраз диска" #: src/import/org.freedesktop.import1.policy:43 -#, fuzzy msgid "Authentication is required to download a disk image." -msgstr "" -"Потребно је да се идентификујете да бисте преузели виртуелну машину или " -"слику контејнера" +msgstr "Потребно је потврђивање идентитета за преузимање одраза диска." #: src/import/org.freedesktop.import1.policy:52 msgid "Cancel transfer of a disk image" -msgstr "" +msgstr "Откажи пренос одраза диска" #: src/import/org.freedesktop.import1.policy:53 -#, fuzzy msgid "" "Authentication is required to cancel the ongoing transfer of a disk image." msgstr "" -"Потребно је да се идентификујете да бисте управљали покренутим сесијама, " -"корисницима и седиштима." +"Потребно је потврђивање идентитета за отказивање текућег преноса одраза " +"диска." #: src/locale/org.freedesktop.locale1.policy:22 msgid "Set system locale" @@ -593,7 +420,7 @@ msgstr "Дозволи качење уређаја на седишта" #: src/login/org.freedesktop.login1.policy:149 msgid "Authentication is required to attach a device to a seat." -msgstr "Потребно је да се идентификујете да бисте закачили уређај на седиште." +msgstr "Потребно је да се идентификујете да бисте закачили уређај на седиште." #: src/login/org.freedesktop.login1.policy:159 msgid "Flush device to seat attachments" @@ -787,18 +614,17 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:352 msgid "Set the reboot \"reason\" in the kernel" -msgstr "" +msgstr "Постави „разлог“ поновног покретања у језгру" #: src/login/org.freedesktop.login1.policy:353 msgid "Authentication is required to set the reboot \"reason\" in the kernel." msgstr "" "Потребно је да се идентификујете да бисте поставили \"разлог\" за поновно " -"поретање унутар језгра." +"покретање унутар језгра." #: src/login/org.freedesktop.login1.policy:363 -#, fuzzy msgid "Indicate to the firmware to boot to setup interface" -msgstr "Напомени фирмверу да се подигне у режиму подешавања интерфејса" +msgstr "Укажи фирмверу да покрене систем у интерфејсу за подешавање" #: src/login/org.freedesktop.login1.policy:364 msgid "" @@ -811,46 +637,43 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:374 msgid "Indicate to the boot loader to boot to the boot loader menu" msgstr "" +"Укажи учитавачу подизања система да прикаже изборник за подизање система" #: src/login/org.freedesktop.login1.policy:375 -#, fuzzy msgid "" "Authentication is required to indicate to the boot loader to boot to the " "boot loader menu." msgstr "" -"Потребно је да се идентификујете да бисте напоменули фирмверу да се подигне " -"у режиму подешавања интерфејса." +"Потребно је потврђивање идентитета за указивање учитавачу подизања система " +"да прикаже изборник за подизање система." #: src/login/org.freedesktop.login1.policy:385 msgid "Indicate to the boot loader to boot a specific entry" -msgstr "" +msgstr "Укажи учитавачу подизања система да покрене одређени унос" #: src/login/org.freedesktop.login1.policy:386 -#, fuzzy msgid "" "Authentication is required to indicate to the boot loader to boot into a " "specific boot loader entry." msgstr "" -"Потребно је да се идентификујете да бисте напоменули фирмверу да се подигне " -"у режиму подешавања интерфејса." +"Потребно је потврђивање идентитета за указивање учитавачу подизања система " +"да покрене одређени унос учитавача подизања система." #: src/login/org.freedesktop.login1.policy:396 msgid "Set a wall message" msgstr "Постави зидну поруку" #: src/login/org.freedesktop.login1.policy:397 -#, fuzzy msgid "Authentication is required to set a wall message." -msgstr "Потребно је да се идентификујете да бисте поставили зидну поруку" +msgstr "Потребно је потврђивање идентитета за постављање зидне поруке." #: src/login/org.freedesktop.login1.policy:406 msgid "Change Session" -msgstr "" +msgstr "Промени сесију" #: src/login/org.freedesktop.login1.policy:407 -#, fuzzy msgid "Authentication is required to change the virtual terminal." -msgstr "Потребно је да се идентификујете да бисте зауставили систем." +msgstr "Потребно је потврђивање идентитета за промену виртуелног терминала." #: src/machine/org.freedesktop.machine1.policy:22 msgid "Log into a local container" @@ -923,30 +746,26 @@ msgstr "" "машинама и контејнерима." #: src/machine/org.freedesktop.machine1.policy:95 -#, fuzzy msgid "Create a local virtual machine or container" -msgstr "Управљај локалним виртуелним машинама и контејнерима" +msgstr "Направи локалну виртуелну машину или контејнер" #: src/machine/org.freedesktop.machine1.policy:96 -#, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" -"Потребно је да се идентификујете да бисте управљали локалним виртуелним " -"машинама и контејнерима." +"Потребно је потврђивање идентитета за прављење локалне виртуелне машине или " +"контејнера." #: src/machine/org.freedesktop.machine1.policy:106 -#, fuzzy msgid "Register a local virtual machine or container" -msgstr "Управљај локалним виртуелним машинама и контејнерима" +msgstr "Региструј локалну виртуелну машину или контејнер" #: src/machine/org.freedesktop.machine1.policy:107 -#, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" -"Потребно је да се идентификујете да бисте управљали локалним виртуелним " -"машинама и контејнерима." +"Потребно је потврђивање идентитета за регистровање локалне виртуелне машине " +"или контејнера." #: src/machine/org.freedesktop.machine1.policy:116 msgid "Manage local virtual machine and container images" @@ -962,344 +781,324 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" -msgstr "" +msgstr "Постави НТП сервере" #: src/network/org.freedesktop.network1.policy:23 -#, fuzzy msgid "Authentication is required to set NTP servers." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за постављање НТП сервера." #: src/network/org.freedesktop.network1.policy:33 #: src/resolve/org.freedesktop.resolve1.policy:44 -#, fuzzy msgid "Set DNS servers" -msgstr "Региструј DNS-SD услугу" +msgstr "Постави ДНС сервере" #: src/network/org.freedesktop.network1.policy:34 #: src/resolve/org.freedesktop.resolve1.policy:45 -#, fuzzy msgid "Authentication is required to set DNS servers." -msgstr "Потребно је да се идентификујете да бисте регистровали DNS-SD услугу" +msgstr "Потребно је потврђивање идентитета за постављање ДНС сервера." #: src/network/org.freedesktop.network1.policy:44 #: src/resolve/org.freedesktop.resolve1.policy:55 msgid "Set domains" -msgstr "" +msgstr "Постави домене" #: src/network/org.freedesktop.network1.policy:45 #: src/resolve/org.freedesktop.resolve1.policy:56 -#, fuzzy msgid "Authentication is required to set domains." -msgstr "Потребно је да се идентификујете да бисте зауставили „$(unit)“." +msgstr "Потребно је потврђивање идентитета за постављање домена." #: src/network/org.freedesktop.network1.policy:55 #: src/resolve/org.freedesktop.resolve1.policy:66 msgid "Set default route" -msgstr "" +msgstr "Постави подразумевану руту" #: src/network/org.freedesktop.network1.policy:56 #: src/resolve/org.freedesktop.resolve1.policy:67 -#, fuzzy msgid "Authentication is required to set default route." -msgstr "Потребно је да се идентификујете да бисте поставили назив машине." +msgstr "Потребно је потврђивање идентитета за постављање подразумеване руте." #: src/network/org.freedesktop.network1.policy:66 #: src/resolve/org.freedesktop.resolve1.policy:77 msgid "Enable/disable LLMNR" -msgstr "" +msgstr "Омогући/онемогући ЛЛМНР" #: src/network/org.freedesktop.network1.policy:67 #: src/resolve/org.freedesktop.resolve1.policy:78 -#, fuzzy msgid "Authentication is required to enable or disable LLMNR." -msgstr "Потребно је да се идентификујете да бисте успавали систем." +msgstr "" +"Потребно је потврђивање идентитета за омогућавање или онемогућавање ЛЛМНР-а." #: src/network/org.freedesktop.network1.policy:77 #: src/resolve/org.freedesktop.resolve1.policy:88 msgid "Enable/disable multicast DNS" -msgstr "" +msgstr "Омогући/онемогући мултикаст ДНС" #: src/network/org.freedesktop.network1.policy:78 #: src/resolve/org.freedesktop.resolve1.policy:89 -#, fuzzy msgid "Authentication is required to enable or disable multicast DNS." msgstr "" -"Потребно је да се идентификујете да бисте се пријавили у локалног домаћина." +"Потребно је потврђивање идентитета за омогућавање или онемогућавање " +"мултикаст ДНС-а." #: src/network/org.freedesktop.network1.policy:88 #: src/resolve/org.freedesktop.resolve1.policy:99 msgid "Enable/disable DNS over TLS" -msgstr "" +msgstr "Омогући/онемогући ДНС преко ТЛС-а" #: src/network/org.freedesktop.network1.policy:89 #: src/resolve/org.freedesktop.resolve1.policy:100 -#, fuzzy msgid "Authentication is required to enable or disable DNS over TLS." -msgstr "Потребно је да се идентификујете да бисте регистровали DNS-SD услугу" +msgstr "" +"Потребно је потврђивање идентитета за омогућавање или онемогућавање ДНС-а " +"преко ТЛС-а." #: src/network/org.freedesktop.network1.policy:99 #: src/resolve/org.freedesktop.resolve1.policy:110 msgid "Enable/disable DNSSEC" -msgstr "" +msgstr "Омогући/онемогући DNSSEC" #: src/network/org.freedesktop.network1.policy:100 #: src/resolve/org.freedesktop.resolve1.policy:111 -#, fuzzy msgid "Authentication is required to enable or disable DNSSEC." -msgstr "Потребно је да се идентификујете да бисте успавали систем." +msgstr "" +"Потребно је потврђивање идентитета за омогућавање или онемогућавање DNSSEC-" +"а." #: src/network/org.freedesktop.network1.policy:110 #: src/resolve/org.freedesktop.resolve1.policy:121 msgid "Set DNSSEC Negative Trust Anchors" -msgstr "" +msgstr "Постави негативне сидре поверења за DNSSEC" #: src/network/org.freedesktop.network1.policy:111 #: src/resolve/org.freedesktop.resolve1.policy:122 -#, fuzzy msgid "Authentication is required to set DNSSEC Negative Trust Anchors." msgstr "" -"Потребно је да се идентификујете да бисте поставили основни језик система." +"Потребно је потврђивање идентитета за постављање негативних сидара поверења " +"за DNSSEC." #: src/network/org.freedesktop.network1.policy:121 msgid "Revert NTP settings" -msgstr "" +msgstr "Врати НТП подешавања" #: src/network/org.freedesktop.network1.policy:122 -#, fuzzy msgid "Authentication is required to reset NTP settings." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за ресетовање НТП подешавања." #: src/network/org.freedesktop.network1.policy:132 msgid "Revert DNS settings" -msgstr "" +msgstr "Врати ДНС подешавања" #: src/network/org.freedesktop.network1.policy:133 -#, fuzzy msgid "Authentication is required to reset DNS settings." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за ресетовање ДНС подешавања." #: src/network/org.freedesktop.network1.policy:143 msgid "DHCP server sends force renew message" -msgstr "" +msgstr "ДХЦП сервер шаље поруку за присилно обнављање" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -msgid "Authentication is required to send force renew message." -msgstr "Потребно је да се идентификујете да бисте поставили зидну поруку" +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"Потребно је потврђивање идентитета за слање поруке за присилно обнављање са " +"ДХЦП сервера." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" -msgstr "" +msgstr "Обнови динамичке адресе" #: src/network/org.freedesktop.network1.policy:155 -#, fuzzy msgid "Authentication is required to renew dynamic addresses." -msgstr "Потребно је да се идентификујете да бисте поставили зидну поруку" +msgstr "Потребно је потврђивање идентитета за обнављање динамичких адреса." #: src/network/org.freedesktop.network1.policy:165 msgid "Reload network settings" -msgstr "" +msgstr "Поново учитај мрежна подешавања" #: src/network/org.freedesktop.network1.policy:166 -#, fuzzy msgid "Authentication is required to reload network settings." msgstr "" -"Потребно је да се идентификујете да бисте поново учитали стање систем-деа." +"Потребно је потврђивање идентитета за поновно учитавање мрежних подешавања." #: src/network/org.freedesktop.network1.policy:176 msgid "Reconfigure network interface" -msgstr "" +msgstr "Поново подеси мрежни интерфејс" #: src/network/org.freedesktop.network1.policy:177 -#, fuzzy msgid "Authentication is required to reconfigure network interface." -msgstr "Потребно је да се идентификујете да бисте поново покренули систем." +msgstr "" +"Потребно је потврђивање идентитета за поновно подешавање мрежног интерфејса." #: src/network/org.freedesktop.network1.policy:187 msgid "Specify whether persistent storage for systemd-networkd is available" -msgstr "" +msgstr "Наведите да ли је доступно трајно складиште за systemd-networkd" #: src/network/org.freedesktop.network1.policy:188 msgid "" "Authentication is required to specify whether persistent storage for systemd-" "networkd is available." msgstr "" +"Потребно је потврђивање идентитета за навођење да ли је доступно трајно " +"складиште за systemd-networkd." #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Управљај мрежним везама" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" -"Потребно је да се идентификујете да бисте поново учитали стање систем-деа." +msgstr "Потребно је да се идентификујете да бисте управљали мрежним везама." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" -msgstr "" +msgstr "Прегледај одраз преносне услуге" #: src/portable/org.freedesktop.portable1.policy:14 -#, fuzzy msgid "Authentication is required to inspect a portable service image." -msgstr "" -"Потребно је да се идентификујете да бисте увезли виртуелну машину или слику " -"контејнера" +msgstr "Потребно је потврђивање идентитета за преглед одраза преносне услуге." #: src/portable/org.freedesktop.portable1.policy:23 msgid "Attach or detach a portable service image" -msgstr "" +msgstr "Прикачи или откачи одраз преносне услуге" #: src/portable/org.freedesktop.portable1.policy:24 -#, fuzzy msgid "" "Authentication is required to attach or detach a portable service image." -msgstr "Потребно је да се идентификујете да бисте закачили уређај на седиште." +msgstr "" +"Потребно је потврђивање идентитета за прикачивање или откачивање одраза " +"преносне услуге." #: src/portable/org.freedesktop.portable1.policy:34 msgid "Delete or modify portable service image" -msgstr "" +msgstr "Обриши или измени одраз преносне услуге" #: src/portable/org.freedesktop.portable1.policy:35 -#, fuzzy msgid "" "Authentication is required to delete or modify a portable service image." msgstr "" -"Потребно је да се идентификујете да бисте преузели виртуелну машину или " -"слику контејнера" +"Потребно је потврђивање идентитета за брисање или измену одраза преносне " +"услуге." #: src/resolve/org.freedesktop.resolve1.policy:22 msgid "Register a DNS-SD service" msgstr "Региструј DNS-SD услугу" #: src/resolve/org.freedesktop.resolve1.policy:23 -#, fuzzy msgid "Authentication is required to register a DNS-SD service." -msgstr "Потребно је да се идентификујете да бисте регистровали DNS-SD услугу" +msgstr "Потребно је потврђивање идентитета за регистрацију DNS-SD услуге." #: src/resolve/org.freedesktop.resolve1.policy:33 msgid "Unregister a DNS-SD service" msgstr "Укини регистрацију DNS-SD услуге" #: src/resolve/org.freedesktop.resolve1.policy:34 -#, fuzzy msgid "Authentication is required to unregister a DNS-SD service." msgstr "" -"Потребно је да се идентификујете да бисте укинули регистрацију DNS-SD услуге" +"Потребно је потврђивање идентитета за поништавање регистрације DNS-SD услуге." #: src/resolve/org.freedesktop.resolve1.policy:132 msgid "Revert name resolution settings" -msgstr "" +msgstr "Врати подешавања разрешавања назива" #: src/resolve/org.freedesktop.resolve1.policy:133 -#, fuzzy msgid "Authentication is required to reset name resolution settings." msgstr "" -"Потребно је да се идентификујете да бисте поставили подешавања системске " -"тастатуре." +"Потребно је потврђивање идентитета за ресетовање подешавања разрешавања " +"назива." #: src/resolve/org.freedesktop.resolve1.policy:143 msgid "Subscribe query results" -msgstr "" +msgstr "Претплати се на резултате упита" #: src/resolve/org.freedesktop.resolve1.policy:144 -#, fuzzy msgid "Authentication is required to subscribe query results." -msgstr "Потребно је да се идентификујете да бисте обуставили систем." +msgstr "Потребно је потврђивање идентитета за претплату на резултате упита." #: src/resolve/org.freedesktop.resolve1.policy:154 msgid "Subscribe to DNS configuration" -msgstr "" +msgstr "Претплати се на ДНС подешавања" #: src/resolve/org.freedesktop.resolve1.policy:155 -#, fuzzy msgid "Authentication is required to subscribe to DNS configuration." -msgstr "Потребно је да се идентификујете да бисте обуставили систем." +msgstr "Потребно је потврђивање идентитета за претплату на ДНС подешавања." #: src/resolve/org.freedesktop.resolve1.policy:165 msgid "Dump cache" -msgstr "" +msgstr "Испиши кеш" #: src/resolve/org.freedesktop.resolve1.policy:166 -#, fuzzy msgid "Authentication is required to dump cache." -msgstr "Потребно је да се идентификујете да бисте зауставили „$(unit)“." +msgstr "Потребно је потврђивање идентитета за исписивање кеша." #: src/resolve/org.freedesktop.resolve1.policy:176 msgid "Dump server state" -msgstr "" +msgstr "Испиши стање сервера" #: src/resolve/org.freedesktop.resolve1.policy:177 -#, fuzzy msgid "Authentication is required to dump server state." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за исписивање стања сервера." #: src/resolve/org.freedesktop.resolve1.policy:187 msgid "Dump statistics" -msgstr "" +msgstr "Испиши статистику" #: src/resolve/org.freedesktop.resolve1.policy:188 -#, fuzzy msgid "Authentication is required to dump statistics." -msgstr "Потребно је да се идентификујете да бисте зауставили „$(unit)“." +msgstr "Потребно је потврђивање идентитета за исписивање статистике." #: src/resolve/org.freedesktop.resolve1.policy:198 msgid "Reset statistics" -msgstr "" +msgstr "Ресетуј статистику" #: src/resolve/org.freedesktop.resolve1.policy:199 -#, fuzzy msgid "Authentication is required to reset statistics." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за ресетовање статистике." #: src/sysupdate/org.freedesktop.sysupdate1.policy:35 msgid "Check for system updates" -msgstr "" +msgstr "Провери ажурирања система" #: src/sysupdate/org.freedesktop.sysupdate1.policy:36 -#, fuzzy msgid "Authentication is required to check for system updates." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за проверу ажурирања система." #: src/sysupdate/org.freedesktop.sysupdate1.policy:45 msgid "Install system updates" -msgstr "" +msgstr "Инсталирај ажурирања система" #: src/sysupdate/org.freedesktop.sysupdate1.policy:46 -#, fuzzy msgid "Authentication is required to install system updates." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за инсталирање ажурирања система." #: src/sysupdate/org.freedesktop.sysupdate1.policy:55 msgid "Install specific system version" -msgstr "" +msgstr "Инсталирај одређено издање система" #: src/sysupdate/org.freedesktop.sysupdate1.policy:56 -#, fuzzy msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." msgstr "" -"Потребно је да се идентификујете да бисте поставили системску временску зону." +"Потребно је потврђивање идентитета за ажурирање система на одређено (могуће " +"старо) издање." #: src/sysupdate/org.freedesktop.sysupdate1.policy:65 msgid "Cleanup old system updates" -msgstr "" +msgstr "Очисти стара ажурирања система" #: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy msgid "Authentication is required to cleanup old system updates." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "" +"Потребно је потврђивање идентитета за чишћење старих ажурирања система." #: src/sysupdate/org.freedesktop.sysupdate1.policy:75 msgid "Manage optional features" -msgstr "" +msgstr "Управљај опционим могућностима" #: src/sysupdate/org.freedesktop.sysupdate1.policy:76 -#, fuzzy msgid "Authentication is required to manage optional features." -msgstr "" -"Потребно је да се идентификујете да бисте управљали покренутим сесијама, " -"корисницима и седиштима." +msgstr "Потребно је потврђивање идентитета за управљање опционим могућностима." #: src/timedate/org.freedesktop.timedate1.policy:22 msgid "Set system time" @@ -1342,83 +1141,3 @@ msgid "" msgstr "" "Потребно је да се идентификујете да бисте подесили да ли се време усклађује " "са мреже." - -#: src/core/dbus-unit.c:372 -msgid "Authentication is required to start '$(unit)'." -msgstr "Потребно је да се идентификујете да бисте покренули „$(unit)“." - -#: src/core/dbus-unit.c:373 -msgid "Authentication is required to stop '$(unit)'." -msgstr "Потребно је да се идентификујете да бисте зауставили „$(unit)“." - -#: src/core/dbus-unit.c:374 -msgid "Authentication is required to reload '$(unit)'." -msgstr "Потребно је да се идентификујете да бисте поново учитали „$(unit)“." - -#: src/core/dbus-unit.c:375 src/core/dbus-unit.c:376 -msgid "Authentication is required to restart '$(unit)'." -msgstr "Потребно је да се идентификујете да бисте поново покренули „$(unit)“." - -#: src/core/dbus-unit.c:568 -#, fuzzy -msgid "" -"Authentication is required to send a UNIX signal to the processes of '$" -"(unit)'." -msgstr "" -"Потребно је да се идентификујете да бисте поставили својства за „$(unit)“." - -#: src/core/dbus-unit.c:621 -#, fuzzy -msgid "" -"Authentication is required to send a UNIX signal to the processes of " -"subgroup of '$(unit)'." -msgstr "" -"Потребно је да се идентификујете да бисте поставили својства за „$(unit)“." - -#: src/core/dbus-unit.c:649 -msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." -msgstr "" -"Потребно је да се идентификујете да бисте поново поставили „неуспешно“ стање " -"за „$(unit)“." - -#: src/core/dbus-unit.c:679 -msgid "Authentication is required to set properties on '$(unit)'." -msgstr "" -"Потребно је да се идентификујете да бисте поставили својства за „$(unit)“." - -#: src/core/dbus-unit.c:776 -#, fuzzy -msgid "" -"Authentication is required to delete files and directories associated with '$" -"(unit)'." -msgstr "" -"Потребно је да се идентификујете да бисте поново поставили „неуспешно“ стање " -"за „$(unit)“." - -#: src/core/dbus-unit.c:813 -#, fuzzy -msgid "" -"Authentication is required to freeze or thaw the processes of '$(unit)' unit." -msgstr "" -"Потребно је да се идентификујете да бисте поново поставили „неуспешно“ стање " -"за „$(unit)“." - -#~ msgid "" -#~ "Authentication is required to halt the system while an application asked " -#~ "to inhibit it." -#~ msgstr "" -#~ "Потребно је да се идентификујете да бисте зауставили систем иако програм " -#~ "тражи да се спречи заустављање система." - -#~ msgid "Authentication is required to kill '$(unit)'." -#~ msgstr "Потребно је да се идентификујете да бисте убили „$(unit)“." - -#~ msgid "Press Ctrl+C to cancel all filesystem checks in progress" -#~ msgstr "" -#~ "Притисните Ctrl+C да бисте прекинули све текуће провере система датотека" - -#~ msgid "Checking in progress on %d disk (%3.1f%% complete)" -#~ msgid_plural "Checking in progress on %d disks (%3.1f%% complete)" -#~ msgstr[0] "Провера у току на %d диску (%3.1f%% готово)" -#~ msgstr[1] "Провера у току на %d диска (%3.1f%% готово)" -#~ msgstr[2] "Провера у току на %d дискова (%3.1f%% готово)" diff --git a/po/sr@latin.po b/po/sr@latin.po new file mode 100644 index 0000000000000..92c83300d2316 --- /dev/null +++ b/po/sr@latin.po @@ -0,0 +1,1143 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Serbian Latin translation of systemd. +# Frantisek Sumsal , 2021. +# Marko Kostić , 2026 +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://github.com/systemd/systemd/issues\n" +"POT-Creation-Date: 2026-03-20 14:34+0000\n" +"PO-Revision-Date: 2026-04-12 18:20+0200\n" +"Last-Translator: Marko Kostić \n" +"Language-Team: Serbian \n" +"Language: sr@latin\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Poedit 3.9\n" + +#: src/core/org.freedesktop.systemd1.policy.in:22 +msgid "Send passphrase back to system" +msgstr "Pošalji frazu nazad ka sistemu" + +#: src/core/org.freedesktop.systemd1.policy.in:23 +msgid "" +"Authentication is required to send the entered passphrase back to the system." +msgstr "" +"Potrebno je da se identifikujete da biste poslali frazu nazad u sistem." + +#: src/core/org.freedesktop.systemd1.policy.in:33 +msgid "Manage system services or other units" +msgstr "Upravljaj sistemskim uslugama i drugim jedinicama" + +#: src/core/org.freedesktop.systemd1.policy.in:34 +msgid "Authentication is required to manage system services or other units." +msgstr "" +"Potrebno je da se identifikujete da biste upravljali sistemskim uslugama ili " +"drugim jedinicama." + +#: src/core/org.freedesktop.systemd1.policy.in:43 +msgid "Manage system service or unit files" +msgstr "Upravljaj sistemskom uslugom ili jediničnim datotekama" + +#: src/core/org.freedesktop.systemd1.policy.in:44 +msgid "Authentication is required to manage system service or unit files." +msgstr "" +"Potrebno je da se identifikujete da biste upravljali sistemskom uslugom ili " +"jediničnim datotekama." + +#: src/core/org.freedesktop.systemd1.policy.in:54 +msgid "Set or unset system and service manager environment variables" +msgstr "Menjaj promenljive okruženja na sistemu i unutar upravnika usluga" + +#: src/core/org.freedesktop.systemd1.policy.in:55 +msgid "" +"Authentication is required to set or unset system and service manager " +"environment variables." +msgstr "" +"Potrebno je da se identifikujete da biste menjali promenljive okruženja na " +"sistemu i unutar upravnika usluga." + +#: src/core/org.freedesktop.systemd1.policy.in:64 +msgid "Reload the systemd state" +msgstr "Ponovo učitaj stanje sistem-dea" + +#: src/core/org.freedesktop.systemd1.policy.in:65 +msgid "Authentication is required to reload the systemd state." +msgstr "" +"Potrebno je da se identifikujete da biste ponovo učitali stanje sistem-dea." + +#: src/core/org.freedesktop.systemd1.policy.in:74 +msgid "Dump the systemd state without rate limits" +msgstr "Ispiši stanje sistemd-a bez ograničenja protoka" + +#: src/core/org.freedesktop.systemd1.policy.in:75 +msgid "" +"Authentication is required to dump the systemd state without rate limits." +msgstr "" +"Potrebno je potvrđivanje identiteta za ispisivanje stanja sistemde-a bez " +"ograničenja protoka." + +#: src/home/org.freedesktop.home1.policy:13 +msgid "Create a home area" +msgstr "Napravi lični prostor" + +#: src/home/org.freedesktop.home1.policy:14 +msgid "Authentication is required to create a user's home area." +msgstr "" +"Potrebno je potvrđivanje identiteta za pravljenje ličnog prostora korisnika." + +#: src/home/org.freedesktop.home1.policy:23 +msgid "Remove a home area" +msgstr "Ukloni lični prostor" + +#: src/home/org.freedesktop.home1.policy:24 +msgid "Authentication is required to remove a user's home area." +msgstr "" +"Potrebno je potvrđivanje identiteta za uklanjanje ličnog prostora korisnika." + +#: src/home/org.freedesktop.home1.policy:33 +msgid "Check credentials of a home area" +msgstr "Proveri akreditive ličnog prostora" + +#: src/home/org.freedesktop.home1.policy:34 +msgid "" +"Authentication is required to check credentials against a user's home area." +msgstr "" +"Potrebno je potvrđivanje identiteta za proveru akreditiva ličnog prostora " +"korisnika." + +#: src/home/org.freedesktop.home1.policy:43 +msgid "Update a home area" +msgstr "Ažuriraj lični prostor" + +#: src/home/org.freedesktop.home1.policy:44 +msgid "Authentication is required to update a user's home area." +msgstr "" +"Potrebno je potvrđivanje identiteta za ažuriranje ličnog prostora korisnika." + +#: src/home/org.freedesktop.home1.policy:53 +msgid "Update your home area" +msgstr "Ažuriraj svoj lični prostor" + +#: src/home/org.freedesktop.home1.policy:54 +msgid "Authentication is required to update your home area." +msgstr "Potrebno je potvrđivanje identiteta za ažuriranje svog ličnog prostora." + +#: src/home/org.freedesktop.home1.policy:63 +msgid "Resize a home area" +msgstr "Promeni veličinu ličnog prostora" + +#: src/home/org.freedesktop.home1.policy:64 +msgid "Authentication is required to resize a user's home area." +msgstr "" +"Potrebno je potvrđivanje identiteta za promenu veličine ličnog prostora " +"korisnika." + +#: src/home/org.freedesktop.home1.policy:73 +msgid "Change password of a home area" +msgstr "Promeni lozinku ličnog prostora" + +#: src/home/org.freedesktop.home1.policy:74 +msgid "" +"Authentication is required to change the password of a user's home area." +msgstr "" +"Potrebno je potvrđivanje identiteta za promenu lozinke ličnog prostora " +"korisnika." + +#: src/home/org.freedesktop.home1.policy:83 +msgid "Activate a home area" +msgstr "Aktiviraj lični prostor" + +#: src/home/org.freedesktop.home1.policy:84 +msgid "Authentication is required to activate a user's home area." +msgstr "" +"Potrebno je potvrđivanje identiteta za aktiviranje ličnog prostora korisnika." + +#: src/home/org.freedesktop.home1.policy:93 +msgid "Manage Home Directory Signing Keys" +msgstr "Upravljaj ključevima za potpisivanje ličnog direktorijuma" + +#: src/home/org.freedesktop.home1.policy:94 +msgid "Authentication is required to manage signing keys for home directories." +msgstr "" +"Potrebno je potvrđivanje identiteta za upravljanje ključevima za potpisivanje " +"ličnih direktorijuma." + +#: src/hostname/org.freedesktop.hostname1.policy:20 +msgid "Set hostname" +msgstr "Postavi naziv mašine" + +#: src/hostname/org.freedesktop.hostname1.policy:21 +msgid "Authentication is required to set the local hostname." +msgstr "Potrebno je da se identifikujete da biste postavili naziv mašine." + +#: src/hostname/org.freedesktop.hostname1.policy:30 +msgid "Set static hostname" +msgstr "Postavi statički naziv mašine" + +#: src/hostname/org.freedesktop.hostname1.policy:31 +msgid "" +"Authentication is required to set the statically configured local hostname, " +"as well as the pretty hostname." +msgstr "" +"Potrebno je da se identifikujete da biste postavili statički naziv mašine i " +"da biste postavili lep naziv mašine." + +#: src/hostname/org.freedesktop.hostname1.policy:41 +msgid "Set machine information" +msgstr "Postavi podatke o mašini" + +#: src/hostname/org.freedesktop.hostname1.policy:42 +msgid "Authentication is required to set local machine information." +msgstr "" +"Potrebno je da se identifikujete da biste postavili podatke o lokalnoj " +"mašini." + +#: src/hostname/org.freedesktop.hostname1.policy:51 +msgid "Get product UUID" +msgstr "Dobavi UUID proizvoda" + +#: src/hostname/org.freedesktop.hostname1.policy:52 +msgid "Authentication is required to get product UUID." +msgstr "Potrebno je potvrđivanje identiteta za dobavljanje UUID-a proizvoda." + +#: src/hostname/org.freedesktop.hostname1.policy:61 +msgid "Get hardware serial number" +msgstr "Dobavi serijski broj hardvera" + +#: src/hostname/org.freedesktop.hostname1.policy:62 +msgid "Authentication is required to get hardware serial number." +msgstr "" +"Potrebno je potvrđivanje identiteta za dobavljanje serijskog broja hardvera." + +#: src/hostname/org.freedesktop.hostname1.policy:71 +msgid "Get system description" +msgstr "Dobavi opis sistema" + +#: src/hostname/org.freedesktop.hostname1.policy:72 +msgid "Authentication is required to get system description." +msgstr "Potrebno je potvrđivanje identiteta za dobavljanje opisa sistema." + +#: src/import/org.freedesktop.import1.policy:22 +msgid "Import a disk image" +msgstr "Uvezi odraz diska" + +#: src/import/org.freedesktop.import1.policy:23 +msgid "Authentication is required to import an image." +msgstr "Potrebno je potvrđivanje identiteta za uvoz odraza." + +#: src/import/org.freedesktop.import1.policy:32 +msgid "Export a disk image" +msgstr "Izvezi odraz diska" + +#: src/import/org.freedesktop.import1.policy:33 +msgid "Authentication is required to export disk image." +msgstr "Potrebno je potvrđivanje identiteta za izvoz odraza diska." + +#: src/import/org.freedesktop.import1.policy:42 +msgid "Download a disk image" +msgstr "Preuzmi odraz diska" + +#: src/import/org.freedesktop.import1.policy:43 +msgid "Authentication is required to download a disk image." +msgstr "Potrebno je potvrđivanje identiteta za preuzimanje odraza diska." + +#: src/import/org.freedesktop.import1.policy:52 +msgid "Cancel transfer of a disk image" +msgstr "Otkaži prenos odraza diska" + +#: src/import/org.freedesktop.import1.policy:53 +msgid "" +"Authentication is required to cancel the ongoing transfer of a disk image." +msgstr "" +"Potrebno je potvrđivanje identiteta za otkazivanje tekućeg prenosa odraza " +"diska." + +#: src/locale/org.freedesktop.locale1.policy:22 +msgid "Set system locale" +msgstr "Postavi osnovni jezik sistema" + +#: src/locale/org.freedesktop.locale1.policy:23 +msgid "Authentication is required to set the system locale." +msgstr "" +"Potrebno je da se identifikujete da biste postavili osnovni jezik sistema." + +#: src/locale/org.freedesktop.locale1.policy:33 +msgid "Set system keyboard settings" +msgstr "Postavi podešavanje sistemske tastature" + +#: src/locale/org.freedesktop.locale1.policy:34 +msgid "Authentication is required to set the system keyboard settings." +msgstr "" +"Potrebno je da se identifikujete da biste postavili podešavanja sistemske " +"tastature." + +#: src/login/org.freedesktop.login1.policy:22 +msgid "Allow applications to inhibit system shutdown" +msgstr "Dozvoli programima da spreče gašenje sistema" + +#: src/login/org.freedesktop.login1.policy:23 +msgid "" +"Authentication is required for an application to inhibit system shutdown." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"gašenje sistema." + +#: src/login/org.freedesktop.login1.policy:33 +msgid "Allow applications to delay system shutdown" +msgstr "Dozvoli programima da odlože gašenje sistema" + +#: src/login/org.freedesktop.login1.policy:34 +msgid "Authentication is required for an application to delay system shutdown." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da odloži " +"gašenje sistema." + +#: src/login/org.freedesktop.login1.policy:44 +msgid "Allow applications to inhibit system sleep" +msgstr "Dozvoli programima da spreče spavanje sistema" + +#: src/login/org.freedesktop.login1.policy:45 +msgid "Authentication is required for an application to inhibit system sleep." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"spavanje sistema." + +#: src/login/org.freedesktop.login1.policy:55 +msgid "Allow applications to delay system sleep" +msgstr "Dozvoli programima da odlože spavanje sistema" + +#: src/login/org.freedesktop.login1.policy:56 +msgid "Authentication is required for an application to delay system sleep." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da odloži " +"spavanje sistema." + +#: src/login/org.freedesktop.login1.policy:65 +msgid "Allow applications to inhibit automatic system suspend" +msgstr "Dozvoli programima da spreče samostalnu obustavu sistema" + +#: src/login/org.freedesktop.login1.policy:66 +msgid "" +"Authentication is required for an application to inhibit automatic system " +"suspend." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"samostalnu obustavu sistema." + +#: src/login/org.freedesktop.login1.policy:75 +msgid "Allow applications to inhibit system handling of the power key" +msgstr "Dozvoli programima da spreče sistemu upravljanje dugmetom za napajanje" + +#: src/login/org.freedesktop.login1.policy:76 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the power key." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"sistemu upravljanje dugmetom za napajanje." + +#: src/login/org.freedesktop.login1.policy:86 +msgid "Allow applications to inhibit system handling of the suspend key" +msgstr "Dozvoli programima da spreče sistemu upravljanje dugmetom za obustavu" + +#: src/login/org.freedesktop.login1.policy:87 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the suspend key." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"sistemu upravljanje dugmetom za obustavu." + +#: src/login/org.freedesktop.login1.policy:97 +msgid "Allow applications to inhibit system handling of the hibernate key" +msgstr "Dozvoli programima da spreče sistemu upravljanje dugmetom za spavanje" + +#: src/login/org.freedesktop.login1.policy:98 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the hibernate key." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"sistemu upravljanje dugmetom za spavanje." + +#: src/login/org.freedesktop.login1.policy:107 +msgid "Allow applications to inhibit system handling of the lid switch" +msgstr "" +"Dozvoli programima da spreče sistemu da uradi bilo šta prilikom zaklapanja " +"ekrana" + +#: src/login/org.freedesktop.login1.policy:108 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the lid switch." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"sistemu da uradi bilo šta prilikom zaklapanja ekrana." + +#: src/login/org.freedesktop.login1.policy:117 +msgid "Allow applications to inhibit system handling of the reboot key" +msgstr "" +"Dozvoli programima da spreče sistemu upravljanje dugmetom za ponovno pokretanje" + +#: src/login/org.freedesktop.login1.policy:118 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the reboot key." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"sistemu upravljanje dugmetom za ponovno pokretanje." + +#: src/login/org.freedesktop.login1.policy:128 +msgid "Allow non-logged-in user to run programs" +msgstr "Dozvoli neprijavljenim korisnicima da pokreću programe" + +#: src/login/org.freedesktop.login1.policy:129 +msgid "Explicit request is required to run programs as a non-logged-in user." +msgstr "" +"Eksplicitan zahtev je potreban da biste pokretali programe kao neprijavljen " +"korisnik." + +#: src/login/org.freedesktop.login1.policy:138 +msgid "Allow non-logged-in users to run programs" +msgstr "Dozvoli neprijavljenim korisnicima da pokreću programe" + +#: src/login/org.freedesktop.login1.policy:139 +msgid "Authentication is required to run programs as a non-logged-in user." +msgstr "" +"Potrebno je da se identifikujete da biste pokretali programe kao neprijavljen " +"korisnik." + +#: src/login/org.freedesktop.login1.policy:148 +msgid "Allow attaching devices to seats" +msgstr "Dozvoli kačenje uređaja na sedišta" + +#: src/login/org.freedesktop.login1.policy:149 +msgid "Authentication is required to attach a device to a seat." +msgstr "Potrebno je da se identifikujete da biste zakačili uređaj na sedište." + +#: src/login/org.freedesktop.login1.policy:159 +msgid "Flush device to seat attachments" +msgstr "Isperi uređaj da bi usedištio zakačeno" + +#: src/login/org.freedesktop.login1.policy:160 +msgid "Authentication is required to reset how devices are attached to seats." +msgstr "" +"Potrebno je da se identifikujete da biste ponovo podesili kako se uređaji " +"kače na sedišta." + +#: src/login/org.freedesktop.login1.policy:169 +msgid "Power off the system" +msgstr "Isključi sistem" + +#: src/login/org.freedesktop.login1.policy:170 +msgid "Authentication is required to power off the system." +msgstr "Potrebno je da se identifikujete da biste isključili sistem." + +#: src/login/org.freedesktop.login1.policy:180 +msgid "Power off the system while other users are logged in" +msgstr "Isključi sistem dok su drugi korisnici prijavljeni" + +#: src/login/org.freedesktop.login1.policy:181 +msgid "" +"Authentication is required to power off the system while other users are " +"logged in." +msgstr "" +"Potrebno je da se identifikujete da biste isključili sistem dok su drugi " +"korisnici prijavljeni." + +#: src/login/org.freedesktop.login1.policy:191 +msgid "Power off the system while an application is inhibiting this" +msgstr "Isključi sistem iako je program zatražio da se spreči gašenje" + +#: src/login/org.freedesktop.login1.policy:192 +msgid "" +"Authentication is required to power off the system while an application is " +"inhibiting this." +msgstr "" +"Potrebno je da se identifikujete da biste isključili sistem iako je program " +"zatražio da se spreči gašenje sistema." + +#: src/login/org.freedesktop.login1.policy:202 +msgid "Reboot the system" +msgstr "Ponovo pokreni sistem" + +#: src/login/org.freedesktop.login1.policy:203 +msgid "Authentication is required to reboot the system." +msgstr "Potrebno je da se identifikujete da biste ponovo pokrenuli sistem." + +#: src/login/org.freedesktop.login1.policy:213 +msgid "Reboot the system while other users are logged in" +msgstr "Ponovo pokreni sistem dok su drugi korisnici prijavljeni" + +#: src/login/org.freedesktop.login1.policy:214 +msgid "" +"Authentication is required to reboot the system while other users are logged " +"in." +msgstr "" +"Potrebno je da se identifikujete da biste ponovo pokrenuli sistem dok su " +"drugi korisnici prijavljeni." + +#: src/login/org.freedesktop.login1.policy:224 +msgid "Reboot the system while an application is inhibiting this" +msgstr "Ponovo pokreni sistem iako je program zatražio da se spreči gašenje" + +#: src/login/org.freedesktop.login1.policy:225 +msgid "" +"Authentication is required to reboot the system while an application is " +"inhibiting this." +msgstr "" +"Potrebno je da se identifikujete da biste ponovo pokrenuli sistem iako je " +"program zatražio da se spreči gašenje sistema." + +#: src/login/org.freedesktop.login1.policy:235 +msgid "Halt the system" +msgstr "Zaustavi sistem" + +#: src/login/org.freedesktop.login1.policy:236 +msgid "Authentication is required to halt the system." +msgstr "Potrebno je da se identifikujete da biste zaustavili sistem." + +#: src/login/org.freedesktop.login1.policy:246 +msgid "Halt the system while other users are logged in" +msgstr "Zaustavi sistem dok su drugi korisnici prijavljeni" + +#: src/login/org.freedesktop.login1.policy:247 +msgid "" +"Authentication is required to halt the system while other users are logged " +"in." +msgstr "" +"Potrebno je da se identifikujete da biste zaustavili sistem dok su drugi " +"korisnici prijavljeni." + +#: src/login/org.freedesktop.login1.policy:257 +msgid "Halt the system while an application is inhibiting this" +msgstr "Zaustavi sistem iako program traži da se spreči zaustavljanje" + +#: src/login/org.freedesktop.login1.policy:258 +msgid "" +"Authentication is required to halt the system while an application is " +"inhibiting this." +msgstr "" +"Potrebno je da se identifikujete da biste obustavili sistem iako je program " +"zatražio da se spreči obustavljanje sistema." + +#: src/login/org.freedesktop.login1.policy:268 +msgid "Suspend the system" +msgstr "Obustavi sistem" + +#: src/login/org.freedesktop.login1.policy:269 +msgid "Authentication is required to suspend the system." +msgstr "Potrebno je da se identifikujete da biste obustavili sistem." + +#: src/login/org.freedesktop.login1.policy:278 +msgid "Suspend the system while other users are logged in" +msgstr "Obustavi sistem dok su drugi korisnici prijavljeni" + +#: src/login/org.freedesktop.login1.policy:279 +msgid "" +"Authentication is required to suspend the system while other users are " +"logged in." +msgstr "" +"Potrebno je da se identifikujete da biste obustavili sistem dok su drugi " +"korisnici prijavljeni." + +#: src/login/org.freedesktop.login1.policy:289 +msgid "Suspend the system while an application is inhibiting this" +msgstr "Obustavite sistem iako program traži da se spreči obustava" + +#: src/login/org.freedesktop.login1.policy:290 +msgid "" +"Authentication is required to suspend the system while an application is " +"inhibiting this." +msgstr "" +"Potrebno je da se identifikujete da biste obustavili sistem iako je program " +"zatražio da se spreči obustava sistema." + +#: src/login/org.freedesktop.login1.policy:300 +msgid "Hibernate the system" +msgstr "Uspavaj sistem" + +#: src/login/org.freedesktop.login1.policy:301 +msgid "Authentication is required to hibernate the system." +msgstr "Potrebno je da se identifikujete da biste uspavali sistem." + +#: src/login/org.freedesktop.login1.policy:310 +msgid "Hibernate the system while other users are logged in" +msgstr "Uspavaj sistem dok su drugi korisnici prijavljeni" + +#: src/login/org.freedesktop.login1.policy:311 +msgid "" +"Authentication is required to hibernate the system while other users are " +"logged in." +msgstr "" +"Potrebno je da se identifikujete da biste uspavali sistem dok su drugi " +"korisnici prijavljeni." + +#: src/login/org.freedesktop.login1.policy:321 +msgid "Hibernate the system while an application is inhibiting this" +msgstr "Uspavaj sistem iako je program zatražio da se spreči spavanje" + +#: src/login/org.freedesktop.login1.policy:322 +msgid "" +"Authentication is required to hibernate the system while an application is " +"inhibiting this." +msgstr "" +"Potrebno je da se identifikujete da biste uspavali sistem iako je program " +"zatražio da se spreči uspavljivanje sistema." + +#: src/login/org.freedesktop.login1.policy:332 +msgid "Manage active sessions, users and seats" +msgstr "Upravljaj pokrenutim sesijama, korisnicima i sedištima" + +#: src/login/org.freedesktop.login1.policy:333 +msgid "Authentication is required to manage active sessions, users and seats." +msgstr "" +"Potrebno je da se identifikujete da biste upravljali pokrenutim sesijama, " +"korisnicima i sedištima." + +#: src/login/org.freedesktop.login1.policy:342 +msgid "Lock or unlock active sessions" +msgstr "Zaključaj ili otključaj pokrenute sesije" + +#: src/login/org.freedesktop.login1.policy:343 +msgid "Authentication is required to lock or unlock active sessions." +msgstr "" +"Potrebno je da se identifikujete da biste zaključavali ili otključavali " +"pokrenute sesije." + +#: src/login/org.freedesktop.login1.policy:352 +msgid "Set the reboot \"reason\" in the kernel" +msgstr "Postavi „razlog“ ponovnog pokretanja u jezgru" + +#: src/login/org.freedesktop.login1.policy:353 +msgid "Authentication is required to set the reboot \"reason\" in the kernel." +msgstr "" +"Potrebno je da se identifikujete da biste postavili \"razlog\" za ponovno " +"pokretanje unutar jezgra." + +#: src/login/org.freedesktop.login1.policy:363 +msgid "Indicate to the firmware to boot to setup interface" +msgstr "Ukaži firmveru da pokrene sistem u interfejsu za podešavanje" + +#: src/login/org.freedesktop.login1.policy:364 +msgid "" +"Authentication is required to indicate to the firmware to boot to setup " +"interface." +msgstr "" +"Potrebno je da se identifikujete da biste napomenuli firmveru da se podigne " +"u režimu podešavanja interfejsa." + +#: src/login/org.freedesktop.login1.policy:374 +msgid "Indicate to the boot loader to boot to the boot loader menu" +msgstr "" +"Ukaži učitavaču podizanja sistema da prikaže izbornik za podizanje sistema" + +#: src/login/org.freedesktop.login1.policy:375 +msgid "" +"Authentication is required to indicate to the boot loader to boot to the " +"boot loader menu." +msgstr "" +"Potrebno je potvrđivanje identiteta za ukazivanje učitavaču podizanja sistema " +"da prikaže izbornik za podizanje sistema." + +#: src/login/org.freedesktop.login1.policy:385 +msgid "Indicate to the boot loader to boot a specific entry" +msgstr "Ukaži učitavaču podizanja sistema da pokrene određeni unos" + +#: src/login/org.freedesktop.login1.policy:386 +msgid "" +"Authentication is required to indicate to the boot loader to boot into a " +"specific boot loader entry." +msgstr "" +"Potrebno je potvrđivanje identiteta za ukazivanje učitavaču podizanja sistema " +"da pokrene određeni unos učitavača podizanja sistema." + +#: src/login/org.freedesktop.login1.policy:396 +msgid "Set a wall message" +msgstr "Postavi zidnu poruku" + +#: src/login/org.freedesktop.login1.policy:397 +msgid "Authentication is required to set a wall message." +msgstr "Potrebno je potvrđivanje identiteta za postavljanje zidne poruke." + +#: src/login/org.freedesktop.login1.policy:406 +msgid "Change Session" +msgstr "Promeni sesiju" + +#: src/login/org.freedesktop.login1.policy:407 +msgid "Authentication is required to change the virtual terminal." +msgstr "Potrebno je potvrđivanje identiteta za promenu virtuelnog terminala." + +#: src/machine/org.freedesktop.machine1.policy:22 +msgid "Log into a local container" +msgstr "Prijavi se u lokalni kontejner" + +#: src/machine/org.freedesktop.machine1.policy:23 +msgid "Authentication is required to log into a local container." +msgstr "" +"Potrebno je da se identifikujete da biste se prijavili u lokalni kontejner." + +#: src/machine/org.freedesktop.machine1.policy:32 +msgid "Log into the local host" +msgstr "Prijavi se u lokalnog domaćina" + +#: src/machine/org.freedesktop.machine1.policy:33 +msgid "Authentication is required to log into the local host." +msgstr "" +"Potrebno je da se identifikujete da biste se prijavili u lokalnog domaćina." + +#: src/machine/org.freedesktop.machine1.policy:42 +msgid "Acquire a shell in a local container" +msgstr "Dobij pristup školjci unutar lokalnog kontejnera" + +#: src/machine/org.freedesktop.machine1.policy:43 +msgid "Authentication is required to acquire a shell in a local container." +msgstr "" +"Potrebno je da se identifikujete da biste dobili pristup školjci unutar " +"lokalnog kontejnera." + +#: src/machine/org.freedesktop.machine1.policy:53 +msgid "Acquire a shell on the local host" +msgstr "Dobij pristup školjci na lokalnom domaćinu" + +#: src/machine/org.freedesktop.machine1.policy:54 +msgid "Authentication is required to acquire a shell on the local host." +msgstr "" +"Potrebno je da se identifikujete da biste dobili pristup školjci na lokalnom " +"domaćinu." + +#: src/machine/org.freedesktop.machine1.policy:64 +msgid "Acquire a pseudo TTY in a local container" +msgstr "Dobij pristup pseudo pisaćoj mašini unutar lokalnog kontejnera" + +#: src/machine/org.freedesktop.machine1.policy:65 +msgid "" +"Authentication is required to acquire a pseudo TTY in a local container." +msgstr "" +"Potrebno je da se identifikujete da biste dobili pristup pseudo pisaćoj " +"mašini unutar lokalnog kontejnera." + +#: src/machine/org.freedesktop.machine1.policy:74 +msgid "Acquire a pseudo TTY on the local host" +msgstr "Dobij pristup pseudo pisaćoj mašini na lokalnom domaćinu" + +#: src/machine/org.freedesktop.machine1.policy:75 +msgid "Authentication is required to acquire a pseudo TTY on the local host." +msgstr "" +"Potrebno je da se identifikujete da biste dobili pristup pseudo pisaćoj " +"mašini na lokalnom domaćinu." + +#: src/machine/org.freedesktop.machine1.policy:84 +msgid "Manage local virtual machines and containers" +msgstr "Upravljaj lokalnim virtuelnim mašinama i kontejnerima" + +#: src/machine/org.freedesktop.machine1.policy:85 +msgid "" +"Authentication is required to manage local virtual machines and containers." +msgstr "" +"Potrebno je da se identifikujete da biste upravljali lokalnim virtuelnim " +"mašinama i kontejnerima." + +#: src/machine/org.freedesktop.machine1.policy:95 +msgid "Create a local virtual machine or container" +msgstr "Napravi lokalnu virtuelnu mašinu ili kontejner" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "" +"Potrebno je potvrđivanje identiteta za pravljenje lokalne virtuelne mašine ili " +"kontejnera." + +#: src/machine/org.freedesktop.machine1.policy:106 +msgid "Register a local virtual machine or container" +msgstr "Registruj lokalnu virtuelnu mašinu ili kontejner" + +#: src/machine/org.freedesktop.machine1.policy:107 +msgid "" +"Authentication is required to register a local virtual machine or container." +msgstr "" +"Potrebno je potvrđivanje identiteta za registrovanje lokalne virtuelne mašine " +"ili kontejnera." + +#: src/machine/org.freedesktop.machine1.policy:116 +msgid "Manage local virtual machine and container images" +msgstr "Upravljaj lokalnim virtuelnim mašinama i slikama kontejnera" + +#: src/machine/org.freedesktop.machine1.policy:117 +msgid "" +"Authentication is required to manage local virtual machine and container " +"images." +msgstr "" +"Potrebno je da se identifikujete da biste upravljali lokalnim virtuelnim " +"mašinama i slikama kontejnera." + +#: src/network/org.freedesktop.network1.policy:22 +msgid "Set NTP servers" +msgstr "Postavi NTP servere" + +#: src/network/org.freedesktop.network1.policy:23 +msgid "Authentication is required to set NTP servers." +msgstr "Potrebno je potvrđivanje identiteta za postavljanje NTP servera." + +#: src/network/org.freedesktop.network1.policy:33 +#: src/resolve/org.freedesktop.resolve1.policy:44 +msgid "Set DNS servers" +msgstr "Postavi DNS servere" + +#: src/network/org.freedesktop.network1.policy:34 +#: src/resolve/org.freedesktop.resolve1.policy:45 +msgid "Authentication is required to set DNS servers." +msgstr "Potrebno je potvrđivanje identiteta za postavljanje DNS servera." + +#: src/network/org.freedesktop.network1.policy:44 +#: src/resolve/org.freedesktop.resolve1.policy:55 +msgid "Set domains" +msgstr "Postavi domene" + +#: src/network/org.freedesktop.network1.policy:45 +#: src/resolve/org.freedesktop.resolve1.policy:56 +msgid "Authentication is required to set domains." +msgstr "Potrebno je potvrđivanje identiteta za postavljanje domena." + +#: src/network/org.freedesktop.network1.policy:55 +#: src/resolve/org.freedesktop.resolve1.policy:66 +msgid "Set default route" +msgstr "Postavi podrazumevanu rutu" + +#: src/network/org.freedesktop.network1.policy:56 +#: src/resolve/org.freedesktop.resolve1.policy:67 +msgid "Authentication is required to set default route." +msgstr "Potrebno je potvrđivanje identiteta za postavljanje podrazumevane rute." + +#: src/network/org.freedesktop.network1.policy:66 +#: src/resolve/org.freedesktop.resolve1.policy:77 +msgid "Enable/disable LLMNR" +msgstr "Omogući/onemogući LLMNR" + +#: src/network/org.freedesktop.network1.policy:67 +#: src/resolve/org.freedesktop.resolve1.policy:78 +msgid "Authentication is required to enable or disable LLMNR." +msgstr "" +"Potrebno je potvrđivanje identiteta za omogućavanje ili onemogućavanje LLMNR-a." + +#: src/network/org.freedesktop.network1.policy:77 +#: src/resolve/org.freedesktop.resolve1.policy:88 +msgid "Enable/disable multicast DNS" +msgstr "Omogući/onemogući multikast DNS" + +#: src/network/org.freedesktop.network1.policy:78 +#: src/resolve/org.freedesktop.resolve1.policy:89 +msgid "Authentication is required to enable or disable multicast DNS." +msgstr "" +"Potrebno je potvrđivanje identiteta za omogućavanje ili onemogućavanje " +"multikast DNS-a." + +#: src/network/org.freedesktop.network1.policy:88 +#: src/resolve/org.freedesktop.resolve1.policy:99 +msgid "Enable/disable DNS over TLS" +msgstr "Omogući/onemogući DNS preko TLS-a" + +#: src/network/org.freedesktop.network1.policy:89 +#: src/resolve/org.freedesktop.resolve1.policy:100 +msgid "Authentication is required to enable or disable DNS over TLS." +msgstr "" +"Potrebno je potvrđivanje identiteta za omogućavanje ili onemogućavanje DNS-a " +"preko TLS-a." + +#: src/network/org.freedesktop.network1.policy:99 +#: src/resolve/org.freedesktop.resolve1.policy:110 +msgid "Enable/disable DNSSEC" +msgstr "Omogući/onemogući DNSSEC" + +#: src/network/org.freedesktop.network1.policy:100 +#: src/resolve/org.freedesktop.resolve1.policy:111 +msgid "Authentication is required to enable or disable DNSSEC." +msgstr "" +"Potrebno je potvrđivanje identiteta za omogućavanje ili onemogućavanje DNSSEC-" +"a." + +#: src/network/org.freedesktop.network1.policy:110 +#: src/resolve/org.freedesktop.resolve1.policy:121 +msgid "Set DNSSEC Negative Trust Anchors" +msgstr "Postavi negativne sidre poverenja za DNSSEC" + +#: src/network/org.freedesktop.network1.policy:111 +#: src/resolve/org.freedesktop.resolve1.policy:122 +msgid "Authentication is required to set DNSSEC Negative Trust Anchors." +msgstr "" +"Potrebno je potvrđivanje identiteta za postavljanje negativnih sidara poverenja " +"za DNSSEC." + +#: src/network/org.freedesktop.network1.policy:121 +msgid "Revert NTP settings" +msgstr "Vrati NTP podešavanja" + +#: src/network/org.freedesktop.network1.policy:122 +msgid "Authentication is required to reset NTP settings." +msgstr "Potrebno je potvrđivanje identiteta za resetovanje NTP podešavanja." + +#: src/network/org.freedesktop.network1.policy:132 +msgid "Revert DNS settings" +msgstr "Vrati DNS podešavanja" + +#: src/network/org.freedesktop.network1.policy:133 +msgid "Authentication is required to reset DNS settings." +msgstr "Potrebno je potvrđivanje identiteta za resetovanje DNS podešavanja." + +#: src/network/org.freedesktop.network1.policy:143 +msgid "DHCP server sends force renew message" +msgstr "DHCP server šalje poruku za prisilno obnavljanje" + +#: src/network/org.freedesktop.network1.policy:144 +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"Potrebno je potvrđivanje identiteta za slanje poruke za prisilno obnavljanje sa " +"DHCP servera." + +#: src/network/org.freedesktop.network1.policy:154 +msgid "Renew dynamic addresses" +msgstr "Obnovi dinamičke adrese" + +#: src/network/org.freedesktop.network1.policy:155 +msgid "Authentication is required to renew dynamic addresses." +msgstr "Potrebno je potvrđivanje identiteta za obnavljanje dinamičkih adresa." + +#: src/network/org.freedesktop.network1.policy:165 +msgid "Reload network settings" +msgstr "Ponovo učitaj mrežna podešavanja" + +#: src/network/org.freedesktop.network1.policy:166 +msgid "Authentication is required to reload network settings." +msgstr "" +"Potrebno je potvrđivanje identiteta za ponovno učitavanje mrežnih podešavanja." + +#: src/network/org.freedesktop.network1.policy:176 +msgid "Reconfigure network interface" +msgstr "Ponovo podesi mrežni interfejs" + +#: src/network/org.freedesktop.network1.policy:177 +msgid "Authentication is required to reconfigure network interface." +msgstr "" +"Potrebno je potvrđivanje identiteta za ponovno podešavanje mrežnog interfejsa." + +#: src/network/org.freedesktop.network1.policy:187 +msgid "Specify whether persistent storage for systemd-networkd is available" +msgstr "Navedite da li je dostupno trajno skladište za systemd-networkd" + +#: src/network/org.freedesktop.network1.policy:188 +msgid "" +"Authentication is required to specify whether persistent storage for systemd-" +"networkd is available." +msgstr "" +"Potrebno je potvrđivanje identiteta za navođenje da li je dostupno trajno " +"skladište za systemd-networkd." + +#: src/network/org.freedesktop.network1.policy:198 +msgid "Manage network links" +msgstr "Upravljaj mrežnim vezama" + +#: src/network/org.freedesktop.network1.policy:199 +msgid "Authentication is required to manage network links." +msgstr "Potrebno je da se identifikujete da biste upravljali mrežnim vezama." + +#: src/portable/org.freedesktop.portable1.policy:13 +msgid "Inspect a portable service image" +msgstr "Pregledaj odraz prenosne usluge" + +#: src/portable/org.freedesktop.portable1.policy:14 +msgid "Authentication is required to inspect a portable service image." +msgstr "Potrebno je potvrđivanje identiteta za pregled odraza prenosne usluge." + +#: src/portable/org.freedesktop.portable1.policy:23 +msgid "Attach or detach a portable service image" +msgstr "Prikači ili otkači odraz prenosne usluge" + +#: src/portable/org.freedesktop.portable1.policy:24 +msgid "" +"Authentication is required to attach or detach a portable service image." +msgstr "" +"Potrebno je potvrđivanje identiteta za prikačivanje ili otkačivanje odraza " +"prenosne usluge." + +#: src/portable/org.freedesktop.portable1.policy:34 +msgid "Delete or modify portable service image" +msgstr "Obriši ili izmeni odraz prenosne usluge" + +#: src/portable/org.freedesktop.portable1.policy:35 +msgid "" +"Authentication is required to delete or modify a portable service image." +msgstr "" +"Potrebno je potvrđivanje identiteta za brisanje ili izmenu odraza prenosne " +"usluge." + +#: src/resolve/org.freedesktop.resolve1.policy:22 +msgid "Register a DNS-SD service" +msgstr "Registruj DNS-SD uslugu" + +#: src/resolve/org.freedesktop.resolve1.policy:23 +msgid "Authentication is required to register a DNS-SD service." +msgstr "Potrebno je potvrđivanje identiteta za registraciju DNS-SD usluge." + +#: src/resolve/org.freedesktop.resolve1.policy:33 +msgid "Unregister a DNS-SD service" +msgstr "Ukini registraciju DNS-SD usluge" + +#: src/resolve/org.freedesktop.resolve1.policy:34 +msgid "Authentication is required to unregister a DNS-SD service." +msgstr "" +"Potrebno je potvrđivanje identiteta za poništavanje registracije DNS-SD usluge." + +#: src/resolve/org.freedesktop.resolve1.policy:132 +msgid "Revert name resolution settings" +msgstr "Vrati podešavanja razrešavanja naziva" + +#: src/resolve/org.freedesktop.resolve1.policy:133 +msgid "Authentication is required to reset name resolution settings." +msgstr "" +"Potrebno je potvrđivanje identiteta za resetovanje podešavanja razrešavanja " +"naziva." + +#: src/resolve/org.freedesktop.resolve1.policy:143 +msgid "Subscribe query results" +msgstr "Pretplati se na rezultate upita" + +#: src/resolve/org.freedesktop.resolve1.policy:144 +msgid "Authentication is required to subscribe query results." +msgstr "Potrebno je potvrđivanje identiteta za pretplatu na rezultate upita." + +#: src/resolve/org.freedesktop.resolve1.policy:154 +msgid "Subscribe to DNS configuration" +msgstr "Pretplati se na DNS podešavanja" + +#: src/resolve/org.freedesktop.resolve1.policy:155 +msgid "Authentication is required to subscribe to DNS configuration." +msgstr "Potrebno je potvrđivanje identiteta za pretplatu na DNS podešavanja." + +#: src/resolve/org.freedesktop.resolve1.policy:165 +msgid "Dump cache" +msgstr "Ispiši keš" + +#: src/resolve/org.freedesktop.resolve1.policy:166 +msgid "Authentication is required to dump cache." +msgstr "Potrebno je potvrđivanje identiteta za ispisivanje keša." + +#: src/resolve/org.freedesktop.resolve1.policy:176 +msgid "Dump server state" +msgstr "Ispiši stanje servera" + +#: src/resolve/org.freedesktop.resolve1.policy:177 +msgid "Authentication is required to dump server state." +msgstr "Potrebno je potvrđivanje identiteta za ispisivanje stanja servera." + +#: src/resolve/org.freedesktop.resolve1.policy:187 +msgid "Dump statistics" +msgstr "Ispiši statistiku" + +#: src/resolve/org.freedesktop.resolve1.policy:188 +msgid "Authentication is required to dump statistics." +msgstr "Potrebno je potvrđivanje identiteta za ispisivanje statistike." + +#: src/resolve/org.freedesktop.resolve1.policy:198 +msgid "Reset statistics" +msgstr "Resetuj statistiku" + +#: src/resolve/org.freedesktop.resolve1.policy:199 +msgid "Authentication is required to reset statistics." +msgstr "Potrebno je potvrđivanje identiteta za resetovanje statistike." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +msgid "Check for system updates" +msgstr "Proveri ažuriranja sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +msgid "Authentication is required to check for system updates." +msgstr "Potrebno je potvrđivanje identiteta za proveru ažuriranja sistema." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +msgid "Install system updates" +msgstr "Instaliraj ažuriranja sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +msgid "Authentication is required to install system updates." +msgstr "Potrebno je potvrđivanje identiteta za instaliranje ažuriranja sistema." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +msgid "Install specific system version" +msgstr "Instaliraj određeno izdanje sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +msgid "" +"Authentication is required to update the system to a specific (possibly old) " +"version." +msgstr "" +"Potrebno je potvrđivanje identiteta za ažuriranje sistema na određeno (moguće " +"staro) izdanje." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 +msgid "Cleanup old system updates" +msgstr "Očisti stara ažuriranja sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 +msgid "Authentication is required to cleanup old system updates." +msgstr "" +"Potrebno je potvrđivanje identiteta za čišćenje starih ažuriranja sistema." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +msgid "Manage optional features" +msgstr "Upravljaj opcionim mogućnostima" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +msgid "Authentication is required to manage optional features." +msgstr "Potrebno je potvrđivanje identiteta za upravljanje opcionim mogućnostima." + +#: src/timedate/org.freedesktop.timedate1.policy:22 +msgid "Set system time" +msgstr "Postavi sistemsko vreme" + +#: src/timedate/org.freedesktop.timedate1.policy:23 +msgid "Authentication is required to set the system time." +msgstr "Potrebno je da se identifikujete da biste postavili sistemsko vreme." + +#: src/timedate/org.freedesktop.timedate1.policy:33 +msgid "Set system timezone" +msgstr "Postavi sistemsku vremensku zonu" + +#: src/timedate/org.freedesktop.timedate1.policy:34 +msgid "Authentication is required to set the system timezone." +msgstr "" +"Potrebno je da se identifikujete da biste postavili sistemsku vremensku zonu." + +#: src/timedate/org.freedesktop.timedate1.policy:43 +msgid "Set RTC to local timezone or UTC" +msgstr "" +"Postavi časovnik realnog vremena na lokalnu vremensku zonu ili UTC zonu" + +#: src/timedate/org.freedesktop.timedate1.policy:44 +msgid "" +"Authentication is required to control whether the RTC stores the local or " +"UTC time." +msgstr "" +"Potrebno je da se identifikujete da biste podesili da li RTC čuva lokalno " +"ili UTC vreme." + +#: src/timedate/org.freedesktop.timedate1.policy:53 +msgid "Turn network time synchronization on or off" +msgstr "Uključi ili isključi usklađivanje vremena sa mreže" + +#: src/timedate/org.freedesktop.timedate1.policy:54 +msgid "" +"Authentication is required to control whether network time synchronization " +"shall be enabled." +msgstr "" +"Potrebno je da se identifikujete da biste podesili da li se vreme usklađuje " +"sa mreže." diff --git a/po/sv.po b/po/sv.po index 2b0630fce8012..96ee57502a8ce 100644 --- a/po/sv.po +++ b/po/sv.po @@ -12,8 +12,8 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2026-02-26 13:58+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-07 01:58+0000\n" "Last-Translator: Daniel Nylander \n" "Language-Team: Swedish \n" @@ -22,7 +22,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1021,8 +1021,12 @@ msgid "DHCP server sends force renew message" msgstr "DHCP-servern skickar tvingande förnyelsemeddelande" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "Autentisering krävs för att skicka tvingande förnyelsemeddelande." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"Autentisering krävs för att skicka ett tvingande förnyelsemeddelande från " +"DHCP-servern." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" diff --git a/po/systemd.pot b/po/systemd.pot index 825fd49cb0205..93bb42609816f 100644 --- a/po/systemd.pot +++ b/po/systemd.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -925,7 +925,9 @@ msgid "DHCP server sends force renew message" msgstr "" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" #: src/network/org.freedesktop.network1.policy:154 diff --git a/po/tr.po b/po/tr.po index 4671c06b87414..a197891714b68 100644 --- a/po/tr.po +++ b/po/tr.po @@ -10,8 +10,8 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2026-02-26 13:58+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-07 01:58+0000\n" "Last-Translator: Oğuz Ersen \n" "Language-Team: Turkish \n" @@ -20,7 +20,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1069,8 +1069,12 @@ msgid "DHCP server sends force renew message" msgstr "DHCP sunucusu zorunlu yenileme mesajı gönderiyor" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "Zorunlu yenileme mesajı göndermek için kimlik doğrulaması gereklidir." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"DHCP sunucusundan zorunlu yenileme mesajı göndermek için kimlik doğrulaması " +"gereklidir." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" diff --git a/po/ug.po b/po/ug.po new file mode 100644 index 0000000000000..5d96d103000db --- /dev/null +++ b/po/ug.po @@ -0,0 +1,1195 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Uyghur translation for systemd. +# Dongshengyuan , 2026. +msgid "" +msgstr "" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-16 14:21+0000\n" +"Last-Translator: Dongshengyuan \n" +"Language-Team: Uyghur\n" +"Language: ug\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: manual\n" + +#: src/core/org.freedesktop.systemd1.policy.in:22 +msgid "Send passphrase back to system" +msgstr "مەخپىي ئىبارىنى سىستېمىغا قايتا يوللا" + +#: src/core/org.freedesktop.systemd1.policy.in:23 +msgid "" +"Authentication is required to send the entered passphrase back to the system." +msgstr "كىرگۈزۈلگەن مەخپىي ئىبارىنى سىستېمىغا قايتا يوللاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/org.freedesktop.systemd1.policy.in:33 +msgid "Manage system services or other units" +msgstr "سىستېما مۇلازىمەتلىرىنى ياكى باشقا بىرىكلەرنى باشقۇر" + +#: src/core/org.freedesktop.systemd1.policy.in:34 +msgid "Authentication is required to manage system services or other units." +msgstr "سىستېما مۇلازىمەتلىرىنى ياكى باشقا بىرىكلەرنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/org.freedesktop.systemd1.policy.in:43 +msgid "Manage system service or unit files" +msgstr "سىستېما مۇلازىمەت ياكى بىرىك ھۆججەتلىرىنى باشقۇر" + +#: src/core/org.freedesktop.systemd1.policy.in:44 +msgid "Authentication is required to manage system service or unit files." +msgstr "سىستېما مۇلازىمەت ياكى بىرىك ھۆججەتلىرىنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/org.freedesktop.systemd1.policy.in:54 +msgid "Set or unset system and service manager environment variables" +msgstr "سىستېما ۋە مۇلازىمەت باشقۇرغۇچنىڭ مۇھىت ئۆزگەرگۈچىلىرىنى بەلگىلە ياكى بىكار قىل" + +#: src/core/org.freedesktop.systemd1.policy.in:55 +msgid "" +"Authentication is required to set or unset system and service manager " +"environment variables." +msgstr "سىستېما ۋە مۇلازىمەت باشقۇرغۇچنىڭ مۇھىت ئۆزگەرگۈچىلىرىنى بەلگىلەش ياكى بىكار قىلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/org.freedesktop.systemd1.policy.in:64 +msgid "Reload the systemd state" +msgstr "systemd ھالىتىنى قايتا يۈكلە" + +#: src/core/org.freedesktop.systemd1.policy.in:65 +msgid "Authentication is required to reload the systemd state." +msgstr "systemd ھالىتىنى قايتا يۈكلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/org.freedesktop.systemd1.policy.in:74 +msgid "Dump the systemd state without rate limits" +msgstr "سۈرئەت چەكلىمىسىز ھالدا systemd ھالىتىنى چىقار" + +#: src/core/org.freedesktop.systemd1.policy.in:75 +msgid "" +"Authentication is required to dump the systemd state without rate limits." +msgstr "سۈرئەت چەكلىمىسىز ھالدا systemd ھالىتىنى چىقىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:13 +msgid "Create a home area" +msgstr "باش مۇندەرىجە رايونىنى قۇر" + +#: src/home/org.freedesktop.home1.policy:14 +msgid "Authentication is required to create a user's home area." +msgstr "ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونىنى قۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:23 +msgid "Remove a home area" +msgstr "باش مۇندەرىجە رايونىنى ئۆچۈر" + +#: src/home/org.freedesktop.home1.policy:24 +msgid "Authentication is required to remove a user's home area." +msgstr "ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونىنى ئۆچۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:33 +msgid "Check credentials of a home area" +msgstr "باش مۇندەرىجە رايونىنىڭ ئىسپات ئۇچۇرىنى تەكشۈر" + +#: src/home/org.freedesktop.home1.policy:34 +msgid "" +"Authentication is required to check credentials against a user's home area." +msgstr "ئىسپات ئۇچۇرىنى ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونى بىلەن سېلىشتۇرۇپ تەكشۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:43 +msgid "Update a home area" +msgstr "باش مۇندەرىجە رايونىنى يېڭىلا" + +#: src/home/org.freedesktop.home1.policy:44 +msgid "Authentication is required to update a user's home area." +msgstr "ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونىنى يېڭىلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:53 +msgid "Update your home area" +msgstr "باش مۇندەرىجە رايونىڭىزنى يېڭىلاڭ" + +#: src/home/org.freedesktop.home1.policy:54 +msgid "Authentication is required to update your home area." +msgstr "باش مۇندەرىجە رايونىڭىزنى يېڭىلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:63 +msgid "Resize a home area" +msgstr "باش مۇندەرىجە رايونىنىڭ چوڭ-كىچىكلىكىنى ئۆزگەرت" + +#: src/home/org.freedesktop.home1.policy:64 +msgid "Authentication is required to resize a user's home area." +msgstr "ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونىنىڭ چوڭ-كىچىكلىكىنى ئۆزگەرتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:73 +msgid "Change password of a home area" +msgstr "باش مۇندەرىجە رايونىنىڭ پارولىنى ئۆزگەرت" + +#: src/home/org.freedesktop.home1.policy:74 +msgid "" +"Authentication is required to change the password of a user's home area." +msgstr "ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونى پارولىنى ئۆزگەرتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:83 +msgid "Activate a home area" +msgstr "باش مۇندەرىجە رايونىنى ئاكتىپلا" + +#: src/home/org.freedesktop.home1.policy:84 +msgid "Authentication is required to activate a user's home area." +msgstr "ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونىنى ئاكتىپلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:93 +msgid "Manage Home Directory Signing Keys" +msgstr "باش مۇندەرىجە ئىمزا ئاچقۇچلىرىنى باشقۇر" + +#: src/home/org.freedesktop.home1.policy:94 +msgid "Authentication is required to manage signing keys for home directories." +msgstr "باش مۇندەرىجىلەرنىڭ ئىمزا ئاچقۇچلىرىنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/pam_systemd_home.c:330 +#, c-format +msgid "" +"Home of user %s is currently absent, please plug in the necessary storage " +"device or backing file system." +msgstr "%s ئىشلەتكۈچىنىڭ باش مۇندەرىجىسى ھازىر مەۋجۇت ئەمەس، زۆرۈر ساقلاش ئۈسكۈنىسى ياكى تۆۋەن قەۋەت ھۆججەت سىستېمىسىنى ئۇلاڭ." + +#: src/home/pam_systemd_home.c:335 +#, c-format +msgid "Too frequent login attempts for user %s, try again later." +msgstr "%s ئىشلەتكۈچىنىڭ كىرىش سىنىقى بەك كۆپ قېتىم بولدى، كېيىن قايتا سىناپ بېقىڭ." + +#: src/home/pam_systemd_home.c:347 +msgid "Password: " +msgstr "پارول: " + +#: src/home/pam_systemd_home.c:349 +#, c-format +msgid "Password incorrect or not sufficient for authentication of user %s." +msgstr "%s ئىشلەتكۈچىنىڭ پارولى خاتا ياكى دەلىللەش ئۈچۈن يېتەرلىك ئەمەس." + +#: src/home/pam_systemd_home.c:350 +msgid "Sorry, try again: " +msgstr "كەچۈرۈڭ، قايتا سىناڭ: " + +#: src/home/pam_systemd_home.c:372 +msgid "Recovery key: " +msgstr "ئەسلىگە كەلتۈرۈش ئاچقۇچى: " + +#: src/home/pam_systemd_home.c:374 +#, c-format +msgid "" +"Password/recovery key incorrect or not sufficient for authentication of user " +"%s." +msgstr "%s ئىشلەتكۈچىنىڭ پارول/ئەسلىگە كەلتۈرۈش ئاچقۇچى خاتا ياكى دەلىللەش ئۈچۈن يېتەرلىك ئەمەس." + +#: src/home/pam_systemd_home.c:375 +msgid "Sorry, reenter recovery key: " +msgstr "كەچۈرۈڭ، ئەسلىگە كەلتۈرۈش ئاچقۇچىنى قايتا كىرگۈزۈڭ: " + +#: src/home/pam_systemd_home.c:395 +#, c-format +msgid "Security token of user %s not inserted." +msgstr "%s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنى چېتىلمىغان." + +#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +msgid "Try again with password: " +msgstr "پارول بىلەن قايتا سىناڭ: " + +#: src/home/pam_systemd_home.c:398 +#, c-format +msgid "" +"Password incorrect or not sufficient, and configured security token of user " +"%s not inserted." +msgstr "پارول خاتا ياكى يېتەرلىك ئەمەس، شۇنداقلا %s ئىشلەتكۈچىگە سەپلىگەن بىخەتەرلىك توكىنى چېتىلمىغان." + +#: src/home/pam_systemd_home.c:418 +msgid "Security token PIN: " +msgstr "بىخەتەرلىك توكىنى PIN: " + +#: src/home/pam_systemd_home.c:435 +#, c-format +msgid "Please authenticate physically on security token of user %s." +msgstr "ئىلتىماس، %s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنىدا فىزىكىلىق دەلىللەشنى تاماملاڭ." + +#: src/home/pam_systemd_home.c:446 +#, c-format +msgid "Please confirm presence on security token of user %s." +msgstr "ئىلتىماس، %s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنىدا ھازىر ئىكەنلىكىڭىزنى جەزملەڭ." + +#: src/home/pam_systemd_home.c:457 +#, c-format +msgid "Please verify user on security token of user %s." +msgstr "ئىلتىماس، %s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنىدىكى ئىشلەتكۈچىنى تەكشۈرۈپ دەلىللەڭ." + +#: src/home/pam_systemd_home.c:466 +msgid "" +"Security token PIN is locked, please unlock it first. (Hint: Removal and re-" +"insertion might suffice.)" +msgstr "بىخەتەرلىك توكىنى PIN قۇلۇپلانغان، ئالدى بىلەن قۇلۇپنى ئېچىڭ. (ئەسكەرتىش: چېتىپ تۇرغاننى چىقىرىپ قايتا چېتىش كۇپايە بولۇشى مۇمكىن.)" + +#: src/home/pam_systemd_home.c:474 +#, c-format +msgid "Security token PIN incorrect for user %s." +msgstr "%s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنى PIN خاتا." + +#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 +#: src/home/pam_systemd_home.c:513 +msgid "Sorry, retry security token PIN: " +msgstr "كەچۈرۈڭ، بىخەتەرلىك توكىنى PIN نى قايتا سىناڭ: " + +#: src/home/pam_systemd_home.c:493 +#, c-format +msgid "Security token PIN of user %s incorrect (only a few tries left!)" +msgstr "%s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنى PIN خاتا (پەقەت بىر قانچەلا سىناق پۇرسىتى قالدى!)" + +#: src/home/pam_systemd_home.c:512 +#, c-format +msgid "Security token PIN of user %s incorrect (only one try left!)" +msgstr "%s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنى PIN خاتا (پەقەت بىرلا قېتىملىق سىناق پۇرسىتى قالدى!)" + +#: src/home/pam_systemd_home.c:679 +#, c-format +msgid "Home of user %s is currently not active, please log in locally first." +msgstr "%s ئىشلەتكۈچىنىڭ باش مۇندەرىجىسى ھازىر ئاكتىپ ئەمەس، ئالدى بىلەن يەرلىك كىرىڭ." + +#: src/home/pam_systemd_home.c:681 +#, c-format +msgid "Home of user %s is currently locked, please unlock locally first." +msgstr "%s ئىشلەتكۈچىنىڭ باش مۇندەرىجىسى ھازىر قۇلۇپلانغان، ئالدى بىلەن يەرلىكتە قۇلۇپنى ئېچىڭ." + +#: src/home/pam_systemd_home.c:715 +#, c-format +msgid "Too many unsuccessful login attempts for user %s, refusing." +msgstr "%s ئىشلەتكۈچىنىڭ مۇۋەپپەقىيەتسىز كىرىش سىنىقى بەك كۆپ بولدى، رەت قىلىندى." + +#: src/home/pam_systemd_home.c:1012 +msgid "User record is blocked, prohibiting access." +msgstr "ئىشلەتكۈچى خاتىرىسى توسۇلغان، زىيارەت چەكلەندى." + +#: src/home/pam_systemd_home.c:1016 +msgid "User record is not valid yet, prohibiting access." +msgstr "ئىشلەتكۈچى خاتىرىسى تېخى كۈچكە ئىگە ئەمەس، زىيارەت چەكلەندى." + +#: src/home/pam_systemd_home.c:1020 +msgid "User record is not valid anymore, prohibiting access." +msgstr "ئىشلەتكۈچى خاتىرىسى ئەمدى كۈچكە ئىگە ئەمەس، زىيارەت چەكلەندى." + +#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +msgid "User record not valid, prohibiting access." +msgstr "ئىشلەتكۈچى خاتىرىسى كۈچكە ئىگە ئەمەس، زىيارەت چەكلەندى." + +#: src/home/pam_systemd_home.c:1035 +#, c-format +msgid "Too many logins, try again in %s." +msgstr "كىرىش قېتىملىرى بەك كۆپ، %s دىن كېيىن قايتا سىناڭ." + +#: src/home/pam_systemd_home.c:1046 +msgid "Password change required." +msgstr "پارولنى ئۆزگەرتىش زۆرۈر." + +#: src/home/pam_systemd_home.c:1050 +msgid "Password expired, change required." +msgstr "پارولنىڭ مۇددىتى توشتى، ئۆزگەرتىش زۆرۈر." + +#: src/home/pam_systemd_home.c:1056 +msgid "Password is expired, but can't change, refusing login." +msgstr "پارولنىڭ مۇددىتى توشقان، ئەمما ئۆزگەرتكىلى بولمايدۇ، كىرىش رەت قىلىندى." + +#: src/home/pam_systemd_home.c:1060 +msgid "Password will expire soon, please change." +msgstr "پارولنىڭ مۇددىتى تېزلا توشىدۇ، ۋاقتىدا ئۆزگەرتىڭ." + +#: src/hostname/org.freedesktop.hostname1.policy:20 +msgid "Set hostname" +msgstr "ساھىبجامال نامىنى بەلگىلە" + +#: src/hostname/org.freedesktop.hostname1.policy:21 +msgid "Authentication is required to set the local hostname." +msgstr "يەرلىك ساھىبجامال نامىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/hostname/org.freedesktop.hostname1.policy:30 +msgid "Set static hostname" +msgstr "مۇقىم ساھىبجامال نامىنى بەلگىلە" + +#: src/hostname/org.freedesktop.hostname1.policy:31 +msgid "" +"Authentication is required to set the statically configured local hostname, " +"as well as the pretty hostname." +msgstr "مۇقىم تەڭشەلگەن يەرلىك ساھىبجامال نامى ۋە چىرايلىق ساھىبجامال نامىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/hostname/org.freedesktop.hostname1.policy:41 +msgid "Set machine information" +msgstr "ماشىنا ئۇچۇرىنى بەلگىلە" + +#: src/hostname/org.freedesktop.hostname1.policy:42 +msgid "Authentication is required to set local machine information." +msgstr "يەرلىك ماشىنا ئۇچۇرىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/hostname/org.freedesktop.hostname1.policy:51 +msgid "Get product UUID" +msgstr "مەھسۇلات UUID نى ئال" + +#: src/hostname/org.freedesktop.hostname1.policy:52 +msgid "Authentication is required to get product UUID." +msgstr "مەھسۇلات UUID نى ئېلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/hostname/org.freedesktop.hostname1.policy:61 +msgid "Get hardware serial number" +msgstr "قاتتىق دېتال تەرتىپ نومۇرىنى ئال" + +#: src/hostname/org.freedesktop.hostname1.policy:62 +msgid "Authentication is required to get hardware serial number." +msgstr "قاتتىق دېتال تەرتىپ نومۇرىنى ئېلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/hostname/org.freedesktop.hostname1.policy:71 +msgid "Get system description" +msgstr "سىستېما چۈشەندۈرۈشىنى ئال" + +#: src/hostname/org.freedesktop.hostname1.policy:72 +msgid "Authentication is required to get system description." +msgstr "سىستېما چۈشەندۈرۈشىنى ئېلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/import/org.freedesktop.import1.policy:22 +msgid "Import a disk image" +msgstr "دىسكا ئەكسىنى ئەكىر" + +#: src/import/org.freedesktop.import1.policy:23 +msgid "Authentication is required to import an image." +msgstr "ئەكسنى ئەكىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/import/org.freedesktop.import1.policy:32 +msgid "Export a disk image" +msgstr "دىسكا ئەكسىنى چىقار" + +#: src/import/org.freedesktop.import1.policy:33 +msgid "Authentication is required to export disk image." +msgstr "دىسكا ئەكسىنى چىقىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/import/org.freedesktop.import1.policy:42 +msgid "Download a disk image" +msgstr "دىسكا ئەكسىنى چۈشۈر" + +#: src/import/org.freedesktop.import1.policy:43 +msgid "Authentication is required to download a disk image." +msgstr "دىسكا ئەكسىنى چۈشۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/import/org.freedesktop.import1.policy:52 +msgid "Cancel transfer of a disk image" +msgstr "دىسكا ئەكسىنى يوللاشنى بىكار قىل" + +#: src/import/org.freedesktop.import1.policy:53 +msgid "" +"Authentication is required to cancel the ongoing transfer of a disk image." +msgstr "داۋاملىشىۋاتقان دىسكا ئەكسى يوللاشنى بىكار قىلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/locale/org.freedesktop.locale1.policy:22 +msgid "Set system locale" +msgstr "سىستېما يەرلىك تەڭشىكىنى بەلگىلە" + +#: src/locale/org.freedesktop.locale1.policy:23 +msgid "Authentication is required to set the system locale." +msgstr "سىستېما يەرلىك تەڭشىكىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/locale/org.freedesktop.locale1.policy:33 +msgid "Set system keyboard settings" +msgstr "سىستېما كۇنۇپكا تاختىسى تەڭشىكىنى بەلگىلە" + +#: src/locale/org.freedesktop.locale1.policy:34 +msgid "Authentication is required to set the system keyboard settings." +msgstr "سىستېما كۇنۇپكا تاختىسى تەڭشىكىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:22 +msgid "Allow applications to inhibit system shutdown" +msgstr "ئەپنىڭ سىستېما تاقاشنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:23 +msgid "" +"Authentication is required for an application to inhibit system shutdown." +msgstr "ئەپنىڭ سىستېما تاقاشنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:33 +msgid "Allow applications to delay system shutdown" +msgstr "ئەپنىڭ سىستېما تاقاشنى كېچىكتۈرۈشىگە يول قوي" + +#: src/login/org.freedesktop.login1.policy:34 +msgid "Authentication is required for an application to delay system shutdown." +msgstr "ئەپنىڭ سىستېما تاقاشنى كېچىكتۈرۈشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:44 +msgid "Allow applications to inhibit system sleep" +msgstr "ئەپنىڭ سىستېما ئۇخلاشنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:45 +msgid "Authentication is required for an application to inhibit system sleep." +msgstr "ئەپنىڭ سىستېما ئۇخلاشنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:55 +msgid "Allow applications to delay system sleep" +msgstr "ئەپنىڭ سىستېما ئۇخلاشنى كېچىكتۈرۈشىگە يول قوي" + +#: src/login/org.freedesktop.login1.policy:56 +msgid "Authentication is required for an application to delay system sleep." +msgstr "ئەپنىڭ سىستېما ئۇخلاشنى كېچىكتۈرۈشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:65 +msgid "Allow applications to inhibit automatic system suspend" +msgstr "ئەپنىڭ سىستېمىنىڭ ئاپتوماتىك توختىتىلىشىنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:66 +msgid "" +"Authentication is required for an application to inhibit automatic system " +"suspend." +msgstr "ئەپنىڭ سىستېمىنىڭ ئاپتوماتىك توختىتىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:75 +msgid "Allow applications to inhibit system handling of the power key" +msgstr "ئەپنىڭ سىستېمىنىڭ توك كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:76 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the power key." +msgstr "ئەپنىڭ سىستېمىنىڭ توك كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:86 +msgid "Allow applications to inhibit system handling of the suspend key" +msgstr "ئەپنىڭ سىستېمىنىڭ توختىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:87 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the suspend key." +msgstr "ئەپنىڭ سىستېمىنىڭ توختىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:97 +msgid "Allow applications to inhibit system handling of the hibernate key" +msgstr "ئەپنىڭ سىستېمىنىڭ ئۇزۇن ئۇخلاش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:98 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the hibernate key." +msgstr "ئەپنىڭ سىستېمىنىڭ ئۇزۇن ئۇخلاش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:107 +msgid "Allow applications to inhibit system handling of the lid switch" +msgstr "ئەپنىڭ سىستېمىنىڭ خاتىرە كومپيۇتېر قاپاق ئالماشتۇرغۇچىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:108 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the lid switch." +msgstr "ئەپنىڭ سىستېمىنىڭ خاتىرە كومپيۇتېر قاپاق ئالماشتۇرغۇچىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:117 +msgid "Allow applications to inhibit system handling of the reboot key" +msgstr "ئەپنىڭ سىستېمىنىڭ قايتا قوزغىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:118 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the reboot key." +msgstr "ئەپنىڭ سىستېمىنىڭ قايتا قوزغىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:128 +msgid "Allow non-logged-in user to run programs" +msgstr "تىزىملاتمىغان ئىشلەتكۈچىنىڭ پروگرامما ئىجرا قىلىشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:129 +msgid "Explicit request is required to run programs as a non-logged-in user." +msgstr "تىزىملاتمىغان ئىشلەتكۈچى سۈپىتىدە پروگرامما ئىجرا قىلىش ئۈچۈن ئېنىق ئىلتىماس تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:138 +msgid "Allow non-logged-in users to run programs" +msgstr "تىزىملاتمىغان ئىشلەتكۈچىلەرنىڭ پروگرامما ئىجرا قىلىشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:139 +msgid "Authentication is required to run programs as a non-logged-in user." +msgstr "تىزىملاتمىغان ئىشلەتكۈچى سۈپىتىدە پروگرامما ئىجرا قىلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:148 +msgid "Allow attaching devices to seats" +msgstr "ئۈسكۈنىنى seat قا ئۇلاشقا يول قوي" + +#: src/login/org.freedesktop.login1.policy:149 +msgid "Authentication is required to attach a device to a seat." +msgstr "ئۈسكۈنىنى seat قا ئۇلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:159 +msgid "Flush device to seat attachments" +msgstr "ئۈسكۈنە بىلەن seat ئۇلىنىشىنى يېڭىلا" + +#: src/login/org.freedesktop.login1.policy:160 +msgid "Authentication is required to reset how devices are attached to seats." +msgstr "ئۈسكۈنىلەرنىڭ seat قا قانداق ئۇلىنىدىغانلىقىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:169 +msgid "Power off the system" +msgstr "سىستېمىنى تاقا" + +#: src/login/org.freedesktop.login1.policy:170 +msgid "Authentication is required to power off the system." +msgstr "سىستېمىنى تاقاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:180 +msgid "Power off the system while other users are logged in" +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى تاقا" + +#: src/login/org.freedesktop.login1.policy:181 +msgid "" +"Authentication is required to power off the system while other users are " +"logged in." +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى تاقاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:191 +msgid "Power off the system while an application is inhibiting this" +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى تاقا" + +#: src/login/org.freedesktop.login1.policy:192 +msgid "" +"Authentication is required to power off the system while an application is " +"inhibiting this." +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى تاقاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:202 +msgid "Reboot the system" +msgstr "سىستېمىنى قايتا قوزغات" + +#: src/login/org.freedesktop.login1.policy:203 +msgid "Authentication is required to reboot the system." +msgstr "سىستېمىنى قايتا قوزغىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:213 +msgid "Reboot the system while other users are logged in" +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى قايتا قوزغات" + +#: src/login/org.freedesktop.login1.policy:214 +msgid "" +"Authentication is required to reboot the system while other users are logged " +"in." +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى قايتا قوزغىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:224 +msgid "Reboot the system while an application is inhibiting this" +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى قايتا قوزغات" + +#: src/login/org.freedesktop.login1.policy:225 +msgid "" +"Authentication is required to reboot the system while an application is " +"inhibiting this." +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى قايتا قوزغىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:235 +msgid "Halt the system" +msgstr "سىستېمىنى توختات" + +#: src/login/org.freedesktop.login1.policy:236 +msgid "Authentication is required to halt the system." +msgstr "سىستېمىنى توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:246 +msgid "Halt the system while other users are logged in" +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى توختات" + +#: src/login/org.freedesktop.login1.policy:247 +msgid "" +"Authentication is required to halt the system while other users are logged " +"in." +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:257 +msgid "Halt the system while an application is inhibiting this" +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى توختات" + +#: src/login/org.freedesktop.login1.policy:258 +msgid "" +"Authentication is required to halt the system while an application is " +"inhibiting this." +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:268 +msgid "Suspend the system" +msgstr "سىستېمىنى ۋاقىتلىق توختات" + +#: src/login/org.freedesktop.login1.policy:269 +msgid "Authentication is required to suspend the system." +msgstr "سىستېمىنى ۋاقىتلىق توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:278 +msgid "Suspend the system while other users are logged in" +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى ۋاقىتلىق توختات" + +#: src/login/org.freedesktop.login1.policy:279 +msgid "" +"Authentication is required to suspend the system while other users are " +"logged in." +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى ۋاقىتلىق توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:289 +msgid "Suspend the system while an application is inhibiting this" +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى ۋاقىتلىق توختات" + +#: src/login/org.freedesktop.login1.policy:290 +msgid "" +"Authentication is required to suspend the system while an application is " +"inhibiting this." +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى ۋاقىتلىق توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:300 +msgid "Hibernate the system" +msgstr "سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈز" + +#: src/login/org.freedesktop.login1.policy:301 +msgid "Authentication is required to hibernate the system." +msgstr "سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈزۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:310 +msgid "Hibernate the system while other users are logged in" +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈز" + +#: src/login/org.freedesktop.login1.policy:311 +msgid "" +"Authentication is required to hibernate the system while other users are " +"logged in." +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈزۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:321 +msgid "Hibernate the system while an application is inhibiting this" +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈز" + +#: src/login/org.freedesktop.login1.policy:322 +msgid "" +"Authentication is required to hibernate the system while an application is " +"inhibiting this." +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈزۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:332 +msgid "Manage active sessions, users and seats" +msgstr "ئاكتىپ سۆھبەتلەر، ئىشلەتكۈچىلەر ۋە seats نى باشقۇر" + +#: src/login/org.freedesktop.login1.policy:333 +msgid "Authentication is required to manage active sessions, users and seats." +msgstr "ئاكتىپ سۆھبەتلەر، ئىشلەتكۈچىلەر ۋە seats نى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:342 +msgid "Lock or unlock active sessions" +msgstr "ئاكتىپ سۆھبەتلەرنى قۇلۇپلا ياكى قۇلۇپنى ئاچ" + +#: src/login/org.freedesktop.login1.policy:343 +msgid "Authentication is required to lock or unlock active sessions." +msgstr "ئاكتىپ سۆھبەتلەرنى قۇلۇپلاش ياكى قۇلۇپنى ئېچىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:352 +msgid "Set the reboot \"reason\" in the kernel" +msgstr "يادرودا قايتا قوزغىتىش «سەۋەبى» نى بەلگىلە" + +#: src/login/org.freedesktop.login1.policy:353 +msgid "Authentication is required to set the reboot \"reason\" in the kernel." +msgstr "يادرودا قايتا قوزغىتىش «سەۋەبى» نى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:363 +msgid "Indicate to the firmware to boot to setup interface" +msgstr "مىكروپروگراممىغا تەڭشەك كۆرۈنمە يۈزىگە قوزغىتىشنى كۆرسەت" + +#: src/login/org.freedesktop.login1.policy:364 +msgid "" +"Authentication is required to indicate to the firmware to boot to setup " +"interface." +msgstr "مىكروپروگراممىغا تەڭشەك كۆرۈنمە يۈزىگە قوزغىتىشنى كۆرسىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:374 +msgid "Indicate to the boot loader to boot to the boot loader menu" +msgstr "قوزغىتىش يۈكلەش پروگراممىسىغا ئۆزىنىڭ تىزىملىكىگە كىرىشنى كۆرسەت" + +#: src/login/org.freedesktop.login1.policy:375 +msgid "" +"Authentication is required to indicate to the boot loader to boot to the " +"boot loader menu." +msgstr "قوزغىتىش يۈكلەش پروگراممىسىغا ئۆزىنىڭ تىزىملىكىگە كىرىشنى كۆرسىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:385 +msgid "Indicate to the boot loader to boot a specific entry" +msgstr "قوزغىتىش يۈكلەش پروگراممىسىغا بەلگىلەنگەن بىر تۈرگە كىرىشنى كۆرسەت" + +#: src/login/org.freedesktop.login1.policy:386 +msgid "" +"Authentication is required to indicate to the boot loader to boot into a " +"specific boot loader entry." +msgstr "قوزغىتىش يۈكلەش پروگراممىسىغا بەلگىلەنگەن بىر تۈرگە كىرىشنى كۆرسىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:396 +msgid "Set a wall message" +msgstr "wall ئۇچۇرىنى بەلگىلە" + +#: src/login/org.freedesktop.login1.policy:397 +msgid "Authentication is required to set a wall message." +msgstr "wall ئۇچۇرىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:406 +msgid "Change Session" +msgstr "سۆھبەتنى ئۆزگەرت" + +#: src/login/org.freedesktop.login1.policy:407 +msgid "Authentication is required to change the virtual terminal." +msgstr "مەۋھۇم تېرمىنالنى ئۆزگەرتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:22 +msgid "Log into a local container" +msgstr "يەرلىك كونتېينېرغا كىر" + +#: src/machine/org.freedesktop.machine1.policy:23 +msgid "Authentication is required to log into a local container." +msgstr "يەرلىك كونتېينېرغا كىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:32 +msgid "Log into the local host" +msgstr "يەرلىك ئاساسىي كومپيۇتېرغا كىر" + +#: src/machine/org.freedesktop.machine1.policy:33 +msgid "Authentication is required to log into the local host." +msgstr "يەرلىك ئاساسىي كومپيۇتېرغا كىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:42 +msgid "Acquire a shell in a local container" +msgstr "يەرلىك كونتېينېردا shell غا ئېرىش" + +#: src/machine/org.freedesktop.machine1.policy:43 +msgid "Authentication is required to acquire a shell in a local container." +msgstr "يەرلىك كونتېينېردا shell غا ئېرىشىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:53 +msgid "Acquire a shell on the local host" +msgstr "يەرلىك ئاساسىي كومپيۇتېردا shell غا ئېرىش" + +#: src/machine/org.freedesktop.machine1.policy:54 +msgid "Authentication is required to acquire a shell on the local host." +msgstr "يەرلىك ئاساسىي كومپيۇتېردا shell غا ئېرىشىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:64 +msgid "Acquire a pseudo TTY in a local container" +msgstr "يەرلىك كونتېينېردا pseudo TTY غا ئېرىش" + +#: src/machine/org.freedesktop.machine1.policy:65 +msgid "" +"Authentication is required to acquire a pseudo TTY in a local container." +msgstr "يەرلىك كونتېينېردا pseudo TTY غا ئېرىشىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:74 +msgid "Acquire a pseudo TTY on the local host" +msgstr "يەرلىك ئاساسىي كومپيۇتېردا pseudo TTY غا ئېرىش" + +#: src/machine/org.freedesktop.machine1.policy:75 +msgid "Authentication is required to acquire a pseudo TTY on the local host." +msgstr "يەرلىك ئاساسىي كومپيۇتېردا pseudo TTY غا ئېرىشىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:84 +msgid "Manage local virtual machines and containers" +msgstr "يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېرلارنى باشقۇر" + +#: src/machine/org.freedesktop.machine1.policy:85 +msgid "" +"Authentication is required to manage local virtual machines and containers." +msgstr "يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېرلارنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:95 +msgid "Create a local virtual machine or container" +msgstr "يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېر قۇر" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېر قۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:106 +msgid "Register a local virtual machine or container" +msgstr "يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېرنى تىزىملا" + +#: src/machine/org.freedesktop.machine1.policy:107 +msgid "" +"Authentication is required to register a local virtual machine or container." +msgstr "يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېرنى تىزىملاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:116 +msgid "Manage local virtual machine and container images" +msgstr "يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېر ئەكسلىرىنى باشقۇر" + +#: src/machine/org.freedesktop.machine1.policy:117 +msgid "" +"Authentication is required to manage local virtual machine and container " +"images." +msgstr "يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېر ئەكسلىرىنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:22 +msgid "Set NTP servers" +msgstr "NTP مۇلازىمېتىرلىرىنى بەلگىلە" + +#: src/network/org.freedesktop.network1.policy:23 +msgid "Authentication is required to set NTP servers." +msgstr "NTP مۇلازىمېتىرلىرىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:33 +#: src/resolve/org.freedesktop.resolve1.policy:44 +msgid "Set DNS servers" +msgstr "DNS مۇلازىمېتىرلىرىنى بەلگىلە" + +#: src/network/org.freedesktop.network1.policy:34 +#: src/resolve/org.freedesktop.resolve1.policy:45 +msgid "Authentication is required to set DNS servers." +msgstr "DNS مۇلازىمېتىرلىرىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:44 +#: src/resolve/org.freedesktop.resolve1.policy:55 +msgid "Set domains" +msgstr "دومېنلارنى بەلگىلە" + +#: src/network/org.freedesktop.network1.policy:45 +#: src/resolve/org.freedesktop.resolve1.policy:56 +msgid "Authentication is required to set domains." +msgstr "دومېنلارنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:55 +#: src/resolve/org.freedesktop.resolve1.policy:66 +msgid "Set default route" +msgstr "كۆڭۈلدىكى route نى بەلگىلە" + +#: src/network/org.freedesktop.network1.policy:56 +#: src/resolve/org.freedesktop.resolve1.policy:67 +msgid "Authentication is required to set default route." +msgstr "كۆڭۈلدىكى route نى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:66 +#: src/resolve/org.freedesktop.resolve1.policy:77 +msgid "Enable/disable LLMNR" +msgstr "LLMNR نى قوزغات/چەكلە" + +#: src/network/org.freedesktop.network1.policy:67 +#: src/resolve/org.freedesktop.resolve1.policy:78 +msgid "Authentication is required to enable or disable LLMNR." +msgstr "LLMNR نى قوزغىتىش ياكى چەكلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:77 +#: src/resolve/org.freedesktop.resolve1.policy:88 +msgid "Enable/disable multicast DNS" +msgstr "multicast DNS نى قوزغات/چەكلە" + +#: src/network/org.freedesktop.network1.policy:78 +#: src/resolve/org.freedesktop.resolve1.policy:89 +msgid "Authentication is required to enable or disable multicast DNS." +msgstr "multicast DNS نى قوزغىتىش ياكى چەكلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:88 +#: src/resolve/org.freedesktop.resolve1.policy:99 +msgid "Enable/disable DNS over TLS" +msgstr "DNS over TLS نى قوزغات/چەكلە" + +#: src/network/org.freedesktop.network1.policy:89 +#: src/resolve/org.freedesktop.resolve1.policy:100 +msgid "Authentication is required to enable or disable DNS over TLS." +msgstr "DNS over TLS نى قوزغىتىش ياكى چەكلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:99 +#: src/resolve/org.freedesktop.resolve1.policy:110 +msgid "Enable/disable DNSSEC" +msgstr "DNSSEC نى قوزغات/چەكلە" + +#: src/network/org.freedesktop.network1.policy:100 +#: src/resolve/org.freedesktop.resolve1.policy:111 +msgid "Authentication is required to enable or disable DNSSEC." +msgstr "DNSSEC نى قوزغىتىش ياكى چەكلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:110 +#: src/resolve/org.freedesktop.resolve1.policy:121 +msgid "Set DNSSEC Negative Trust Anchors" +msgstr "DNSSEC سەلبىي ئىشەنچ لەڭگەرلىرىنى بەلگىلە" + +#: src/network/org.freedesktop.network1.policy:111 +#: src/resolve/org.freedesktop.resolve1.policy:122 +msgid "Authentication is required to set DNSSEC Negative Trust Anchors." +msgstr "DNSSEC سەلبىي ئىشەنچ لەڭگەرلىرىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:121 +msgid "Revert NTP settings" +msgstr "NTP تەڭشەكلىرىنى ئەسلىگە قايتۇر" + +#: src/network/org.freedesktop.network1.policy:122 +msgid "Authentication is required to reset NTP settings." +msgstr "NTP تەڭشەكلىرىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:132 +msgid "Revert DNS settings" +msgstr "DNS تەڭشەكلىرىنى ئەسلىگە قايتۇر" + +#: src/network/org.freedesktop.network1.policy:133 +msgid "Authentication is required to reset DNS settings." +msgstr "DNS تەڭشەكلىرىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:143 +msgid "DHCP server sends force renew message" +msgstr "DHCP مۇلازىمېتىرى زورلاپ يېڭىلاش ئۇچۇرىنى ئەۋەتىدۇ" + +#: src/network/org.freedesktop.network1.policy:144 +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "DHCP مۇلازىمېتىرىدىن زورلاپ يېڭىلاش ئۇچۇرى ئەۋەتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:154 +msgid "Renew dynamic addresses" +msgstr "ھەرىكەتچان ئادرېسلارنى يېڭىلا" + +#: src/network/org.freedesktop.network1.policy:155 +msgid "Authentication is required to renew dynamic addresses." +msgstr "ھەرىكەتچان ئادرېسلارنى يېڭىلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:165 +msgid "Reload network settings" +msgstr "تور تەڭشەكلىرىنى قايتا يۈكلە" + +#: src/network/org.freedesktop.network1.policy:166 +msgid "Authentication is required to reload network settings." +msgstr "تور تەڭشەكلىرىنى قايتا يۈكلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:176 +msgid "Reconfigure network interface" +msgstr "تور ئۇلاش ئېغىزىنى قايتا سەپلە" + +#: src/network/org.freedesktop.network1.policy:177 +msgid "Authentication is required to reconfigure network interface." +msgstr "تور ئۇلاش ئېغىزىنى قايتا سەپلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:187 +msgid "Specify whether persistent storage for systemd-networkd is available" +msgstr "systemd-networkd ئۈچۈن تۇراقلىق ساقلاش ئىشلەتكىلى بولىدىغان-بولمايدىغانلىقىنى بەلگىلە" + +#: src/network/org.freedesktop.network1.policy:188 +msgid "" +"Authentication is required to specify whether persistent storage for systemd-" +"networkd is available." +msgstr "systemd-networkd ئۈچۈن تۇراقلىق ساقلاش ئىشلەتكىلى بولىدىغان-بولمايدىغانلىقىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:198 +msgid "Manage network links" +msgstr "تور ئۇلىنىشلىرىنى باشقۇر" + +#: src/network/org.freedesktop.network1.policy:199 +msgid "Authentication is required to manage network links." +msgstr "تور ئۇلىنىشلىرىنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/portable/org.freedesktop.portable1.policy:13 +msgid "Inspect a portable service image" +msgstr "portable service image نى تەكشۈر" + +#: src/portable/org.freedesktop.portable1.policy:14 +msgid "Authentication is required to inspect a portable service image." +msgstr "portable service image نى تەكشۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/portable/org.freedesktop.portable1.policy:23 +msgid "Attach or detach a portable service image" +msgstr "portable service image نى ئۇلا ياكى ئايرى" + +#: src/portable/org.freedesktop.portable1.policy:24 +msgid "" +"Authentication is required to attach or detach a portable service image." +msgstr "portable service image نى ئۇلاش ياكى ئايرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/portable/org.freedesktop.portable1.policy:34 +msgid "Delete or modify portable service image" +msgstr "portable service image نى ئۆچۈر ياكى ئۆزگەرت" + +#: src/portable/org.freedesktop.portable1.policy:35 +msgid "" +"Authentication is required to delete or modify a portable service image." +msgstr "portable service image نى ئۆچۈرۈش ياكى ئۆزگەرتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:22 +msgid "Register a DNS-SD service" +msgstr "DNS-SD مۇلازىمىتىنى تىزىملا" + +#: src/resolve/org.freedesktop.resolve1.policy:23 +msgid "Authentication is required to register a DNS-SD service." +msgstr "DNS-SD مۇلازىمىتىنى تىزىملاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:33 +msgid "Unregister a DNS-SD service" +msgstr "DNS-SD مۇلازىمىتىنى تىزىمدىن چىقار" + +#: src/resolve/org.freedesktop.resolve1.policy:34 +msgid "Authentication is required to unregister a DNS-SD service." +msgstr "DNS-SD مۇلازىمىتىنى تىزىمدىن چىقىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:132 +msgid "Revert name resolution settings" +msgstr "ئات ئېچىش تەڭشەكلىرىنى ئەسلىگە قايتۇر" + +#: src/resolve/org.freedesktop.resolve1.policy:133 +msgid "Authentication is required to reset name resolution settings." +msgstr "ئات ئېچىش تەڭشەكلىرىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:143 +msgid "Subscribe query results" +msgstr "سۈرۈشتۈرۈش نەتىجىلىرىگە مۇشتەرى بول" + +#: src/resolve/org.freedesktop.resolve1.policy:144 +msgid "Authentication is required to subscribe query results." +msgstr "سۈرۈشتۈرۈش نەتىجىلىرىگە مۇشتەرى بولۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:154 +msgid "Subscribe to DNS configuration" +msgstr "DNS سەپلىمىسىگە مۇشتەرى بول" + +#: src/resolve/org.freedesktop.resolve1.policy:155 +msgid "Authentication is required to subscribe to DNS configuration." +msgstr "DNS سەپلىمىسىگە مۇشتەرى بولۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:165 +msgid "Dump cache" +msgstr "كېشنى چىقار" + +#: src/resolve/org.freedesktop.resolve1.policy:166 +msgid "Authentication is required to dump cache." +msgstr "كېشنى چىقىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:176 +msgid "Dump server state" +msgstr "مۇلازىمېتىر ھالىتىنى چىقار" + +#: src/resolve/org.freedesktop.resolve1.policy:177 +msgid "Authentication is required to dump server state." +msgstr "مۇلازىمېتىر ھالىتىنى چىقىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:187 +msgid "Dump statistics" +msgstr "ئىستاتىستىكىنى چىقار" + +#: src/resolve/org.freedesktop.resolve1.policy:188 +msgid "Authentication is required to dump statistics." +msgstr "ئىستاتىستىكىنى چىقىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:198 +msgid "Reset statistics" +msgstr "ئىستاتىستىكىنى قايتا بەلگىلە" + +#: src/resolve/org.freedesktop.resolve1.policy:199 +msgid "Authentication is required to reset statistics." +msgstr "ئىستاتىستىكىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +msgid "Check for system updates" +msgstr "سىستېما يېڭىلانمىلىرىنى تەكشۈر" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +msgid "Authentication is required to check for system updates." +msgstr "سىستېما يېڭىلانمىلىرىنى تەكشۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +msgid "Install system updates" +msgstr "سىستېما يېڭىلانمىلىرىنى ئورنات" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +msgid "Authentication is required to install system updates." +msgstr "سىستېما يېڭىلانمىلىرىنى ئورنىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +msgid "Install specific system version" +msgstr "بەلگىلەنگەن سىستېما نەشرىنى ئورنات" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +msgid "" +"Authentication is required to update the system to a specific (possibly old) " +"version." +msgstr "سىستېمىنى بەلگىلەنگەن (بەلكىم كونا) نەشرگە يېڭىلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 +msgid "Cleanup old system updates" +msgstr "كونا سىستېما يېڭىلانمىلىرىنى تازىلا" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 +msgid "Authentication is required to cleanup old system updates." +msgstr "كونا سىستېما يېڭىلانمىلىرىنى تازىلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +msgid "Manage optional features" +msgstr "تاللانما ئىقتىدارلارنى باشقۇر" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +msgid "Authentication is required to manage optional features." +msgstr "تاللانما ئىقتىدارلارنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/timedate/org.freedesktop.timedate1.policy:22 +msgid "Set system time" +msgstr "سىستېما ۋاقتىنى بەلگىلە" + +#: src/timedate/org.freedesktop.timedate1.policy:23 +msgid "Authentication is required to set the system time." +msgstr "سىستېما ۋاقتىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/timedate/org.freedesktop.timedate1.policy:33 +msgid "Set system timezone" +msgstr "سىستېما ۋاقىت رايونىنى بەلگىلە" + +#: src/timedate/org.freedesktop.timedate1.policy:34 +msgid "Authentication is required to set the system timezone." +msgstr "سىستېما ۋاقىت رايونىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/timedate/org.freedesktop.timedate1.policy:43 +msgid "Set RTC to local timezone or UTC" +msgstr "RTC نى يەرلىك ۋاقىت رايونى ياكى UTC غا بەلگىلە" + +#: src/timedate/org.freedesktop.timedate1.policy:44 +msgid "" +"Authentication is required to control whether the RTC stores the local or " +"UTC time." +msgstr "RTC نىڭ يەرلىك ۋاقىتنى ياكى UTC نى ساقلىشىنى كونترول قىلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/timedate/org.freedesktop.timedate1.policy:53 +msgid "Turn network time synchronization on or off" +msgstr "تور ۋاقتى ماس قەدەملىشىنى ئېچىش ياكى تاقاش" + +#: src/timedate/org.freedesktop.timedate1.policy:54 +msgid "" +"Authentication is required to control whether network time synchronization " +"shall be enabled." +msgstr "تور ۋاقتى ماس قەدەملىشىنى قوزغىتىش-قوزغاتماسلىقنى كونترول قىلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:372 +msgid "Authentication is required to start '$(unit)'." +msgstr "'$(unit)' نى قوزغىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:373 +msgid "Authentication is required to stop '$(unit)'." +msgstr "'$(unit)' نى توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:374 +msgid "Authentication is required to reload '$(unit)'." +msgstr "'$(unit)' نى قايتا يۈكلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:375 src/core/dbus-unit.c:376 +msgid "Authentication is required to restart '$(unit)'." +msgstr "'$(unit)' نى قايتا قوزغىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:568 +msgid "" +"Authentication is required to send a UNIX signal to the processes of '$" +"(unit)'." +msgstr "UNIX سىگنالىنى '$(unit)' نىڭ جەريانلىرىغا ئەۋەتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:621 +msgid "" +"Authentication is required to send a UNIX signal to the processes of " +"subgroup of '$(unit)'." +msgstr "UNIX سىگنالىنى '$(unit)' نىڭ تارماق گۇرۇپپا جەريانلىرىغا ئەۋەتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:649 +msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." +msgstr "'$(unit)' نىڭ «مەغلۇپ» ھالىتىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:679 +msgid "Authentication is required to set properties on '$(unit)'." +msgstr "'$(unit)' ئۈستىدىكى خاسلىقلارنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:776 +msgid "" +"Authentication is required to delete files and directories associated with '$" +"(unit)'." +msgstr "'$(unit)' بىلەن مۇناسىۋەتلىك ھۆججەت ۋە مۇندەرىجىلەرنى ئۆچۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:813 +msgid "" +"Authentication is required to freeze or thaw the processes of '$(unit)' unit." +msgstr "'$(unit)' بىرىكىنىڭ جەريانلىرىنى توڭلىتىش ياكى ئېچىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." diff --git a/po/uk.po b/po/uk.po index b19efa16cd526..45f816287fcc1 100644 --- a/po/uk.po +++ b/po/uk.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2026-02-26 13:58+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-07 01:58+0000\n" "Last-Translator: Yuri Chornoivan \n" "Language-Team: Ukrainian \n" @@ -19,7 +19,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1055,10 +1055,12 @@ msgid "DHCP server sends force renew message" msgstr "Сервер DHCP надсилає повідомлення щодо примусового оновлення" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" -"Для надсилання повідомлення щодо примусового оновлення слід пройти " -"розпізнавання." +"Для надсилання повідомлення від сервера DHCP щодо примусового оновлення слід " +"пройти розпізнавання." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" diff --git a/po/zh_CN.po b/po/zh_CN.po index 70cebffc2fcbb..ceeb96a72048f 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -10,14 +10,14 @@ # lumingzh , 2024, 2025, 2026. # z z <3397542367@qq.com>, 2025. # Hang Li , 2025. -# Jesse Guo , 2025. +# Jesse Guo , 2025, 2026. # Zongyuan He , 2025. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2026-02-27 16:58+0000\n" -"Last-Translator: lumingzh \n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-10 15:58+0000\n" +"Last-Translator: Jesse Guo \n" "Language-Team: Chinese (Simplified) \n" "Language: zh_CN\n" @@ -25,7 +25,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -948,8 +948,10 @@ msgid "DHCP server sends force renew message" msgstr "DHCP 服务器发送强制更新消息" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "发送强制更新消息需要认证。" +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "从 DHCP 服务器发送强制续约消息需要认证。" #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" diff --git a/po/zh_TW.po b/po/zh_TW.po index 683e08a985ba2..40e8668af782d 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2025-04-09 02:53+0000\n" "Last-Translator: hsu zangmen \n" "Language-Team: Chinese (Traditional) . + +__contains_word() { + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done +} + +_systemd_hwdb() { + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword + local i verb comps + + local -A OPTS=( + [STANDALONE]='-h --help --version -s --strict --usr' + [ARG]='-r --root' + ) + + local -A VERBS=( + [STANDALONE]='update' + [ARG]='query' + ) + + _init_completion || return + + if __contains_word "$prev" ${OPTS[ARG]}; then + case $prev in + --root|-r) + comps=$(compgen -A directory -- "$cur") + compopt -o dirnames + ;; + esac + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + fi + + for ((i=0; i < COMP_CWORD; i++)); do + if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]} && + ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then + verb=${COMP_WORDS[i]} + break + fi + done + + if [[ -z ${verb-} ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]} ${VERBS[*]}' -- "$cur") ) + return 0 + fi + + if [[ "$cur" = -* ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) + return 0 + fi + + COMPREPLY=( $(compgen -W '${comps-}' -- "$cur") ) + return 0 +} + +complete -F _systemd_hwdb systemd-hwdb diff --git a/shell-completion/bash/systemd-nspawn b/shell-completion/bash/systemd-nspawn index 574018b1fff20..85f3023083c55 100644 --- a/shell-completion/bash/systemd-nspawn +++ b/shell-completion/bash/systemd-nspawn @@ -69,15 +69,18 @@ _systemd_nspawn() { local -A OPTS=( [STANDALONE]='-h --help --version --private-network -b --boot --read-only -q --quiet --share-system --keep-unit -n --network-veth -j -x --ephemeral -a --as-pid2 -U --suppress-sync=yes - --cleanup' - [ARG]='-D --directory -u --user --uuid --capability --drop-capability --link-journal --bind --bind-ro + --cleanup --user --system' + [ARG]='-D --directory -u --uid --uuid --capability --drop-capability --link-journal --bind --bind-ro -M --machine -S --slice -E --setenv -Z --selinux-context -L --selinux-apifs-context --register --network-interface --network-bridge --personality -i --image --image-policy --tmpfs --volatile --network-macvlan --kill-signal --template --notify-ready --root-hash --chdir --pivot-root --property --private-users --private-users-ownership --network-namespace-path --network-ipvlan --network-veth-extra --network-zone -p --port --system-call-filter --overlay --overlay-ro --settings --rlimit --hostname --no-new-privileges --oom-score-adjust --cpu-affinity - --resolv-conf --timezone --root-hash-sig --background --oci-bundle --verity-data' + --resolv-conf --timezone --root-hash-sig --background --oci-bundle --verity-data + --restrict-address-families + --forward-journal --forward-journal-max-use --forward-journal-keep-free + --forward-journal-max-file-size --forward-journal-max-files' ) _init_completion || return @@ -88,7 +91,7 @@ _systemd_nspawn() { compopt -o nospace comps=$(compgen -S/ -A directory -- "$cur" ) ;; - --user|-u) + --uid|-u) comps=$( __get_users ) ;; --uuid|--root-hash) @@ -131,7 +134,7 @@ _systemd_nspawn() { comps='' ;; --register) - comps='yes no' + comps='yes no auto' ;; --network-interface) comps=$(__get_interfaces) diff --git a/shell-completion/bash/systemd-vmspawn b/shell-completion/bash/systemd-vmspawn index b17586de14555..efa0dae58de04 100644 --- a/shell-completion/bash/systemd-vmspawn +++ b/shell-completion/bash/systemd-vmspawn @@ -30,35 +30,44 @@ _systemd_vmspawn() { local -A OPTS=( [STANDALONE]='-h --help --version -q --quiet --no-pager -n --network-tap --network-user-mode --user --system -x --ephemeral' - [PATH]='-D --directory -i --image --linux --initrd --extra-drive --forward-journal' - [BOOL]='--kvm --vsock --tpm --secure-boot --discard-disk --register --pass-ssh-key' + [PATH]='-D --directory -i --image --linux --initrd --extra-drive --forward-journal --efi-nvram-template' + [BOOL]='--kvm --vsock --tpm --discard-disk --pass-ssh-key' + [TRISTATE]='--register --secure-boot' [FIRMWARE]='--firmware' + [FIRMWARE_FEATURES]='--firmware-features' [BIND]='--bind --bind-ro' [SSH_KEY]='--ssh-key' [CONSOLE]='--console' - [ARG]='--cpus --ram --vsock-cid -M --machine --uuid --private-users --background --set-credential --load-credential' + [ARG]='--cpus --ram --vsock-cid -M --machine --uuid --private-users --background --set-credential --load-credential --forward-journal-max-use --forward-journal-keep-free --forward-journal-max-file-size --forward-journal-max-files' [IMAGE_FORMAT]='--image-format' + [IMAGE_DISK_TYPE]='--image-disk-type' ) _init_completion || return if __contains_word "$prev" ${OPTS[BOOL]}; then comps='yes no' + elif __contains_word "$prev" ${OPTS[TRISTATE]}; then + comps='yes no auto' elif __contains_word "$prev" ${OPTS[PATH]}; then compopt -o nospace -o filenames comps=$(compgen -f -- "$cur" ) elif __contains_word "$prev" ${OPTS[FIRMWARE]}; then compopt -o nospace -o filenames - comps="list $(compgen -f -- "$cur" )" + comps="list describe $(compgen -f -- "$cur" )" + elif __contains_word "$prev" ${OPTS[FIRMWARE_FEATURES]}; then + comps='list' elif __contains_word "$prev" ${OPTS[BIND]}; then compopt -o nospace -o filenames comps=$(compgen -f -- "${cur}" ) elif __contains_word "$prev" ${OPTS[SSH_KEY]}; then comps='dsa ecdsa ecdsa-sk ed25519 ed25519-sk rsa' elif __contains_word "$prev" ${OPTS[CONSOLE]}; then - comps='interactive native gui' + comps='interactive native gui read-only headless' elif __contains_word "$prev" ${OPTS[IMAGE_FORMAT]}; then comps='raw qcow2' + elif __contains_word "$prev" ${OPTS[IMAGE_DISK_TYPE]}; then + comps='virtio-blk virtio-scsi nvme' elif __contains_word "$prev" ${OPTS[ARG]}; then comps='' else diff --git a/shell-completion/bash/varlinkctl b/shell-completion/bash/varlinkctl index e6557ffb02036..53c62dbae5135 100644 --- a/shell-completion/bash/varlinkctl +++ b/shell-completion/bash/varlinkctl @@ -43,7 +43,7 @@ _varlinkctl() { local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} local -A OPTS=( [STANDALONE]='-h --help --version --no-pager -q --quiet --no-ask-password - --oneway --collect --more --exec -j -E' + --oneway --collect --more --exec --upgrade -j -E' [ARG]='--graceful --timeout --push-fd --json' ) diff --git a/shell-completion/zsh/_bootctl b/shell-completion/zsh/_bootctl index 447612856197f..f7ed2a8e4148a 100644 --- a/shell-completion/zsh/_bootctl +++ b/shell-completion/zsh/_bootctl @@ -74,6 +74,7 @@ _arguments \ '--boot-path=[Path to the $BOOT partition]:path:_directories' \ '(-p --print-esp-path)'{-p,--print-esp-path}'[Print path to the EFI system partition]' \ '(-x --print-boot-path)'{-x,--print-boot-path}'[Print path to the $BOOT partition]' \ + '--print-efi-architecture[Print the EFI architecture identifier]' \ '--make-machine-id-directory=[Control creation and deletion of the top-level machine ID directory.]:options:(yes no auto)' \ '--no-pager[Do not pipe output into a pager]' \ '--graceful[Do not fail when locating ESP or writing fails]' \ diff --git a/shell-completion/zsh/_machinectl b/shell-completion/zsh/_machinectl index d5f7aa9680d6c..31ddf4fca571d 100644 --- a/shell-completion/zsh/_machinectl +++ b/shell-completion/zsh/_machinectl @@ -38,6 +38,8 @@ "disable:Disable automatic container start at boot" "poweroff:Power off one or more VMs/containers" "reboot:Reboot one or more VMs/containers" + "pause:Pause one or more machines" + "resume:Resume one or more previously paused machines" "terminate:Terminate one or more VMs/containers" "kill:Send signal to process or a VM/container" "copy-to:Copy files from the host to a container" @@ -77,7 +79,7 @@ start|enable|disable) _machinectl_images ;; - status|show|poweroff|reboot|terminate|kill) + status|show|poweroff|reboot|pause|resume|terminate|kill) _sd_machines ;; login|shell) diff --git a/shell-completion/zsh/_run0 b/shell-completion/zsh/_run0 index dc95486bef319..f76be2e5ff0dc 100644 --- a/shell-completion/zsh/_run0 +++ b/shell-completion/zsh/_run0 @@ -39,20 +39,29 @@ _run0_slices() { local -a args=( '--no-ask-password[Do not query the user for authentication]' '--unit=[Use this unit name instead of an automatically generated one]' - {--property=,-p+}'[Sets a property on the service unit created]:property:_run0_unit_properties' - '--description=[Provide a description for the service unit]' + {'*--property=','*-p+'}'[Sets a property on the service unit created]:property:_run0_unit_properties' + '--description=[Provide a description for the service unit]:TEXT' '--slice=[Make the new .service unit part of the specified slice]:slice unit:_run0_slices' '--slice-inherit[Make the new service unit part of the current slice]' - {--user=,-u+}'[Switch to the specified user]:user:_users' - {--group=,-g+}'[Switch to the specified group]:group:_groups' + '(--user -u)'{--user=,-u+}'[Switch to the specified user]:user:_users' + '(--group -g)'{--group=,-g+}'[Switch to the specified group]:group:_groups' '--nice=[Run with specified nice level]:nice value' - {--chdir=,-D+}'[Run within the specified working directory]:directory:_files -/' - '--setenv=[Set the specified environment variable in the session]:environment variable:_parameters -g "*export*" -S = -q' + '(--chdir -D -i --same-root-dir)'{--chdir=,-D+}'[Run within the specified working directory]:directory:_files -/' + '(-i)'--via-shell"[Invoke command via target user's login shell]" + '(--via-shell --chdir -D --same-root-dir)'-i"[Shortcut for --via-shell --chdir='~']" + '*--setenv=[Set the specified environment variable in the session]:environment variable:_parameters -g "*export*" -S = -q' '--background=[Change the terminal background color to the specified ANSI color]:ansi color' + '(--pty-late --pipe)'--pty'[Request allocation of a pseudo TTY for stdio]' + '(--pty --pipe)'--pty-late'[Just like --pty, but leave TTY access to agents until unit is started up]' + "(--pty --pty-late)--pipe[request passing the caller's STDIO file descriptors directly through]" + '--shell-prompt-prefix=[Set $SHELL_PROMPT_PREFIX]:PREFIX' + '--lightweight=[Control whether to register a session with service manager or without]:bool:_values bool true false' '--machine=[Execute the operation on a local container]:machine:_sd_machines' - {-h,--help}'[Show the help text and exit]' - '--version[Print a short version string and exit]' + '--area=[Home area to log into]:AREA' + '(- *)'{-h,--help}'[Show the help text and exit]' + '(- *)'{-V,--version}'[Print a short version string and exit]' '--empower[Give privileges to selected or current user]' + '(--chdir -D -i)--same-root-dir[Execute the run0 session in the same root directory that the run0 command is executed in]' ) _arguments -S $args '*:: :{_normal -p $service}' diff --git a/shell-completion/zsh/_systemd-hwdb b/shell-completion/zsh/_systemd-hwdb new file mode 100644 index 0000000000000..92238d63ad3c7 --- /dev/null +++ b/shell-completion/zsh/_systemd-hwdb @@ -0,0 +1,40 @@ +#compdef systemd-hwdb +# SPDX-License-Identifier: LGPL-2.1-or-later + +local context state state_descr line +typeset -A opt_args + +local -a opt_common=( + {-h,--help}'[show this help]' + '--version[show package version]' + {-s,--strict}'[when updating, return non-zero exit value on any parsing error]' + '--usr[generate in /usr/lib/udev instead of /etc/udev]' + {-r+,--root=}'[alternative root path in the filesystem]:path:_directories' +) + +local -a hwdb_commands=( + 'update:update the hwdb database' + 'query:query database and print result' +) + +local ret=1 +_arguments -s -A '-*' "$opt_common[@]" \ + ':command:->command' \ + '*:: :->option-or-argument' && ret=0 + +case $state in + command) + _describe -t command 'systemd-hwdb command' hwdb_commands && ret=0 + ;; + option-or-argument) + case $words[1] in + update) + _arguments -s "$opt_common[@]" && ret=0 + ;; + query) + _arguments -s "$opt_common[@]" ':modalias:' && ret=0 + ;; + esac + ;; +esac +return ret diff --git a/shell-completion/zsh/_systemd-id128 b/shell-completion/zsh/_systemd-id128 index 2cc06de80b026..33522e1e37d22 100644 --- a/shell-completion/zsh/_systemd-id128 +++ b/shell-completion/zsh/_systemd-id128 @@ -32,7 +32,7 @@ _systemd-id128_names() { } local ret=1 -_arguments -s "$opt_common[@]" \ +_arguments -s -A '-*' "$opt_common[@]" \ ':command:->command' \ '*:: :->option-or-argument' && ret=0 diff --git a/shell-completion/zsh/_systemd-nspawn b/shell-completion/zsh/_systemd-nspawn index 5f081700644c6..10dc527236c52 100644 --- a/shell-completion/zsh/_systemd-nspawn +++ b/shell-completion/zsh/_systemd-nspawn @@ -22,7 +22,9 @@ _arguments \ '(--ephemeral -x)'{--ephemeral,-x}'[Run container with snapshot of root directory, and remove it after exit.]' \ '(--image -i)'{--image=,-i+}'[Disk image to mount the root directory for the container from.]:disk image: _files' \ '(--boot -b)'{--boot,-b}'[Automatically search for an init binary and invoke it instead of a shell or a user supplied program.]' \ - '(--user -u)'{--user=,-u+}'[Run the command under specified user, create home directory and cd into it.]:user:_users' \ + '(--uid -u)'{--uid=,-u+}'[Run the command under specified user, create home directory and cd into it.]:user:_users' \ + '--user[Run in user service manager scope]' \ + '--system[Run in system service manager scope]' \ '(--machine -M)'{--machine=,-M+}'[Sets the machine name for this container.]: : _message "container name"' \ '--uuid=[Set the specified uuid for the container.]: : _message "container UUID"' \ '(--slice -S)'{--slice=,-S+}'[Make the container part of the specified slice, instead of the default machine.slice.]: : _message slice' \ @@ -45,10 +47,16 @@ _arguments \ '--tmpfs=[Mount an empty tmpfs to the specified directory.]: : _files' \ '--setenv=[Specifies an environment variable assignment to pass to the init process in the container, in the format "NAME=VALUE".]: : _message "environment variables"' \ '--share-system[Allows the container to share certain system facilities with the host.]' \ - '--register=[Controls whether the container is registered with systemd-machined(8).]:systemd-machined registration:( yes no )' \ + '--register=[Controls whether the container is registered with systemd-machined(8).]:systemd-machined registration:( yes no auto )' \ '--keep-unit[Instead of creating a transient scope unit to run the container in, simply register the service or scope unit systemd-nspawn has been invoked in with systemd-machined(8).]' \ '--personality=[Control the architecture ("personality") reported by uname(2) in the container.]:architecture:(x86 x86-64)' \ '--volatile=[Run the system in volatile mode.]:volatile:(no yes state)' \ "--notify-ready=[Control when the ready notification is sent]:options:(yes no)" \ "--suppress-sync=[Control whether to suppress disk synchronization for the container payload]:options:(yes no)" \ + '--restrict-address-families=[Restrict socket address families accessible in the container.]: : _message "address families"' \ + '--forward-journal=[Forward the container journal to the host via systemd-journal-remote.]: : _files' \ + '--forward-journal-max-use=[Maximum disk space used by forwarded journal files.]: : _message bytes' \ + '--forward-journal-keep-free=[Disk space to keep free for forwarded journal files.]: : _message bytes' \ + '--forward-journal-max-file-size=[Maximum size of individual forwarded journal files.]: : _message bytes' \ + '--forward-journal-max-files=[Maximum number of forwarded journal files.]: : _message number' \ '*:: : _normal' diff --git a/shell-completion/zsh/_userdbctl b/shell-completion/zsh/_userdbctl index b3122fa908bfa..2a2106b62ef3d 100644 --- a/shell-completion/zsh/_userdbctl +++ b/shell-completion/zsh/_userdbctl @@ -26,7 +26,7 @@ local -a opt_user_group=( '-S[Equivalent to --disposition=system]' '-R[Equivalent to --disposition=regular]' '--uid-min=[Filter by minimum UID/GID]:uid:_numbers -t uids uid -d 0' - '--uid-max=[Filter by maximum UID/GID]:gid:_numbers -t gids gid -d 4294967294' + '--uid-max=[Filter by maximum UID/GID]:uid:_numbers -t uids uid -d 4294967294' '--uuid=[Filter by UUID]:uuid' '(-B)--boundaries=[Show/hide UID/GID range boundaries in output]:bool:(yes no)' '(--boundaries)-B[Equivalent to --boundaries=no]' @@ -44,7 +44,7 @@ local -a userdbctl_commands=( ) local ret=1 -_arguments -s \ +_arguments -s -A '-*' \ "$opt_common[@]" \ ':userdbctl command:->command' \ '*:: :->option-or-argument' && ret=0 diff --git a/shell-completion/zsh/_varlinkctl b/shell-completion/zsh/_varlinkctl index f33eea24b60fc..a071f6ec2efb8 100644 --- a/shell-completion/zsh/_varlinkctl +++ b/shell-completion/zsh/_varlinkctl @@ -65,6 +65,7 @@ local -a opts=( '--no-pager[Do not pipe output to a pager]' '--more[Request multiple responses]' '--collect[Collect multiple responses in a JSON array]' + '--upgrade[Upgrade connection to raw protocol after method call]' {-j+,--json=}'[Output as json]:json-mode:(pretty short)' ) _arguments -S $opts '*:: := _varlinkctl_command' diff --git a/shell-completion/zsh/meson.build b/shell-completion/zsh/meson.build index c5ee8d43a90d4..b1bff151e41a3 100644 --- a/shell-completion/zsh/meson.build +++ b/shell-completion/zsh/meson.build @@ -30,12 +30,13 @@ foreach item : [ ['_run0', ''], ['_sd_bus_address', ''], ['_sd_hosts_or_user_at_host', ''], - ['_sd_machines', ''], + ['_sd_machines', 'ENABLE_MACHINED'], ['_sd_outputmodes', ''], ['_sd_unit_files', ''], ['_systemd', ''], ['_systemd-analyze', ''], ['_systemd-delta', ''], + ['_systemd-hwdb', 'ENABLE_HWDB'], ['_systemd-id128', ''], ['_systemd-inhibit', 'ENABLE_LOGIND'], ['_systemd-nspawn', ''], diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index 13382b9994377..e773d8d4314f5 100644 --- a/src/ac-power/ac-power.c +++ b/src/ac-power/ac-power.c @@ -1,14 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - -#include "alloc-util.h" -#include "ansi-color.h" #include "battery-util.h" #include "build.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" #include "main-func.h" -#include "pretty-print.h" +#include "options.h" #include "string-util.h" static bool arg_verbose = false; @@ -19,78 +17,52 @@ static enum { } arg_action = ACTION_AC_POWER; static int help(void) { - _cleanup_free_ char *link = NULL; int r; - r = terminal_urlify_man("systemd-ac-power", "1", &link); + _cleanup_(table_unrefp) Table *options = NULL; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...]"); + help_abstract("Report whether we are connected to an external power source."); + + help_section("Options:"); + r = table_print_or_warn(options); if (r < 0) - return log_oom(); - - printf("%1$s [OPTION]\n" - "\n%2$sReport whether we are connected to an external power source.%3$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -v --verbose Show state as text\n" - " --low Check if battery is discharging and low\n" - "\nSee the %4$s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + return r; + help_man_page_reference("systemd-ac-power", "1"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_LOW, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "verbose", no_argument, NULL, 'v' }, - { "low", no_argument, NULL, ARG_LOW }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { + OPTION_COMMON_HELP: + return help(); - case 'h': - help(); - return 0; - - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'v': + OPTION('v', "verbose", NULL, "Show state as text"): arg_verbose = true; break; - case ARG_LOW: + OPTION_LONG("low", NULL, "Check if battery is discharging and low"): arg_action = ACTION_LOW; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s takes no arguments.", - program_invocation_short_name); + if (option_parser_get_n_args(&state) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); return 1; } diff --git a/src/analyze/analyze-architectures.c b/src/analyze/analyze-architectures.c index de9b899f12f37..7df7f91c2c31f 100644 --- a/src/analyze/analyze-architectures.c +++ b/src/analyze/analyze-architectures.c @@ -42,7 +42,7 @@ static int add_arch(Table *t, Architecture a) { return 0; } -int verb_architectures(int argc, char *argv[], void *userdata) { +int verb_architectures(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; int r; diff --git a/src/analyze/analyze-architectures.h b/src/analyze/analyze-architectures.h index 06b9473784475..d8ba8b82aeeca 100644 --- a/src/analyze/analyze-architectures.h +++ b/src/analyze/analyze-architectures.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_architectures(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_architectures(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-blame.c b/src/analyze/analyze-blame.c index 7476342caa51b..d400380ade903 100644 --- a/src/analyze/analyze-blame.c +++ b/src/analyze/analyze-blame.c @@ -9,7 +9,7 @@ #include "format-table.h" #include "runtime-scope.h" -int verb_blame(int argc, char *argv[], void *userdata) { +int verb_blame(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL; _cleanup_(table_unrefp) Table *table = NULL; @@ -28,29 +28,21 @@ int verb_blame(int argc, char *argv[], void *userdata) { if (!table) return log_oom(); - table_set_header(table, false); - assert_se(cell = table_get_cell(table, 0, 0)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); - r = table_set_align_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_align_percent(table, cell, 100); assert_se(cell = table_get_cell(table, 0, 1)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); r = table_set_sort(table, (size_t) 0); if (r < 0) - return r; + return table_log_sort_error(r); r = table_set_reverse(table, 0, true); if (r < 0) - return r; + return table_log_sort_error(r); for (UnitTimes *u = times; u->has_data; u++) { if (u->time <= 0) @@ -63,11 +55,5 @@ int verb_blame(int argc, char *argv[], void *userdata) { return table_log_add_error(r); } - pager_open(arg_pager_flags); - - r = table_print(table, NULL); - if (r < 0) - return r; - - return 0; + return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */ false); } diff --git a/src/analyze/analyze-blame.h b/src/analyze/analyze-blame.h index d9aa985c1e611..362f6c9d36242 100644 --- a/src/analyze/analyze-blame.h +++ b/src/analyze/analyze-blame.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_blame(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_blame(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-calendar.c b/src/analyze/analyze-calendar.c index b6e23e0a4449c..a9fb1e897e67e 100644 --- a/src/analyze/analyze-calendar.c +++ b/src/analyze/analyze-calendar.c @@ -35,14 +35,10 @@ static int test_calendar_one(usec_t n, const char *p) { return log_oom(); assert_se(cell = table_get_cell(table, 0, 0)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); assert_se(cell = table_get_cell(table, 0, 1)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); if (!streq(t, p)) { r = table_add_many(table, @@ -119,10 +115,10 @@ static int test_calendar_one(usec_t n, const char *p) { n = next; } - return table_print(table, NULL); + return table_print_or_warn(table); } -int verb_calendar(int argc, char *argv[], void *userdata) { +int verb_calendar(int argc, char *argv[], uintptr_t _data, void *userdata) { int r = 0; usec_t n; diff --git a/src/analyze/analyze-calendar.h b/src/analyze/analyze-calendar.h index 3d6eac200ddac..673a6ed61b56f 100644 --- a/src/analyze/analyze-calendar.h +++ b/src/analyze/analyze-calendar.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_calendar(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_calendar(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-capability.c b/src/analyze/analyze-capability.c index 57bb67ace997f..3ddbdb4cc14cc 100644 --- a/src/analyze/analyze-capability.c +++ b/src/analyze/analyze-capability.c @@ -19,7 +19,7 @@ static int table_add_capability(Table *table, int c) { return 0; } -int verb_capabilities(int argc, char *argv[], void *userdata) { +int verb_capabilities(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; unsigned last_cap; int r; diff --git a/src/analyze/analyze-capability.h b/src/analyze/analyze-capability.h index 07ff0887fd948..fa6f5537e125d 100644 --- a/src/analyze/analyze-capability.h +++ b/src/analyze/analyze-capability.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_capabilities(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_capabilities(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-cat-config.c b/src/analyze/analyze-cat-config.c index e8c118a4b2e63..549b1b4f3f3ff 100644 --- a/src/analyze/analyze-cat-config.c +++ b/src/analyze/analyze-cat-config.c @@ -9,7 +9,7 @@ #include "pretty-print.h" #include "strv.h" -int verb_cat_config(int argc, char *argv[], void *userdata) { +int verb_cat_config(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; pager_open(arg_pager_flags); diff --git a/src/analyze/analyze-cat-config.h b/src/analyze/analyze-cat-config.h index 64e87a3a6d4fe..c90d7e82a4464 100644 --- a/src/analyze/analyze-cat-config.h +++ b/src/analyze/analyze-cat-config.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_cat_config(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_cat_config(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-chid.c b/src/analyze/analyze-chid.c index 612465095f8b2..9fe68b2de45da 100644 --- a/src/analyze/analyze-chid.c +++ b/src/analyze/analyze-chid.c @@ -338,7 +338,7 @@ static int edid_search(char16_t **ret_panel) { return -ENOTUNIQ; } -int verb_chid(int argc, char *argv[], void *userdata) { +int verb_chid(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; int r; diff --git a/src/analyze/analyze-chid.h b/src/analyze/analyze-chid.h index a3f40c601341c..64e2bab0e16b2 100644 --- a/src/analyze/analyze-chid.h +++ b/src/analyze/analyze-chid.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_chid(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_chid(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-compare-versions.c b/src/analyze/analyze-compare-versions.c index 2cf9b4a47ce72..5c15fd044d65a 100644 --- a/src/analyze/analyze-compare-versions.c +++ b/src/analyze/analyze-compare-versions.c @@ -7,7 +7,7 @@ #include "log.h" #include "string-util.h" -int verb_compare_versions(int argc, char *argv[], void *userdata) { +int verb_compare_versions(int argc, char *argv[], uintptr_t _data, void *userdata) { const char *v1 = ASSERT_PTR(argv[1]), *v2 = ASSERT_PTR(argv[argc-1]); int r; diff --git a/src/analyze/analyze-compare-versions.h b/src/analyze/analyze-compare-versions.h index 91913039035bc..f907ffff2093a 100644 --- a/src/analyze/analyze-compare-versions.h +++ b/src/analyze/analyze-compare-versions.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_compare_versions(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_compare_versions(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-condition.c b/src/analyze/analyze-condition.c index 17d3126b7ba9e..a928f84ef4e95 100644 --- a/src/analyze/analyze-condition.c +++ b/src/analyze/analyze-condition.c @@ -136,7 +136,7 @@ static int verify_conditions(char **lines, RuntimeScope scope, const char *unit, return r > 0 && q > 0 ? 0 : -EIO; } -int verb_condition(int argc, char *argv[], void *userdata) { +int verb_condition(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = verify_conditions(strv_skip(argv, 1), arg_runtime_scope, arg_unit, arg_root); diff --git a/src/analyze/analyze-condition.h b/src/analyze/analyze-condition.h index 28ef51a453373..c385cdfb78182 100644 --- a/src/analyze/analyze-condition.h +++ b/src/analyze/analyze-condition.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_condition(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_condition(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-critical-chain.c b/src/analyze/analyze-critical-chain.c index 887baf8d149a0..659d1b564c596 100644 --- a/src/analyze/analyze-critical-chain.c +++ b/src/analyze/analyze-critical-chain.c @@ -67,6 +67,9 @@ static int list_dependencies_compare(char *const *a, char *const *b) { usec_t usa = 0, usb = 0; UnitTimes *times; + assert(a); + assert(b); + times = hashmap_get(unit_times_hashmap, *a); if (times) usa = times->activated; @@ -201,7 +204,7 @@ static int list_dependencies(sd_bus *bus, const char *name) { return list_dependencies_one(bus, name, 0, &units, 0); } -int verb_critical_chain(int argc, char *argv[], void *userdata) { +int verb_critical_chain(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL; int n, r; diff --git a/src/analyze/analyze-critical-chain.h b/src/analyze/analyze-critical-chain.h index 844249c911eeb..c4e84b1e1113e 100644 --- a/src/analyze/analyze-critical-chain.h +++ b/src/analyze/analyze-critical-chain.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_critical_chain(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_critical_chain(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-dlopen-metadata.c b/src/analyze/analyze-dlopen-metadata.c index 2d440055fa23a..76c161105f324 100644 --- a/src/analyze/analyze-dlopen-metadata.c +++ b/src/analyze/analyze-dlopen-metadata.c @@ -12,7 +12,7 @@ #include "json-util.h" #include "strv.h" -int verb_dlopen_metadata(int argc, char *argv[], void *userdata) { +int verb_dlopen_metadata(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_free_ char *abspath = NULL; diff --git a/src/analyze/analyze-dlopen-metadata.h b/src/analyze/analyze-dlopen-metadata.h index 3f7355d96bd42..8abb8bb9c41d8 100644 --- a/src/analyze/analyze-dlopen-metadata.h +++ b/src/analyze/analyze-dlopen-metadata.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_dlopen_metadata(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_dlopen_metadata(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-dot.c b/src/analyze/analyze-dot.c index 0a250b7c80ca5..6f1044aa60356 100644 --- a/src/analyze/analyze-dot.c +++ b/src/analyze/analyze-dot.c @@ -146,7 +146,7 @@ static int expand_patterns(sd_bus *bus, char **patterns, char ***ret) { return 0; } -int verb_dot(int argc, char *argv[], void *userdata) { +int verb_dot(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; diff --git a/src/analyze/analyze-dot.h b/src/analyze/analyze-dot.h index 144b43d1ef74f..944e1d7a30ce9 100644 --- a/src/analyze/analyze-dot.h +++ b/src/analyze/analyze-dot.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_dot(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_dot(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-dump.c b/src/analyze/analyze-dump.c index 624403f2a9cc0..7d246535ccb6e 100644 --- a/src/analyze/analyze-dump.c +++ b/src/analyze/analyze-dump.c @@ -112,7 +112,7 @@ static int mangle_patterns(char **args, char ***ret) { return 0; } -int verb_dump(int argc, char *argv[], void *userdata) { +int verb_dump(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_strv_free_ char **patterns = NULL; int r; diff --git a/src/analyze/analyze-dump.h b/src/analyze/analyze-dump.h index 5d6107cb589ac..30c697e2adb31 100644 --- a/src/analyze/analyze-dump.h +++ b/src/analyze/analyze-dump.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_dump(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_dump(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-exit-status.c b/src/analyze/analyze-exit-status.c index 8b50684854672..b291ff0ab7cfb 100644 --- a/src/analyze/analyze-exit-status.c +++ b/src/analyze/analyze-exit-status.c @@ -7,7 +7,7 @@ #include "log.h" #include "strv.h" -int verb_exit_status(int argc, char *argv[], void *userdata) { +int verb_exit_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; int r; diff --git a/src/analyze/analyze-exit-status.h b/src/analyze/analyze-exit-status.h index ce14cdbb96de6..63e1601066d3b 100644 --- a/src/analyze/analyze-exit-status.h +++ b/src/analyze/analyze-exit-status.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_exit_status(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_exit_status(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-fdstore.c b/src/analyze/analyze-fdstore.c index 98c3d621463de..f9721119f2e22 100644 --- a/src/analyze/analyze-fdstore.c +++ b/src/analyze/analyze-fdstore.c @@ -101,7 +101,7 @@ static int dump_fdstore(sd_bus *bus, const char *arg) { return EXIT_SUCCESS; } -int verb_fdstore(int argc, char *argv[], void *userdata) { +int verb_fdstore(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; diff --git a/src/analyze/analyze-fdstore.h b/src/analyze/analyze-fdstore.h index d548ad2b4d1dd..b48496a384b9d 100644 --- a/src/analyze/analyze-fdstore.h +++ b/src/analyze/analyze-fdstore.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_fdstore(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_fdstore(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-filesystems.c b/src/analyze/analyze-filesystems.c index a0a2b5afed466..3fc3471d8849c 100644 --- a/src/analyze/analyze-filesystems.c +++ b/src/analyze/analyze-filesystems.c @@ -112,7 +112,7 @@ static void dump_filesystem_set(const FilesystemSet *set) { } } -int verb_filesystems(int argc, char *argv[], void *userdata) { +int verb_filesystems(int argc, char *argv[], uintptr_t _data, void *userdata) { #if ! HAVE_LIBBPF return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not compiled with libbpf support, sorry."); #endif diff --git a/src/analyze/analyze-filesystems.h b/src/analyze/analyze-filesystems.h index 09045716d0a1c..480e5ae7deaf5 100644 --- a/src/analyze/analyze-filesystems.h +++ b/src/analyze/analyze-filesystems.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_filesystems(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_filesystems(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-has-tpm2.c b/src/analyze/analyze-has-tpm2.c index 3e13be9f160fd..c01e686ce8ce5 100644 --- a/src/analyze/analyze-has-tpm2.c +++ b/src/analyze/analyze-has-tpm2.c @@ -2,8 +2,141 @@ #include "analyze.h" #include "analyze-has-tpm2.h" +#include "format-table.h" +#include "log.h" +#include "string-util.h" +#include "time-util.h" #include "tpm2-util.h" -int verb_has_tpm2(int argc, char **argv, void *userdata) { +int verb_has_tpm2(int argc, char *argv[], uintptr_t _data, void *userdata) { return verb_has_tpm2_generic(arg_quiet); } + +int verb_identify_tpm2(int argc, char **argv, uintptr_t _data, void *userdata) { +#if HAVE_TPM2 + int r; + + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + r = tpm2_context_new_or_warn(/* device= */ NULL, &c); + if (r < 0) + return r; + + Tpm2VendorInfo info; + r = tpm2_get_vendor_info(c, &info); + if (r < 0) + return log_error_errno(r, "Failed to acquire TPM2 vendor information: %m"); + + _cleanup_(table_unrefp) Table *table = table_new_vertical(); + if (!table) + return log_oom(); + + if (!isempty(info.family_indicator)) { + r = table_add_many( + table, + TABLE_FIELD, "Family Indicator", + TABLE_STRING, info.family_indicator); + if (r < 0) + return table_log_add_error(r); + } + + _cleanup_free_ char *rv = NULL; + if (asprintf(&rv, "%" PRIu32 ".%" PRIu32, + info.revision_major, + info.revision_minor) < 0) + return log_oom(); + + r = table_add_many( + table, + TABLE_FIELD, "Level", + TABLE_UINT32, info.level, + TABLE_FIELD, "Revision", + TABLE_STRING, rv); + if (r < 0) + return table_log_add_error(r); + + if (info.year >= 1900) { + struct tm tm = { + .tm_year = info.year - 1900, + .tm_mon = 0, /* january */ + .tm_mday = info.day_of_year, /* timegm() will normalize this */ + }; + + usec_t ts; + r = mktime_or_timegm_usec(&tm, /* utc= */ true, &ts); + if (r < 0) + log_debug_errno(r, "Failed to convert the specification date, ignoring."); + else { + r = table_add_many( + table, + TABLE_FIELD, "Specification Date", + TABLE_TIMESTAMP_DATE, ts); + if (r < 0) + return table_log_add_error(r); + } + } + + if (!isempty(info.manufacturer)) { + r = table_add_many( + table, + TABLE_FIELD, "Manufacturer", + TABLE_STRING, info.manufacturer); + if (r < 0) + return table_log_add_error(r); + } + + if (!isempty(info.vendor_string)) { + r = table_add_many( + table, + TABLE_FIELD, "Vendor String", + TABLE_STRING, info.vendor_string); + if (r < 0) + return table_log_add_error(r); + } + + if (info.vendor_tpm_type != 0) { + r = table_add_many( + table, + TABLE_FIELD, "Vendor TPM Type", + TABLE_UINT32_HEX_0x, info.vendor_tpm_type); + if (r < 0) + return table_log_add_error(r); + } + + /* Show the first two 16bit words of the firmware version as major/minor */ + _cleanup_free_ char *fw = NULL; + if (asprintf(&fw, "%" PRIu16 ".%" PRIu16, + info.firmware_version_major, + info.firmware_version_minor) < 0) + return log_oom(); + + /* Show the second 32bit as a single value, if non-zero */ + if (info.firmware_version2 != 0 && strextendf(&fw, ".%" PRIu32, info.firmware_version2) < 0) + return log_oom(); + + r = table_add_many( + table, + TABLE_FIELD, "Firmware Version", + TABLE_STRING, fw); + if (r < 0) + return table_log_add_error(r); + + _cleanup_free_ char *m = NULL; + if (tpm2_vendor_info_to_modalias(&info, &m) < 0) + return log_oom(); + + r = table_add_many( + table, + TABLE_FIELD, "Modalias String", + TABLE_STRING, m); + if (r < 0) + return table_log_add_error(r); + + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */ false); + if (r < 0) + return r; + + return EXIT_SUCCESS; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support not enabled at build time."); +#endif +} diff --git a/src/analyze/analyze-has-tpm2.h b/src/analyze/analyze-has-tpm2.h index c7c639228d87c..bb5af66fbbae1 100644 --- a/src/analyze/analyze-has-tpm2.h +++ b/src/analyze/analyze-has-tpm2.h @@ -1,4 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_has_tpm2(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_has_tpm2(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_identify_tpm2(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-image-policy.c b/src/analyze/analyze-image-policy.c index 65f54505979f8..220716878a524 100644 --- a/src/analyze/analyze-image-policy.c +++ b/src/analyze/analyze-image-policy.c @@ -83,7 +83,7 @@ static int table_add_designator_line(Table *table, PartitionDesignator d, Partit return 0; } -int verb_image_policy(int argc, char *argv[], void *userdata) { +int verb_image_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; for (int i = 1; i < argc; i++) { @@ -157,7 +157,7 @@ int verb_image_policy(int argc, char *argv[], void *userdata) { putc('\n', stdout); - r = table_print(table, NULL); + r = table_print_or_warn(table); if (r < 0) return r; } diff --git a/src/analyze/analyze-image-policy.h b/src/analyze/analyze-image-policy.h index 16c1e966e896b..3e62e1279149a 100644 --- a/src/analyze/analyze-image-policy.h +++ b/src/analyze/analyze-image-policy.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_image_policy(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_image_policy(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-inspect-elf.c b/src/analyze/analyze-inspect-elf.c index d84601dfabe09..c664aea588673 100644 --- a/src/analyze/analyze-inspect-elf.c +++ b/src/analyze/analyze-inspect-elf.c @@ -118,16 +118,16 @@ static int analyze_elf(char **filenames, sd_json_format_flags_t json_flags) { if (sd_json_format_enabled(json_flags)) sd_json_variant_dump(package_metadata, json_flags, stdout, NULL); else { - r = table_print(t, NULL); + r = table_print_or_warn(t); if (r < 0) - return table_log_print_error(r); + return r; } } return 0; } -int verb_elf_inspection(int argc, char *argv[], void *userdata) { +int verb_elf_inspection(int argc, char *argv[], uintptr_t _data, void *userdata) { pager_open(arg_pager_flags); return analyze_elf(strv_skip(argv, 1), arg_json_format_flags); diff --git a/src/analyze/analyze-inspect-elf.h b/src/analyze/analyze-inspect-elf.h index a790eae6bb68e..890572ea00a01 100644 --- a/src/analyze/analyze-inspect-elf.h +++ b/src/analyze/analyze-inspect-elf.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_elf_inspection(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_elf_inspection(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-log-control.c b/src/analyze/analyze-log-control.c index f105d7d0326ab..13a5860436855 100644 --- a/src/analyze/analyze-log-control.c +++ b/src/analyze/analyze-log-control.c @@ -8,7 +8,7 @@ #include "runtime-scope.h" #include "verb-log-control.h" -int verb_log_control(int argc, char *argv[], void *userdata) { +int verb_log_control(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; diff --git a/src/analyze/analyze-log-control.h b/src/analyze/analyze-log-control.h index 350c22861a660..0ad41aeddc170 100644 --- a/src/analyze/analyze-log-control.h +++ b/src/analyze/analyze-log-control.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_log_control(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_log_control(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-malloc.c b/src/analyze/analyze-malloc.c index 35f533e790ae6..c892b37ce312a 100644 --- a/src/analyze/analyze-malloc.c +++ b/src/analyze/analyze-malloc.c @@ -34,7 +34,7 @@ static int dump_malloc_info(sd_bus *bus, char *service) { return bus_message_dump_fd(reply); } -int verb_malloc(int argc, char *argv[], void *userdata) { +int verb_malloc(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; char **services = STRV_MAKE("org.freedesktop.systemd1"); int r; diff --git a/src/analyze/analyze-malloc.h b/src/analyze/analyze-malloc.h index d3feabd757efb..3e30467e77b59 100644 --- a/src/analyze/analyze-malloc.h +++ b/src/analyze/analyze-malloc.h @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ - #pragma once -int verb_malloc(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_malloc(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-nvpcrs.c b/src/analyze/analyze-nvpcrs.c index ddb850fcef5a3..56b5c9a204945 100644 --- a/src/analyze/analyze-nvpcrs.c +++ b/src/analyze/analyze-nvpcrs.c @@ -50,13 +50,13 @@ static int add_nvpcr_to_table(Tpm2Context **c, Table *t, const char *name) { } #endif -int verb_nvpcrs(int argc, char *argv[], void *userdata) { +int verb_nvpcrs(int argc, char *argv[], uintptr_t _data, void *userdata) { #if HAVE_TPM2 _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; _cleanup_(table_unrefp) Table *table = NULL; int r; - bool have_tpm2 = tpm2_is_fully_supported(); + bool have_tpm2 = tpm2_is_mostly_supported(); if (!have_tpm2) log_notice("System lacks full TPM2 support, not showing NvPCR state."); diff --git a/src/analyze/analyze-nvpcrs.h b/src/analyze/analyze-nvpcrs.h index 258005617ea1e..7e287f2298a80 100644 --- a/src/analyze/analyze-nvpcrs.h +++ b/src/analyze/analyze-nvpcrs.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_nvpcrs(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_nvpcrs(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-pcrs.c b/src/analyze/analyze-pcrs.c index 8c1d7f3cfc65a..7e3ddde800bc2 100644 --- a/src/analyze/analyze-pcrs.c +++ b/src/analyze/analyze-pcrs.c @@ -96,13 +96,13 @@ static int add_pcr_to_table(Table *table, const char *alg, uint32_t pcr) { return 0; } -int verb_pcrs(int argc, char *argv[], void *userdata) { +int verb_pcrs(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; const char *alg = NULL; int r; - if (!tpm2_is_fully_supported()) - log_notice("System lacks full TPM2 support, not showing PCR state."); + if (!tpm2_is_mostly_supported()) + log_notice("System lacks sufficient TPM2 support, not showing PCR state."); else { r = get_pcr_alg(&alg); if (r < 0) diff --git a/src/analyze/analyze-pcrs.h b/src/analyze/analyze-pcrs.h index 2a59511885aee..7fa5a8379f251 100644 --- a/src/analyze/analyze-pcrs.h +++ b/src/analyze/analyze-pcrs.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_pcrs(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_pcrs(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-plot.c b/src/analyze/analyze-plot.c index 21a9aa9f1cf08..3a3f07d2e36d8 100644 --- a/src/analyze/analyze-plot.c +++ b/src/analyze/analyze-plot.c @@ -87,6 +87,8 @@ static int acquire_host_info(sd_bus *bus, HostInfo **hi) { _cleanup_(free_host_infop) HostInfo *host = NULL; int r; + assert(hi); + host = new0(HostInfo, 1); if (!host) return log_oom(); @@ -429,7 +431,7 @@ static int show_table(Table *table, const char *word) { if (sd_json_format_enabled(arg_json_format_flags)) r = table_print_json(table, NULL, arg_json_format_flags | SD_JSON_FORMAT_COLOR_AUTO); else - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); } @@ -470,7 +472,7 @@ static int produce_plot_as_text(UnitTimes *times, const BootTimes *boot) { return show_table(table, "Units"); } -int verb_plot(int argc, char *argv[], void *userdata) { +int verb_plot(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(free_host_infop) HostInfo *host = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL; diff --git a/src/analyze/analyze-plot.h b/src/analyze/analyze-plot.h index eb2e398b3109a..4854498ce9810 100644 --- a/src/analyze/analyze-plot.h +++ b/src/analyze/analyze-plot.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_plot(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_plot(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c index 3e086a1775d6b..08bff2cf9e9d2 100644 --- a/src/analyze/analyze-security.c +++ b/src/analyze/analyze-security.c @@ -3,6 +3,7 @@ #include #include "sd-bus.h" +#include "sd-json.h" #include "alloc-util.h" #include "analyze-verify-util.h" @@ -557,6 +558,8 @@ static int assess_system_call_architectures( } static bool syscall_names_in_filter(Set *s, bool allow_list, const SyscallFilterSet *f, const char **ret_offending_syscall) { + assert(ret_offending_syscall); + NULSTR_FOREACH(syscall, f->value) { if (syscall[0] == '@') { const SyscallFilterSet *g; @@ -574,8 +577,7 @@ static bool syscall_names_in_filter(Set *s, bool allow_list, const SyscallFilter if (set_contains(s, syscall) == allow_list) { log_debug("Offending syscall filter item: %s", syscall); - if (ret_offending_syscall) - *ret_offending_syscall = syscall; + *ret_offending_syscall = syscall; return true; /* bad! */ } } @@ -603,7 +605,7 @@ static int assess_system_call_filter( uint64_t b; int r; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) { *ret_badness = UINT64_MAX; *ret_description = NULL; @@ -2576,7 +2578,7 @@ static int get_security_info(Unit *u, ExecContext *c, CGroupContext *g, Security info->_umask = c->umask; #if HAVE_SECCOMP - if (dlopen_libseccomp() >= 0) { + if (dlopen_libseccomp(LOG_DEBUG) >= 0) { SET_FOREACH(key, c->syscall_archs) { const char *name; @@ -2904,7 +2906,7 @@ static int analyze_security(sd_bus *bus, return ret; } -int verb_security(int argc, char *argv[], void *userdata) { +int verb_security(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *policy = NULL; int r; @@ -2919,7 +2921,7 @@ int verb_security(int argc, char *argv[], void *userdata) { unsigned line = 0, column = 0; if (arg_security_policy) { - r = sd_json_parse_file(/* f= */ NULL, arg_security_policy, /* flags= */ 0, &policy, &line, &column); + r = sd_json_parse_file(/* f= */ NULL, arg_security_policy, SD_JSON_PARSE_MUST_BE_OBJECT, &policy, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse '%s' at %u:%u: %m", arg_security_policy, line, column); } else { @@ -2931,7 +2933,7 @@ int verb_security(int argc, char *argv[], void *userdata) { return r; if (f) { - r = sd_json_parse_file(f, pp, /* flags= */ 0, &policy, &line, &column); + r = sd_json_parse_file(f, pp, SD_JSON_PARSE_MUST_BE_OBJECT, &policy, &line, &column); if (r < 0) return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON policy: %m", pp, line, column); } diff --git a/src/analyze/analyze-security.h b/src/analyze/analyze-security.h index 82f4c7fdeea7f..cd5fec307cd9b 100644 --- a/src/analyze/analyze-security.h +++ b/src/analyze/analyze-security.h @@ -1,10 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "shared-forward.h" + typedef enum AnalyzeSecurityFlags { ANALYZE_SECURITY_SHORT = 1 << 0, ANALYZE_SECURITY_ONLY_LOADED = 1 << 1, ANALYZE_SECURITY_ONLY_LONG_RUNNING = 1 << 2, } AnalyzeSecurityFlags; -int verb_security(int argc, char *argv[], void *userdata); +int verb_security(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-service-watchdogs.c b/src/analyze/analyze-service-watchdogs.c index 4a2892273c968..4d27fef5330c3 100644 --- a/src/analyze/analyze-service-watchdogs.c +++ b/src/analyze/analyze-service-watchdogs.c @@ -11,7 +11,7 @@ #include "runtime-scope.h" #include "string-util.h" -int verb_service_watchdogs(int argc, char *argv[], void *userdata) { +int verb_service_watchdogs(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int b, r; diff --git a/src/analyze/analyze-service-watchdogs.h b/src/analyze/analyze-service-watchdogs.h index 2f59f5a3f4376..e626a223c35f5 100644 --- a/src/analyze/analyze-service-watchdogs.h +++ b/src/analyze/analyze-service-watchdogs.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_service_watchdogs(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_service_watchdogs(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-smbios11.c b/src/analyze/analyze-smbios11.c index 30c18fef273b7..d76bc106d199a 100644 --- a/src/analyze/analyze-smbios11.c +++ b/src/analyze/analyze-smbios11.c @@ -10,7 +10,7 @@ #include "log.h" #include "smbios11.h" -int verb_smbios11(int argc, char *argv[], void *userdata) { +int verb_smbios11(int argc, char *argv[], uintptr_t _data, void *userdata) { unsigned n = 0; int r; diff --git a/src/analyze/analyze-smbios11.h b/src/analyze/analyze-smbios11.h index 4b1f334dc8fd7..32ae157fa1136 100644 --- a/src/analyze/analyze-smbios11.h +++ b/src/analyze/analyze-smbios11.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_smbios11(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_smbios11(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-srk.c b/src/analyze/analyze-srk.c index 2b81c864a0dac..dcf9459f2dee3 100644 --- a/src/analyze/analyze-srk.c +++ b/src/analyze/analyze-srk.c @@ -10,7 +10,7 @@ #include "terminal-util.h" #include "tpm2-util.h" -int verb_srk(int argc, char *argv[], void *userdata) { +int verb_srk(int argc, char *argv[], uintptr_t _data, void *userdata) { #if HAVE_TPM2 _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; diff --git a/src/analyze/analyze-srk.h b/src/analyze/analyze-srk.h index 26028354f86ab..73d3c865ad0e4 100644 --- a/src/analyze/analyze-srk.h +++ b/src/analyze/analyze-srk.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_srk(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_srk(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-syscall-filter.c b/src/analyze/analyze-syscall-filter.c index 11024803fdf3d..73d03d47a183a 100644 --- a/src/analyze/analyze-syscall-filter.c +++ b/src/analyze/analyze-syscall-filter.c @@ -21,6 +21,8 @@ static int load_kernel_syscalls(Set **ret) { _cleanup_fclose_ FILE *f = NULL; int r; + assert(ret); + /* Let's read the available system calls from the list of available tracing events. Slightly dirty, * but good enough for analysis purposes. */ @@ -106,7 +108,7 @@ static void dump_syscall_filter(const SyscallFilterSet *set) { printf(" %s%s%s\n", syscall[0] == '@' ? ansi_underline() : "", syscall, ansi_normal()); } -int verb_syscall_filters(int argc, char *argv[], void *userdata) { +int verb_syscall_filters(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; pager_open(arg_pager_flags); @@ -195,7 +197,7 @@ int verb_syscall_filters(int argc, char *argv[], void *userdata) { } #else -int verb_syscall_filters(int argc, char *argv[], void *userdata) { +int verb_syscall_filters(int argc, char *argv[], uintptr_t _data, void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not compiled with syscall filters, sorry."); } #endif diff --git a/src/analyze/analyze-syscall-filter.h b/src/analyze/analyze-syscall-filter.h index 3a1af85a69456..a648b3c603b3a 100644 --- a/src/analyze/analyze-syscall-filter.h +++ b/src/analyze/analyze-syscall-filter.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_syscall_filters(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_syscall_filters(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-time-data.c b/src/analyze/analyze-time-data.c index 9b67a1b417c32..70332b0f691ad 100644 --- a/src/analyze/analyze-time-data.c +++ b/src/analyze/analyze-time-data.c @@ -172,6 +172,8 @@ int pretty_boot_time(sd_bus *bus, char **ret) { BootTimes *t; int r; + assert(ret); + r = acquire_boot_times(bus, /* require_finished= */ true, &t); if (r < 0) return r; @@ -297,6 +299,8 @@ int acquire_time_data(sd_bus *bus, bool require_finished, UnitTimes **out) { UnitInfo u; int r; + assert(out); + r = acquire_boot_times(bus, require_finished, &boot_times); if (r < 0) return r; diff --git a/src/analyze/analyze-time.c b/src/analyze/analyze-time.c index da69eebeb4e9d..a4faee977f6aa 100644 --- a/src/analyze/analyze-time.c +++ b/src/analyze/analyze-time.c @@ -9,7 +9,7 @@ #include "bus-util.h" #include "runtime-scope.h" -int verb_time(int argc, char *argv[], void *userdata) { +int verb_time(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_free_ char *buf = NULL; int r; diff --git a/src/analyze/analyze-time.h b/src/analyze/analyze-time.h index a8f8575c3b6f7..23591d8fd4ddf 100644 --- a/src/analyze/analyze-time.h +++ b/src/analyze/analyze-time.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_time(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_time(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-timespan.c b/src/analyze/analyze-timespan.c index cb6e4f00fe121..de78736e2ca09 100644 --- a/src/analyze/analyze-timespan.c +++ b/src/analyze/analyze-timespan.c @@ -9,7 +9,7 @@ #include "strv.h" #include "time-util.h" -int verb_timespan(int argc, char *argv[], void *userdata) { +int verb_timespan(int argc, char *argv[], uintptr_t _data, void *userdata) { STRV_FOREACH(input_timespan, strv_skip(argv, 1)) { _cleanup_(table_unrefp) Table *table = NULL; usec_t output_usecs; @@ -28,14 +28,10 @@ int verb_timespan(int argc, char *argv[], void *userdata) { return log_oom(); assert_se(cell = table_get_cell(table, 0, 0)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); assert_se(cell = table_get_cell(table, 0, 1)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); r = table_add_many(table, TABLE_FIELD, "Original", @@ -55,7 +51,7 @@ int verb_timespan(int argc, char *argv[], void *userdata) { if (r < 0) return table_log_add_error(r); - r = table_print(table, NULL); + r = table_print_or_warn(table); if (r < 0) return r; diff --git a/src/analyze/analyze-timespan.h b/src/analyze/analyze-timespan.h index 46d2295600819..b2f238b413029 100644 --- a/src/analyze/analyze-timespan.h +++ b/src/analyze/analyze-timespan.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_timespan(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_timespan(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-timestamp.c b/src/analyze/analyze-timestamp.c index 2271133268345..50cda12d71865 100644 --- a/src/analyze/analyze-timestamp.c +++ b/src/analyze/analyze-timestamp.c @@ -27,14 +27,10 @@ static int test_timestamp_one(const char *p) { return log_oom(); assert_se(cell = table_get_cell(table, 0, 0)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); assert_se(cell = table_get_cell(table, 0, 1)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); r = table_add_many(table, TABLE_FIELD, "Original form", @@ -65,7 +61,7 @@ static int test_timestamp_one(const char *p) { usec / USEC_PER_SEC, usec % USEC_PER_SEC); if (r < 0) - return r; + return table_log_add_error(r); r = table_add_many(table, TABLE_FIELD, "From now", @@ -73,10 +69,10 @@ static int test_timestamp_one(const char *p) { if (r < 0) return table_log_add_error(r); - return table_print(table, NULL); + return table_print_or_warn(table); } -int verb_timestamp(int argc, char *argv[], void *userdata) { +int verb_timestamp(int argc, char *argv[], uintptr_t _data, void *userdata) { int r = 0; char **args = strv_skip(argv, 1); diff --git a/src/analyze/analyze-timestamp.h b/src/analyze/analyze-timestamp.h index 43e4b57d2a330..e71ab7055414c 100644 --- a/src/analyze/analyze-timestamp.h +++ b/src/analyze/analyze-timestamp.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_timestamp(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_timestamp(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-unit-files.c b/src/analyze/analyze-unit-files.c index 2d59d1808fdcb..a15815ddcc000 100644 --- a/src/analyze/analyze-unit-files.c +++ b/src/analyze/analyze-unit-files.c @@ -21,7 +21,7 @@ static bool strv_fnmatch_strv_or_empty(char* const* patterns, char **strv, int f return false; } -int verb_unit_files(int argc, char *argv[], void *userdata) { +int verb_unit_files(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_hashmap_free_ Hashmap *unit_ids = NULL, *unit_names = NULL; _cleanup_(lookup_paths_done) LookupPaths lp = {}; char **patterns = strv_skip(argv, 1); diff --git a/src/analyze/analyze-unit-files.h b/src/analyze/analyze-unit-files.h index c193fd82746a6..4dc46e7d7b323 100644 --- a/src/analyze/analyze-unit-files.h +++ b/src/analyze/analyze-unit-files.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_unit_files(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_unit_files(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-unit-gdb.c b/src/analyze/analyze-unit-gdb.c index 1d989462cf15d..b208f0b9289fe 100644 --- a/src/analyze/analyze-unit-gdb.c +++ b/src/analyze/analyze-unit-gdb.c @@ -19,7 +19,7 @@ #include "unit-def.h" #include "unit-name.h" -int verb_unit_gdb(int argc, char *argv[], void *userdata) { +int verb_unit_gdb(int argc, char *argv[], uintptr_t _data, void *userdata) { static const struct sigaction sa = { .sa_sigaction = sigterm_process_group_handler, .sa_flags = SA_SIGINFO, diff --git a/src/analyze/analyze-unit-gdb.h b/src/analyze/analyze-unit-gdb.h index a3df6b128f15c..4e62610c5864a 100644 --- a/src/analyze/analyze-unit-gdb.h +++ b/src/analyze/analyze-unit-gdb.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_unit_gdb(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_unit_gdb(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-unit-paths.c b/src/analyze/analyze-unit-paths.c index 500b768299080..3903166bf29f5 100644 --- a/src/analyze/analyze-unit-paths.c +++ b/src/analyze/analyze-unit-paths.c @@ -7,7 +7,7 @@ #include "path-lookup.h" #include "strv.h" -int verb_unit_paths(int argc, char *argv[], void *userdata) { +int verb_unit_paths(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(lookup_paths_done) LookupPaths paths = {}; int r; diff --git a/src/analyze/analyze-unit-paths.h b/src/analyze/analyze-unit-paths.h index b8d46e87d00a7..609c17074f0c4 100644 --- a/src/analyze/analyze-unit-paths.h +++ b/src/analyze/analyze-unit-paths.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_unit_paths(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_unit_paths(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-unit-shell.c b/src/analyze/analyze-unit-shell.c index 8990ffdf1de13..98dc474e6211c 100644 --- a/src/analyze/analyze-unit-shell.c +++ b/src/analyze/analyze-unit-shell.c @@ -20,7 +20,7 @@ #include "unit-def.h" #include "unit-name.h" -int verb_unit_shell(int argc, char *argv[], void *userdata) { +int verb_unit_shell(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; diff --git a/src/analyze/analyze-unit-shell.h b/src/analyze/analyze-unit-shell.h index 7c15e083a8e0f..533cd9a9892f7 100644 --- a/src/analyze/analyze-unit-shell.h +++ b/src/analyze/analyze-unit-shell.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_unit_shell(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_unit_shell(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-verify-util.c b/src/analyze/analyze-verify-util.c index dfa1cf7b3bc8a..e7ffae5a28787 100644 --- a/src/analyze/analyze-verify-util.c +++ b/src/analyze/analyze-verify-util.c @@ -268,6 +268,8 @@ static int verify_unit(Unit *u, bool check_man, const char *root) { } static void set_destroy_ignore_pointer_max(Set **s) { + assert(s); + if (*s == POINTER_MAX) return; set_free(*s); diff --git a/src/analyze/analyze-verify.c b/src/analyze/analyze-verify.c index b87fc45767d6d..3369f6c3b96a1 100644 --- a/src/analyze/analyze-verify.c +++ b/src/analyze/analyze-verify.c @@ -59,7 +59,7 @@ static int process_aliases(char *argv[], char *tempdir, char ***ret) { return 0; } -int verb_verify(int argc, char *argv[], void *userdata) { +int verb_verify(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **filenames = NULL; _cleanup_(rm_rf_physical_and_freep) char *tempdir = NULL; int r; diff --git a/src/analyze/analyze-verify.h b/src/analyze/analyze-verify.h index 4892c9aa4f532..0108ccf3a1b12 100644 --- a/src/analyze/analyze-verify.h +++ b/src/analyze/analyze-verify.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_verify(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_verify(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index c69f03ec155cc..e23b0038a9944 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -124,6 +124,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); int acquire_bus(sd_bus **bus, bool *use_full_bus) { int r; + POINTER_MAY_BE_NULL(use_full_bus); + if (use_full_bus && *use_full_bus) { r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, bus); if (IN_SET(r, 0, -EHOSTDOWN)) @@ -170,7 +172,7 @@ void time_parsing_hint(const char *p, bool calendar, bool timestamp, bool timesp "Use 'systemd-analyze timespan \"%s\"' instead?", p); } -static int verb_transient_settings(int argc, char *argv[], void *userdata) { +static int verb_transient_settings(int argc, char *argv[], uintptr_t _data, void *userdata) { assert(argc >= 2); pager_open(arg_pager_flags); @@ -193,7 +195,7 @@ static int verb_transient_settings(int argc, char *argv[], void *userdata) { return 0; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL, *dot_link = NULL; int r; @@ -258,6 +260,7 @@ static int help(int argc, char *argv[], void *userdata) { " dlopen-metadata FILE Parse and print ELF dlopen metadata\n" "\n%3$sTPM Operations:%4$s\n" " has-tpm2 Report whether TPM2 support is available\n" + " identify-tpm2 Show TPM2 vendor information\n" " pcrs [PCR...] Show TPM2 PCRs and their names\n" " nvpcrs [NVPCR...] Show additional TPM2 PCRs stored in NV indexes\n" " srk [>FILE] Write TPM2 SRK (to FILE)\n" @@ -325,6 +328,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, @@ -465,7 +472,7 @@ static int parse_argv(int argc, char *argv[]) { break; case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -772,7 +779,7 @@ static int run(int argc, char *argv[]) { _cleanup_(umount_and_freep) char *mounted_dir = NULL; static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, { "time", VERB_ANY, 1, VERB_DEFAULT, verb_time }, { "blame", VERB_ANY, 1, 0, verb_blame }, { "critical-chain", VERB_ANY, VERB_ANY, 0, verb_critical_chain }, @@ -810,6 +817,7 @@ static int run(int argc, char *argv[]) { { "fdstore", 2, VERB_ANY, 0, verb_fdstore }, { "image-policy", 2, 2, 0, verb_image_policy }, { "has-tpm2", VERB_ANY, 1, 0, verb_has_tpm2 }, + { "identify-tpm2", VERB_ANY, 1, 0, verb_identify_tpm2 }, { "pcrs", VERB_ANY, VERB_ANY, 0, verb_pcrs }, { "nvpcrs", VERB_ANY, VERB_ANY, 0, verb_nvpcrs }, { "srk", VERB_ANY, 1, 0, verb_srk }, diff --git a/src/analyze/test-verify.c b/src/analyze/test-verify.c index 8355ae6a807f5..048d866dbe73c 100644 --- a/src/analyze/test-verify.c +++ b/src/analyze/test-verify.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "analyze.h" #include "analyze-verify-util.h" #include "execute.h" #include "tests.h" diff --git a/src/ask-password/ask-password.c b/src/ask-password/ask-password.c index f6634b54f6891..4bd618b2a7f09 100644 --- a/src/ask-password/ask-password.c +++ b/src/ask-password/ask-password.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-varlink.h" @@ -10,10 +9,12 @@ #include "build.h" #include "bus-polkit.h" #include "constants.h" +#include "format-table.h" #include "hashmap.h" #include "json-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "string-table.h" @@ -39,124 +40,74 @@ STATIC_DESTRUCTOR_REGISTER(arg_message, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-ask-password", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] MESSAGE\n\n" - "%3$sQuery the user for a passphrase, via the TTY or a UI agent.%4$s\n\n" - " -h --help Show this help\n" - " --icon=NAME Icon name\n" - " --id=ID Query identifier (e.g. \"cryptsetup:/dev/sda5\")\n" - " --keyname=NAME Kernel key name for caching passwords (e.g. \"cryptsetup\")\n" - " --credential=NAME\n" - " Credential name for ImportCredential=, LoadCredential= or\n" - " SetCredential= credentials\n" - " --timeout=SEC Timeout in seconds\n" - " --echo=yes|no|masked\n" - " Control whether to show password while typing (echo)\n" - " -e --echo Equivalent to --echo=yes\n" - " --emoji=yes|no|auto\n" - " Show a lock and key emoji\n" - " --no-tty Ask question via agent even on TTY\n" - " --accept-cached Accept cached passwords\n" - " --multiple List multiple passwords if available\n" - " --no-output Do not print password to standard output\n" - " -n Do not suffix password written to standard output with\n" - " newline\n" - " --user Ask only our own user's agents\n" - " --system Ask agents of the system and of all users\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] MESSAGE\n" + "\n%sQuery the user for a passphrase, via the TTY or a UI agent.%s\n" + "\nOptions:\n", program_invocation_short_name, - link, ansi_highlight(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_ICON = 0x100, - ARG_TIMEOUT, - ARG_EMOJI, - ARG_NO_TTY, - ARG_ACCEPT_CACHED, - ARG_MULTIPLE, - ARG_ID, - ARG_KEYNAME, - ARG_NO_OUTPUT, - ARG_VERSION, - ARG_CREDENTIAL, - ARG_USER, - ARG_SYSTEM, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "icon", required_argument, NULL, ARG_ICON }, - { "timeout", required_argument, NULL, ARG_TIMEOUT }, - { "echo", optional_argument, NULL, 'e' }, - { "emoji", required_argument, NULL, ARG_EMOJI }, - { "no-tty", no_argument, NULL, ARG_NO_TTY }, - { "accept-cached", no_argument, NULL, ARG_ACCEPT_CACHED }, - { "multiple", no_argument, NULL, ARG_MULTIPLE }, - { "id", required_argument, NULL, ARG_ID }, - { "keyname", required_argument, NULL, ARG_KEYNAME }, - { "no-output", no_argument, NULL, ARG_NO_OUTPUT }, - { "credential", required_argument, NULL, ARG_CREDENTIAL }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - {} - }; - const char *emoji = NULL; - int c, r; + int r; assert(argc >= 0); assert(argv); - /* Note the asymmetry: the long option --echo= allows an optional argument, the short option does - * not. */ - - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; - while ((c = getopt_long(argc, argv, "+hen", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ICON: - arg_icon = optarg; + OPTION_LONG("icon", "NAME", "Icon name"): + arg_icon = arg; break; - case ARG_TIMEOUT: - r = parse_sec(optarg, &arg_timeout); + OPTION_LONG("timeout", "SEC", "Timeout in seconds"): + r = parse_sec(arg, &arg_timeout); if (r < 0) - return log_error_errno(r, "Failed to parse --timeout= parameter: %s", optarg); - + return log_error_errno(r, "Failed to parse --timeout= parameter: %s", arg); break; - case 'e': - if (!optarg) { + /* Note the asymmetry: the long option --echo= allows an optional argument, + * the short option does not. */ + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "echo", "yes|no|masked", + "Control whether to show password while typing"): {} + OPTION('e', "echo", NULL, "Equivalent to --echo=yes"): + if (!arg) { /* Short option -e is used, or no argument to long option --echo= */ arg_flags |= ASK_PASSWORD_ECHO; arg_flags &= ~ASK_PASSWORD_SILENT; - } else if (isempty(optarg) || streq(optarg, "masked")) + } else if (isempty(arg) || streq(arg, "masked")) /* Empty argument or explicit string "masked" for default behaviour. */ arg_flags &= ~(ASK_PASSWORD_ECHO|ASK_PASSWORD_SILENT); else { - r = parse_boolean_argument("--echo=", optarg, NULL); + r = parse_boolean_argument("--echo=", arg, NULL); if (r < 0) return r; @@ -165,55 +116,50 @@ static int parse_argv(int argc, char *argv[]) { } break; - case ARG_EMOJI: - emoji = optarg; + OPTION_LONG("emoji", "yes|no|auto", "Show a lock and key emoji"): + emoji = arg; break; - case ARG_NO_TTY: + OPTION_LONG("no-tty", NULL, "Ask question via agent even on TTY"): arg_flags |= ASK_PASSWORD_NO_TTY; break; - case ARG_ACCEPT_CACHED: + OPTION_LONG("accept-cached", NULL, "Accept cached passwords"): arg_flags |= ASK_PASSWORD_ACCEPT_CACHED; break; - case ARG_MULTIPLE: + OPTION_LONG("multiple", NULL, "List multiple passwords if available"): arg_multiple = true; break; - case ARG_ID: - arg_id = optarg; + OPTION_LONG("id", "ID", "Query identifier (e.g. \"cryptsetup:/dev/sda5\")"): + arg_id = arg; break; - case ARG_KEYNAME: - arg_key_name = optarg; + OPTION_LONG("keyname", "NAME", "Kernel key name for caching passwords"): + arg_key_name = arg; break; - case ARG_NO_OUTPUT: + OPTION_LONG("no-output", NULL, "Do not print password to standard output"): arg_no_output = true; break; - case ARG_CREDENTIAL: - arg_credential_name = optarg; + OPTION_LONG("credential", "NAME", + "Credential name for ImportCredential=, LoadCredential= or SetCredential= credentials"): + arg_credential_name = arg; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Ask only our own user's agents"): arg_flags |= ASK_PASSWORD_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Ask agents of the system and of all users"): arg_flags &= ~ASK_PASSWORD_USER; break; - case 'n': + OPTION_SHORT('n', NULL, "Do not suffix password written to standard output with newline"): arg_newline = false; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (isempty(emoji) || streq(emoji, "auto")) @@ -226,8 +172,10 @@ static int parse_argv(int argc, char *argv[]) { SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, !r); } - if (argc > optind) { - arg_message = strv_join(argv + optind, " "); + char **args = option_parser_get_args(&state); + + if (!strv_isempty(args)) { + arg_message = strv_join(args, " "); if (!arg_message) return log_oom(); } else if (FLAGS_SET(arg_flags, ASK_PASSWORD_ECHO)) { diff --git a/src/backlight/backlight.c b/src/backlight/backlight.c index 027379809d10f..69bd42ebcc828 100644 --- a/src/backlight/backlight.c +++ b/src/backlight/backlight.c @@ -555,7 +555,7 @@ static int device_new_from_arg(const char *s, sd_device **ret) { return 1; /* Found. */ } -static int verb_load(int argc, char *argv[], void *userdata) { +static int verb_load(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_device_unrefp) sd_device *device = NULL; unsigned max_brightness, brightness, percent; bool clamp; @@ -605,7 +605,7 @@ static int verb_load(int argc, char *argv[], void *userdata) { return 0; } -static int verb_save(int argc, char *argv[], void *userdata) { +static int verb_save(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_device_unrefp) sd_device *device = NULL; _cleanup_free_ char *path = NULL; unsigned max_brightness, brightness; diff --git a/src/basic/alloc-util.c b/src/basic/alloc-util.c index 58b70dede4b01..3a813575c3afd 100644 --- a/src/basic/alloc-util.c +++ b/src/basic/alloc-util.c @@ -34,6 +34,46 @@ void* memdup_suffix0(const void *p, size_t l) { return memcpy_safe(ret, p, l); } +size_t malloc_sizeof_safe(void **xp) { + POINTER_MAY_BE_NULL(xp); + + if (_unlikely_(!xp || !*xp)) + return 0; + + size_t sz = malloc_usable_size(*xp); + *xp = expand_to_usable(*xp, sz); + /* GCC doesn't see the _returns_nonnull_ when built with ubsan, so yet another hint to make it doubly + * clear that expand_to_usable won't return NULL. + * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79265 */ + if (!*xp) + assert_not_reached(); + return sz; +} + +void* expand_to_usable(void *ptr, size_t newsize _unused_) { + return ptr; +} + +void* realloc0(void *p, size_t new_size) { + size_t old_size; + void *q; + + /* Like realloc(), but initializes anything appended to zero */ + + old_size = MALLOC_SIZEOF_SAFE(p); + + q = realloc(p, new_size); + if (!q) + return NULL; + + new_size = MALLOC_SIZEOF_SAFE(q); /* Update with actually allocated space */ + + if (new_size > old_size) + memset((uint8_t*) q + old_size, 0, new_size - old_size); + + return q; +} + void* greedy_realloc( void **p, size_t need, @@ -125,20 +165,9 @@ void* greedy_realloc_append( return q; } -void *expand_to_usable(void *ptr, size_t newsize _unused_) { - return ptr; -} - -size_t malloc_sizeof_safe(void **xp) { - if (_unlikely_(!xp || !*xp)) - return 0; +void free_many(void **p, size_t n) { + assert(p || n == 0); - size_t sz = malloc_usable_size(*xp); - *xp = expand_to_usable(*xp, sz); - /* GCC doesn't see the _returns_nonnull_ when built with ubsan, so yet another hint to make it doubly - * clear that expand_to_usable won't return NULL. - * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79265 */ - if (!*xp) - assert_not_reached(); - return sz; + FOREACH_ARRAY(i, p, n) + *i = mfree(*i); } diff --git a/src/basic/alloc-util.h b/src/basic/alloc-util.h index cd062d5fe3efc..2c40c4771cdc3 100644 --- a/src/basic/alloc-util.h +++ b/src/basic/alloc-util.h @@ -108,6 +108,34 @@ static inline void *memdup_suffix0_multiply(const void *p, size_t need, size_t s return memdup_suffix0(p, size * need); } +size_t malloc_sizeof_safe(void **xp); + +/* This returns the number of usable bytes in a malloc()ed region as per malloc_usable_size(), which may + * return a value larger than the size that was actually allocated. Access to that additional memory is + * discouraged because it violates the C standard; a compiler cannot see that this as valid. To help the + * compiler out, the MALLOC_SIZEOF_SAFE macro 'allocates' the usable size using a dummy allocator function + * expand_to_usable. There is a possibility of malloc_usable_size() returning different values during the + * lifetime of an object, which may cause problems, but the glibc allocator does not do that at the moment. */ +#define MALLOC_SIZEOF_SAFE(x) \ + malloc_sizeof_safe((void**) &__builtin_choose_expr(__builtin_constant_p(x), (void*) { NULL }, (x))) + +/* Inspired by ELEMENTSOF() but operates on malloc()'ed memory areas: typesafely returns the number of items + * that fit into the specified memory block */ +#define MALLOC_ELEMENTSOF(x) \ + (__builtin_choose_expr( \ + __builtin_types_compatible_p(typeof(x), typeof(&*(x))), \ + MALLOC_SIZEOF_SAFE(x)/sizeof((x)[0]), \ + VOID_0)) + +/* Dummy allocator to tell the compiler that the new size of p is newsize. The implementation returns the + * pointer as is; the only reason for its existence is as a conduit for the _alloc_ attribute. This must not + * be inlined (hence a non-static function with _noinline_ because LTO otherwise tries to inline it) because + * gcc then loses the attributes on the function. + * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96503 */ +void *expand_to_usable(void *p, size_t newsize) _alloc_(2) _returns_nonnull_ _noinline_; + +void* realloc0(void *p, size_t new_size) _alloc_(2); + static inline size_t GREEDY_ALLOC_ROUND_UP(size_t l) { size_t m; @@ -179,61 +207,10 @@ void* greedy_realloc_append(void **p, size_t *n_p, const void *from, size_t n_fr # define msan_unpoison(r, s) #endif -/* Dummy allocator to tell the compiler that the new size of p is newsize. The implementation returns the - * pointer as is; the only reason for its existence is as a conduit for the _alloc_ attribute. This must not - * be inlined (hence a non-static function with _noinline_ because LTO otherwise tries to inline it) because - * gcc then loses the attributes on the function. - * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96503 */ -void *expand_to_usable(void *p, size_t newsize) _alloc_(2) _returns_nonnull_ _noinline_; - -size_t malloc_sizeof_safe(void **xp); - -/* This returns the number of usable bytes in a malloc()ed region as per malloc_usable_size(), which may - * return a value larger than the size that was actually allocated. Access to that additional memory is - * discouraged because it violates the C standard; a compiler cannot see that this as valid. To help the - * compiler out, the MALLOC_SIZEOF_SAFE macro 'allocates' the usable size using a dummy allocator function - * expand_to_usable. There is a possibility of malloc_usable_size() returning different values during the - * lifetime of an object, which may cause problems, but the glibc allocator does not do that at the moment. */ -#define MALLOC_SIZEOF_SAFE(x) \ - malloc_sizeof_safe((void**) &__builtin_choose_expr(__builtin_constant_p(x), (void*) { NULL }, (x))) - -/* Inspired by ELEMENTSOF() but operates on malloc()'ed memory areas: typesafely returns the number of items - * that fit into the specified memory block */ -#define MALLOC_ELEMENTSOF(x) \ - (__builtin_choose_expr( \ - __builtin_types_compatible_p(typeof(x), typeof(&*(x))), \ - MALLOC_SIZEOF_SAFE(x)/sizeof((x)[0]), \ - VOID_0)) - /* Free every element of the array. */ -static inline void free_many(void **p, size_t n) { - assert(p || n == 0); - - FOREACH_ARRAY(i, p, n) - *i = mfree(*i); -} +void free_many(void **p, size_t n); /* Typesafe wrapper for char** rather than void**. Unfortunately C won't implicitly cast this. */ static inline void free_many_charp(char **c, size_t n) { free_many((void**) c, n); } - -_alloc_(2) static inline void *realloc0(void *p, size_t new_size) { - size_t old_size; - void *q; - - /* Like realloc(), but initializes anything appended to zero */ - - old_size = MALLOC_SIZEOF_SAFE(p); - - q = realloc(p, new_size); - if (!q) - return NULL; - - new_size = MALLOC_SIZEOF_SAFE(q); /* Update with actually allocated space */ - - if (new_size > old_size) - memset((uint8_t*) q + old_size, 0, new_size - old_size); - - return q; -} diff --git a/src/basic/ansi-color.c b/src/basic/ansi-color.c index e5c78c93458f6..36cdca727c93d 100644 --- a/src/basic/ansi-color.c +++ b/src/basic/ansi-color.c @@ -58,16 +58,20 @@ static ColorMode get_color_mode_impl(void) { if (m >= 0 && m < _COLOR_MODE_FIXED_MAX) return m; - /* Next, check for the presence of $NO_COLOR; value is ignored. */ - if (m != COLOR_TRUE && getenv("NO_COLOR")) - return COLOR_OFF; - - /* If the above didn't work, we turn colors off unless we are on a TTY. And if we are on a TTY we - * turn it off if $TERM is set to "dumb". There's one special tweak though: if we are PID 1 then we - * do not check whether we are connected to a TTY, because we don't keep /dev/console open - * continuously due to fear of SAK, and hence things are a bit weird. */ - if (getpid_cached() == 1 ? getenv_terminal_is_dumb() : terminal_is_dumb()) - return COLOR_OFF; + /* If SYSTEMD_COLORS=true was set explicitly, skip the environment checks below — the user + * explicitly requested colors, so honor it even when stdout is piped or $NO_COLOR is set. */ + if (m != COLOR_TRUE) { + /* Check for the presence of $NO_COLOR; value is ignored. */ + if (getenv("NO_COLOR")) + return COLOR_OFF; + + /* Turn colors off unless we are on a TTY. And if we are on a TTY we turn it off if $TERM + * is set to "dumb". There's one special tweak though: if we are PID 1 then we do not check + * whether we are connected to a TTY, because we don't keep /dev/console open continuously + * due to fear of SAK, and hence things are a bit weird. */ + if (getpid_cached() == 1 ? getenv_terminal_is_dumb() : terminal_is_dumb()) + return COLOR_OFF; + } /* We failed to figure out any reason to *disable* colors. Let's see how many colors we shall use. */ if (m == COLOR_AUTO_16) diff --git a/src/basic/ansi-color.h b/src/basic/ansi-color.h index e686e1167b14e..20f6b3bf62dcf 100644 --- a/src/basic/ansi-color.h +++ b/src/basic/ansi-color.h @@ -91,6 +91,8 @@ bool looks_like_ansi_color_code(const char *str); #define ANSI_UNDERLINE "\x1B[0;4m" #define ANSI_ADD_UNDERLINE "\x1B[4m" #define ANSI_ADD_UNDERLINE_GREY ANSI_ADD_UNDERLINE "\x1B[58:5:245m" +#define ANSI_ITALICS "\x1B[0;3m" +#define ANSI_ADD_ITALICS "\x1B[3m" #define ANSI_HIGHLIGHT "\x1B[0;1;39m" #define ANSI_HIGHLIGHT_UNDERLINE "\x1B[0;1;4m" @@ -111,41 +113,20 @@ bool looks_like_ansi_color_code(const char *str); return colors_enabled() ? ANSI_##NAME : ""; \ } -#define DEFINE_ANSI_FUNC_256(name, NAME, FALLBACK) \ - static inline const char* ansi_##name(void) { \ - switch (get_color_mode()) { \ - case COLOR_OFF: return ""; \ - case COLOR_16: return ANSI_##FALLBACK; \ - default : return ANSI_##NAME; \ - } \ - } - -static inline const char* ansi_underline(void) { - return underline_enabled() ? ANSI_UNDERLINE : ""; -} - -static inline const char* ansi_add_underline(void) { - return underline_enabled() ? ANSI_ADD_UNDERLINE : ""; -} - -static inline const char* ansi_add_underline_grey(void) { - return underline_enabled() ? - (colors_enabled() ? ANSI_ADD_UNDERLINE_GREY : ANSI_ADD_UNDERLINE) : ""; -} - -#define DEFINE_ANSI_FUNC_UNDERLINE(name, NAME) \ - static inline const char* ansi_##name(void) { \ - return underline_enabled() ? ANSI_##NAME##_UNDERLINE : \ - colors_enabled() ? ANSI_##NAME : ""; \ - } - -#define DEFINE_ANSI_FUNC_UNDERLINE_256(name, NAME, FALLBACK) \ - static inline const char* ansi_##name(void) { \ - switch (get_color_mode()) { \ - case COLOR_OFF: return ""; \ - case COLOR_16: return underline_enabled() ? ANSI_##FALLBACK##_UNDERLINE : ANSI_##FALLBACK; \ - default : return underline_enabled() ? ANSI_##NAME##_UNDERLINE: ANSI_##NAME; \ - } \ +/* NB: in 256 mode we always emit the fallback color first, in order to deal with terminals with + * incomplete 256 color support (most notably Linux console, which a) lacks support for ":" + * subcommand separator and b) skips over the whole CSI-m sequence if it sees an "invalid" command). + * In 24-bit mode we don't bother with this however, under the assumption that $COLORTERM and friends + * reflect the correct status. */ + +#define DEFINE_ANSI_FUNC_256(name, NAME, FALLBACK) \ + static inline const char* ansi_##name(void) { \ + switch (get_color_mode()) { \ + case COLOR_OFF: return ""; \ + case COLOR_16: return ANSI_##FALLBACK; \ + case COLOR_256: return ANSI_##FALLBACK ANSI_##NAME; \ + default: return ANSI_##NAME; \ + } \ } DEFINE_ANSI_FUNC(normal, NORMAL); @@ -184,15 +165,56 @@ static inline const char* _ansi_highlight_yellow(void) { return colors_enabled() ? _ANSI_HIGHLIGHT_YELLOW : ""; } -DEFINE_ANSI_FUNC_UNDERLINE(highlight_underline, HIGHLIGHT); -DEFINE_ANSI_FUNC_UNDERLINE_256(grey_underline, GREY, BRIGHT_BLACK); -DEFINE_ANSI_FUNC_UNDERLINE(highlight_red_underline, HIGHLIGHT_RED); -DEFINE_ANSI_FUNC_UNDERLINE(highlight_green_underline, HIGHLIGHT_GREEN); -DEFINE_ANSI_FUNC_UNDERLINE_256(highlight_yellow_underline, HIGHLIGHT_YELLOW, HIGHLIGHT_YELLOW_FALLBACK); -DEFINE_ANSI_FUNC_UNDERLINE(highlight_blue_underline, HIGHLIGHT_BLUE); -DEFINE_ANSI_FUNC_UNDERLINE(highlight_magenta_underline, HIGHLIGHT_MAGENTA); -DEFINE_ANSI_FUNC_UNDERLINE_256(highlight_grey_underline, HIGHLIGHT_GREY, HIGHLIGHT_GREY_FALLBACK); - static inline const char* ansi_highlight_green_red(bool b) { return b ? ansi_highlight_green() : ansi_highlight_red(); } + +static inline const char* ansi_underline(void) { + return underline_enabled() ? ANSI_UNDERLINE : ""; +} + +static inline const char* ansi_add_underline(void) { + return underline_enabled() ? ANSI_ADD_UNDERLINE : ""; +} + +static inline const char* ansi_add_underline_grey(void) { + return underline_enabled() ? + (colors_enabled() ? ANSI_ADD_UNDERLINE_GREY : ANSI_ADD_UNDERLINE) : ""; +} + +static inline const char* ansi_italics(void) { + /* We hook italics also into the underline checks, close enough */ + return underline_enabled() ? ANSI_ITALICS : ""; +} + +static inline const char* ansi_add_italics(void) { + return underline_enabled() ? ANSI_ADD_ITALICS : ""; +} + +#define DEFINE_ANSI_FUNC_UNDERLINE(name, NAME) \ + static inline const char* ansi_##name##_underline(void) { \ + return underline_enabled() ? ANSI_##NAME##_UNDERLINE : \ + ansi_##name(); \ + } + +#define DEFINE_ANSI_FUNC_UNDERLINE_256(name, NAME, FALLBACK) \ + static inline const char* ansi_##name##_underline(void) { \ + if (!underline_enabled()) \ + return ansi_##name(); \ + \ + switch (get_color_mode()) { \ + case COLOR_OFF: return ""; \ + case COLOR_16: return ANSI_##FALLBACK##_UNDERLINE; \ + case COLOR_256: return ANSI_##FALLBACK##_UNDERLINE ANSI_##NAME##_UNDERLINE; \ + default: return ANSI_##NAME##_UNDERLINE; \ + } \ + } + +DEFINE_ANSI_FUNC_UNDERLINE(highlight, HIGHLIGHT); +DEFINE_ANSI_FUNC_UNDERLINE(highlight_red, HIGHLIGHT_RED); +DEFINE_ANSI_FUNC_UNDERLINE(highlight_green, HIGHLIGHT_GREEN); +DEFINE_ANSI_FUNC_UNDERLINE_256(highlight_yellow, HIGHLIGHT_YELLOW, HIGHLIGHT_YELLOW_FALLBACK); +DEFINE_ANSI_FUNC_UNDERLINE(highlight_blue, HIGHLIGHT_BLUE); +DEFINE_ANSI_FUNC_UNDERLINE(highlight_magenta, HIGHLIGHT_MAGENTA); +DEFINE_ANSI_FUNC_UNDERLINE_256(grey, GREY, BRIGHT_BLACK); +DEFINE_ANSI_FUNC_UNDERLINE_256(highlight_grey, HIGHLIGHT_GREY, HIGHLIGHT_GREY_FALLBACK); diff --git a/src/basic/basic-forward.h b/src/basic/basic-forward.h index 1ca9ecfeff43e..396056a8e55eb 100644 --- a/src/basic/basic-forward.h +++ b/src/basic/basic-forward.h @@ -69,6 +69,15 @@ struct fdisk_context; struct fdisk_table; struct crypt_device; +typedef struct buf_mem_st BUF_MEM; +typedef struct evp_pkey_st EVP_PKEY; +typedef struct evp_md_st EVP_MD; +typedef struct evp_md_ctx_st EVP_MD_CTX; +typedef struct ssl_st SSL; +typedef struct ssl_ctx_st SSL_CTX; +typedef struct ssl_session_st SSL_SESSION; +typedef struct x509_st X509; + /* basic/ forward declarations */ typedef void (*hash_func_t)(const void *p, struct siphash *state); @@ -111,6 +120,7 @@ typedef struct Set Set; typedef struct dual_timestamp dual_timestamp; typedef struct triple_timestamp triple_timestamp; +typedef struct Compressor Compressor; typedef struct ConfFile ConfFile; typedef struct LockFile LockFile; typedef struct PidRef PidRef; diff --git a/src/basic/build-path.c b/src/basic/build-path.c index ddbaf4ee3c64c..44e04d98aca5b 100644 --- a/src/basic/build-path.c +++ b/src/basic/build-path.c @@ -195,6 +195,8 @@ static int find_build_dir_binary(const char *fn, char **ret) { static int find_environment_binary(const char *fn, const char **ret) { + assert(ret); + /* If a path such as /usr/lib/systemd/systemd-foobar is specified, then this will check for an * environment variable SYSTEMD_FOOBAR_PATH and return it if set. */ diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index d613e65c4b820..6a40ede29ed66 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -1256,6 +1256,8 @@ bool cg_needs_escape(const char *p) { int cg_escape(const char *p, char **ret) { _cleanup_free_ char *n = NULL; + assert(ret); + /* This implements very minimal escaping for names to be used as file names in the cgroup tree: any * name which might conflict with a kernel name or is prefixed with '_' is prefixed with a '_'. That * way, when reading cgroup names it is sufficient to remove a single prefixing underscore if there @@ -1560,6 +1562,24 @@ int cg_get_keyed_attribute( return r; } +int cg_get_keyed_attribute_uint64(const char *path, const char *attribute, const char *key, uint64_t *ret) { + _cleanup_free_ char *val = NULL; + int r; + + assert(key); + assert(ret); + + r = cg_get_keyed_attribute(path, attribute, STRV_MAKE(key), &val); + if (r < 0) + return r; + + r = safe_atou64(val, ret); + if (r < 0) + return log_debug_errno(r, "Failed to parse value '%s' of key '%s' in cgroup attribute '%s': %m", val, key, attribute); + + return 0; +} + int cg_mask_to_string(CGroupMask mask, char **ret) { _cleanup_free_ char *s = NULL; bool space = false; @@ -1634,6 +1654,8 @@ int cg_mask_supported_subtree(const char *root, CGroupMask *ret) { CGroupMask mask; int r; + assert(ret); + /* Determines the mask of supported cgroup controllers. Only includes controllers we can make sense of and that * are actually accessible. Only covers real controllers, i.e. not the CGROUP_CONTROLLER_BPF_xyz * pseudo-controllers. */ diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h index 68a771e5aed64..7cf0f779b8b95 100644 --- a/src/basic/cgroup-util.h +++ b/src/basic/cgroup-util.h @@ -165,6 +165,7 @@ int cg_get_attribute_as_uint64(const char *path, const char *attribute, uint64_t int cg_get_attribute_as_bool(const char *path, const char *attribute); int cg_get_keyed_attribute(const char *path, const char *attribute, char * const *keys, char **values); +int cg_get_keyed_attribute_uint64(const char *path, const char *attribute, const char *key, uint64_t *ret); int cg_get_owner(const char *path, uid_t *ret_uid); diff --git a/src/basic/chase.c b/src/basic/chase.c index abe85a2a0892b..50ab4e3c47495 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "alloc-util.h" @@ -18,28 +17,87 @@ #include "strv.h" #include "user-util.h" +/* Flags that prevent us from taking any of the early shortcuts: either they change the path resolution + * semantics (e.g. CHASE_NONEXISTENT, CHASE_PARENT, CHASE_STEP) or ask for per-component validation that a + * single open() cannot provide (e.g. CHASE_SAFE, CHASE_NO_AUTOFS, CHASE_PROHIBIT_SYMLINKS). + * + * Notably, the following are *not* listed here: + * - CHASE_TRIGGER_AUTOFS: plain open() already triggers automounts, and O_PATH shortcuts can use + * XO_TRIGGER_AUTOMOUNT to tell xopenat_full() to use open_tree() instead. + * - CHASE_MUST_BE_{DIRECTORY,REGULAR,SOCKET}: xopenat_full() can enforce these via O_DIRECTORY, + * XO_REGULAR and XO_SOCKET. Shortcut callers that don't go through xopenat_full() (stat/access + * paths) must include CHASE_MUST_BE_ANY in their local mask to still bail on these. */ #define CHASE_NO_SHORTCUT_MASK \ (CHASE_NONEXISTENT | \ CHASE_NO_AUTOFS | \ - CHASE_TRIGGER_AUTOFS | \ CHASE_SAFE | \ CHASE_STEP | \ CHASE_PROHIBIT_SYMLINKS | \ CHASE_PARENT | \ - CHASE_MKDIR_0755 | \ - CHASE_MUST_BE_DIRECTORY | \ - CHASE_MUST_BE_REGULAR | \ - CHASE_MUST_BE_SOCKET) + CHASE_MKDIR_0755) + +#define CHASE_MUST_BE_ANY \ + (CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_SOCKET) + +static int chase_statx(int fd, struct statx *ret) { + return xstatx_full(fd, + /* path= */ NULL, + /* statx_flags= */ 0, + XSTATX_MNT_ID_BEST, + STATX_TYPE|STATX_UID|STATX_INO, + /* optional_mask= */ 0, + /* mandatory_attributes= */ 0, + ret); +} + +static int chase_xopenat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, XOpenFlags xopen_flags) { + /* Wrapper around xopenat_full() that translates CHASE_NOFOLLOW, CHASE_MUST_BE_* and + * CHASE_TRIGGER_AUTOFS into their xopenat_full() counterparts. Used by shortcuts that want to open + * the final target of a chase operation: they all want O_NOFOLLOW honoured, MUST_BE_* verified on + * the opened inode, and automounts triggered if requested. */ + + if (FLAGS_SET(chase_flags, CHASE_NOFOLLOW)) + open_flags |= O_NOFOLLOW; + if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY)) + open_flags |= O_DIRECTORY; + if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR)) + xopen_flags |= XO_REGULAR; + if (FLAGS_SET(chase_flags, CHASE_MUST_BE_SOCKET)) + xopen_flags |= XO_SOCKET; + /* Only needed for O_PATH since plain open() already triggers automounts */ + if (FLAGS_SET(chase_flags, CHASE_TRIGGER_AUTOFS) && FLAGS_SET(open_flags, O_PATH)) + xopen_flags |= XO_TRIGGER_AUTOMOUNT; -bool unsafe_transition(const struct stat *a, const struct stat *b) { - /* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to - * privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files - * making us believe we read something safe even though it isn't safe in the specific context we open it in. */ + return xopenat_full(dir_fd, path, open_flags, xopen_flags, MODE_INVALID); +} - if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */ +static bool uid_unsafe_transition(uid_t a, uid_t b) { + /* Returns true if the transition from a to b is safe, i.e. that we never transition from + * unprivileged to privileged files or directories. Why bother? So that unprivileged code can't + * symlink to privileged files making us believe we read something safe even though it isn't safe in + * the specific context we open it in. */ + + if (a == 0) /* Transitioning from privileged to unprivileged is always fine */ return false; - return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */ + return a != b; /* Otherwise we need to stay within the same UID */ +} + +int statx_unsafe_transition(const struct statx *a, const struct statx *b) { + assert(a); + assert(b); + + if (!FLAGS_SET(a->stx_mask, STATX_UID) || !FLAGS_SET(b->stx_mask, STATX_UID)) + return -ENODATA; + + return uid_unsafe_transition(a->stx_uid, b->stx_uid); +} + +bool stat_unsafe_transition(const struct stat *a, const struct stat *b) { + assert(a); + assert(b); + + return uid_unsafe_transition(a->st_uid, b->st_uid); } static int log_unsafe_transition(int a, int b, const char *path, ChaseFlags flags) { @@ -90,95 +148,40 @@ static int log_prohibited_symlink(int fd, ChaseFlags flags) { strna(n1)); } -static int openat_opath_with_automount(int dir_fd, const char *path, bool automount) { - static bool can_open_tree = true; - int r; - - /* Pin an inode via O_PATH semantics. Sounds pretty obvious to do this, right? You just do open() - * with O_PATH, and there you go. But uh, it's not that easy. open() via O_PATH does not trigger - * automounts, but we may want that when CHASE_TRIGGER_AUTOFS is set. But thankfully there's - * a way out: the newer open_tree() call, when specified without OPEN_TREE_CLONE actually is fully - * equivalent to open() with O_PATH – except for one thing: it triggers automounts. - * - * As it turns out some sandboxes prohibit open_tree(), and return EPERM or ENOSYS if we call it. - * But since autofs does not work inside of mount namespace anyway, let's simply handle this - * as gracefully as we can, and fall back to classic openat() if we see EPERM/ENOSYS. */ - - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - assert(path); - - if (automount && can_open_tree) { - r = RET_NERRNO(open_tree(dir_fd, path, AT_SYMLINK_NOFOLLOW|OPEN_TREE_CLOEXEC)); - if (r >= 0 || (r != -EPERM && !ERRNO_IS_NEG_NOT_SUPPORTED(r))) - return r; - - can_open_tree = false; - } - - return RET_NERRNO(openat(dir_fd, path, O_PATH|O_NOFOLLOW|O_CLOEXEC)); -} - -static int chaseat_needs_absolute(int dir_fd, const char *path) { - if (dir_fd < 0) - return path_is_absolute(path); - - return dir_fd_is_root(dir_fd); -} - -int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) { - _cleanup_free_ char *buffer = NULL, *done = NULL; - _cleanup_close_ int fd = -EBADF, root_fd = -EBADF; - unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */ - bool exists = true, append_trail_slash = false; - struct stat st; /* stat obtained from fd */ - bool need_absolute = false; /* allocate early to avoid compiler warnings around goto */ - const char *todo; +int chaseat(int root_fd, int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) { int r; assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT)); assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME)); assert(!FLAGS_SET(flags, CHASE_NO_AUTOFS|CHASE_TRIGGER_AUTOFS)); assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); + assert(root_fd >= 0 || IN_SET(root_fd, AT_FDCWD, XAT_FDROOT)); + /* AT_FDCWD for dir_fd is only allowed when there is no chroot boundary: otherwise the current + * working directory might live outside root_fd's subtree. */ + assert(dir_fd != AT_FDCWD || IN_SET(root_fd, AT_FDCWD, XAT_FDROOT)); if (FLAGS_SET(flags, CHASE_STEP)) assert(!ret_fd); - if (isempty(path)) - path = "."; - - /* This function resolves symlinks of the path relative to the given directory file descriptor. If - * CHASE_AT_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks - * are resolved relative to the given directory file descriptor. Otherwise, they are resolved - * relative to the root directory of the host. + /* This function resolves symlinks of the path relative to the given directory file descriptor. + * The root directory file descriptor sets the chroot boundary: symlinks may not escape it, and + * absolute symlinks encountered during resolution are resolved relative to it. When the root fd is + * XAT_FDROOT, symlinks are resolved relative to the host's root directory with no containment. * - * Note that when a positive directory file descriptor is provided and CHASE_AT_RESOLVE_IN_ROOT is - * specified and we find an absolute symlink, it is resolved relative to given directory file - * descriptor and not the root of the host. Also, when following relative symlinks, this functions - * ensures they cannot be used to "escape" the given directory file descriptor. If a positive - * directory file descriptor is provided, the "path" parameter is always interpreted relative to the - * given directory file descriptor, even if it is absolute. If the given directory file descriptor is - * AT_FDCWD and "path" is absolute, it is interpreted relative to the root directory of the host. + * The given path is always resolved starting at dir_fd, regardless of whether it is absolute or + * relative. The leading slashes of an absolute path are ignored. The only exceptions are + * dir_fd == XAT_FDROOT (which starts resolution at root_fd) and dir_fd == AT_FDCWD with an absolute + * path (which starts resolution at "/" rather than the current working directory). * - * When "dir_fd" points to a non-root directory and CHASE_AT_RESOLVE_IN_ROOT is set, this function - * always returns a relative path in "ret_path", even if "path" is an absolute path, because openat() - * like functions generally ignore the directory fd if they are provided with an absolute path. When - * CHASE_AT_RESOLVE_IN_ROOT is not set, then this returns relative path to the specified file - * descriptor if all resolved symlinks are relative, otherwise absolute path will be returned. When - * "dir_fd" is AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path" - * because otherwise, if the caller passes the returned relative path to another openat() like - * function, it would be resolved relative to the current working directory instead of to "/". + * Note that we do not verify that dir_fd actually points to a descendant of root_fd. If dir_fd + * lies outside the root_fd subtree, ".." traversal and absolute symlinks may still be clamped to + * root_fd, leading to surprising results. Callers must ensure the relationship themselves. * - * Summary about the result path: - * - "dir_fd" points to the root directory - * → result will be absolute - * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is set - * → relative - * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is not set - * → relative when all resolved symlinks are relative, otherwise absolute - * - "dir_fd" is AT_FDCWD, and "path" is absolute - * → absolute - * - "dir_fd" is AT_FDCWD, and "path" is relative - * → relative when all resolved symlinks are relative, otherwise absolute + * Absolute paths returned by this function are relative to the given root file descriptor. Relative + * paths returned by this function are relative to the given directory file descriptor. The result is + * absolute when root_fd is XAT_FDROOT (i.e. there is no chroot boundary, so openat()-like callers + * need an absolute path to reach the host inode), or when an absolute symlink made us jump to a + * different subtree than the one dir_fd points into. Otherwise the result is relative. * * Algorithmically this operates on two path buffers: "done" are the components of the path we * already processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we @@ -186,11 +189,6 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int * its special meaning each time. We always keep an O_PATH fd to the component we are currently * processing, thus keeping lookup races to a minimum. * - * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute - * path you got as-is: fully qualified and relative to your host's root. Optionally, specify the - * "dir_fd" parameter to tell this function what to do when encountering a symlink with an absolute - * path as directory: resolve it relative to the given directory file descriptor. - * * There are five ways to invoke this function: * * 1. Without CHASE_STEP or ret_fd: in this case the path is resolved and the normalized path is @@ -221,125 +219,127 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int * the mount point is emitted. CHASE_WARN cannot be used in PID 1. */ - _cleanup_close_ int _dir_fd = -EBADF; + /* We treat AT_FDCWD as XAT_FDROOT for a more seamless migration for all callers of chaseat() before + * it was reworked to support separate root_fd and dir_fd arguments. */ + if (root_fd == AT_FDCWD) + root_fd = XAT_FDROOT; + else { + r = dir_fd_is_root(root_fd); + if (r < 0) + return r; + if (r > 0) + root_fd = XAT_FDROOT; + } + + /* If dir_fd points to the host's root directory and there is no chroot boundary, normalize it + * to XAT_FDROOT so the shortcut path can kick in. */ r = dir_fd_is_root(dir_fd); if (r < 0) return r; - if (r > 0) { + if (r > 0 && root_fd == XAT_FDROOT) + dir_fd = XAT_FDROOT; - /* Shortcut the common case where no root dir is specified, and no special flags are given to - * a regular open() */ - if (!ret_path && - (flags & (CHASE_STEP|CHASE_NO_AUTOFS|CHASE_NONEXISTENT|CHASE_SAFE|CHASE_WARN|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) { - _cleanup_free_ char *slash_path = NULL; + /* dir_fd == XAT_FDROOT means "start at root_fd". An absolute path is always resolved relative to + * root_fd, regardless of what dir_fd points to. */ + if (dir_fd == XAT_FDROOT || path_is_absolute(path)) + dir_fd = root_fd; - if (!path_is_absolute(path)) { - slash_path = strjoin("/", path); - if (!slash_path) - return -ENOMEM; - } - - /* We use open_tree() rather than regular open() here, because it gives us direct - * control over automount behaviour, and otherwise is equivalent to open() with - * O_PATH */ - fd = open_tree(-EBADF, slash_path ?: path, OPEN_TREE_CLOEXEC|(FLAGS_SET(flags, CHASE_TRIGGER_AUTOFS) ? 0 : AT_NO_AUTOMOUNT)); - if (fd < 0) - return -errno; + if (isempty(path)) + path = "."; - if (fstat(fd, &st) < 0) - return -errno; + bool append_trail_slash = false; + if (ENDSWITH_SET(path, "/", "/.")) { + flags |= CHASE_MUST_BE_DIRECTORY; + if (FLAGS_SET(flags, CHASE_TRAIL_SLASH)) + append_trail_slash = true; + } else if (dot_or_dot_dot(path) || endswith(path, "/..")) + flags |= CHASE_MUST_BE_DIRECTORY; - exists = true; - goto success; - } + if (FLAGS_SET(flags, CHASE_PARENT)) + flags |= CHASE_MUST_BE_DIRECTORY; - _dir_fd = open("/", O_DIRECTORY|O_RDONLY|O_CLOEXEC); - if (_dir_fd < 0) - return -errno; + /* If multiple flags are set now, fail immediately */ + if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY) + FLAGS_SET(flags, CHASE_MUST_BE_REGULAR) + FLAGS_SET(flags, CHASE_MUST_BE_SOCKET) > 1) + return -EBADSLT; - dir_fd = _dir_fd; - flags &= ~CHASE_AT_RESOLVE_IN_ROOT; - } else if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) { - /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to - * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */ + if (root_fd == XAT_FDROOT && !ret_path && (flags & CHASE_NO_SHORTCUT_MASK) == 0) { + /* Shortcut the common case where we don't have a real root boundary and no fancy features + * are requested: open the target directly via xopenat_full() which applies any MUST_BE_* + * verification and automount triggering for us. */ - r = dir_fd_is_root_or_cwd(dir_fd); + r = chase_xopenat(dir_fd, path, flags, O_PATH|O_CLOEXEC, /* xopen_flags= */ 0); if (r < 0) return r; - if (r > 0) - flags &= ~CHASE_AT_RESOLVE_IN_ROOT; - } - if (!ret_path && ret_fd && (flags & (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NO_SHORTCUT_MASK)) == 0) { - /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root - * set and doesn't care about any of the other special features we provide either. */ - r = openat(dir_fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0)); - if (r < 0) - return -errno; + if (ret_fd) + *ret_fd = r; + else + safe_close(r); - *ret_fd = r; - return 0; + return 1; } - buffer = strdup(path); - if (!buffer) - return -ENOMEM; - - /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because - * a relative path would be interpreted relative to the current working directory. Also, let's make - * the result absolute when the file descriptor of the root directory is specified. */ - r = chaseat_needs_absolute(dir_fd, path); - if (r < 0) - return r; + /* Decide whether to return an absolute or relative path. + * + * We return an absolute path only when there is no chroot boundary (root_fd == XAT_FDROOT) + * and resolution starts from root — i.e. either dir_fd was XAT_FDROOT or path is absolute, + * both of which caused dir_fd = root_fd above. In every other case we return a relative + * path so the result keeps working when fed to an openat()-style call against dir_fd, + * which would ignore dir_fd if handed an absolute path. + * + * When root_fd != XAT_FDROOT and an absolute symlink later causes resolution to escape + * dir_fd, the loop below rebases onto root_fd and switches to an absolute result at that + * point — it is not handled here. + */ + bool need_absolute = (root_fd == XAT_FDROOT || dir_fd != root_fd) && (dir_fd == XAT_FDROOT || path_is_absolute(path)); - need_absolute = r; + _cleanup_free_ char *done = NULL; if (need_absolute) { done = strdup("/"); if (!done) return -ENOMEM; } - /* If a positive directory file descriptor is provided, always resolve the given path relative to it, - * regardless of whether it is absolute or not. If we get AT_FDCWD, follow regular openat() - * semantics, if the path is relative, resolve against the current working directory. Otherwise, - * resolve against root. */ - fd = openat(dir_fd, done ?: ".", O_CLOEXEC|O_DIRECTORY|O_PATH); + _cleanup_close_ int fd = xopenat(dir_fd, NULL, O_CLOEXEC|O_DIRECTORY|O_PATH); if (fd < 0) - return -errno; - - if (fstat(fd, &st) < 0) - return -errno; - - /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive - * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine - * whether to resolve symlinks in it or not. */ - if (dir_fd >= 0 && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) - root_fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH); - else - root_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH); - if (root_fd < 0) - return -errno; + return fd; - if (ENDSWITH_SET(buffer, "/", "/.")) { - flags |= CHASE_MUST_BE_DIRECTORY; - if (FLAGS_SET(flags, CHASE_TRAIL_SLASH)) - append_trail_slash = true; - } else if (dot_or_dot_dot(buffer) || endswith(buffer, "/..")) - flags |= CHASE_MUST_BE_DIRECTORY; + struct statx stx; + r = chase_statx(fd, &stx); + if (r < 0) + return r; - if (FLAGS_SET(flags, CHASE_PARENT)) - flags |= CHASE_MUST_BE_DIRECTORY; + /* Remember stat data of the root, so that we can recognize it later during .. handling. Only + * needed when there is an actual chroot boundary — with root_fd == XAT_FDROOT the boundary + * check in the .. loop below is skipped and root_stx is never consulted. */ + struct statx root_stx; + if (root_fd != XAT_FDROOT) { + if (root_fd == dir_fd) + root_stx = stx; + else { + r = chase_statx(root_fd, &root_stx); + if (r < 0) + return r; + } + } - /* If multiple flags are set now, fail immediately */ - if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY) + FLAGS_SET(flags, CHASE_MUST_BE_REGULAR) + FLAGS_SET(flags, CHASE_MUST_BE_SOCKET) > 1) - return -EBADSLT; + _cleanup_free_ char *buffer = strdup(path); + if (!buffer) + return -ENOMEM; - for (todo = buffer;;) { + const char *todo = buffer; + bool exists = true; + for (unsigned n_steps = 0;; n_steps++) { _cleanup_free_ char *first = NULL; _cleanup_close_ int child = -EBADF; - struct stat st_child; + struct statx stx_child; const char *e; + /* If people change our tree behind our back, they might send us in circles. Put a limit on + * things */ + if (n_steps > CHASE_MAX) + return -ELOOP; + r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e); if (r < 0) return r; @@ -354,27 +354,46 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (streq(first, "..")) { _cleanup_free_ char *parent = NULL; _cleanup_close_ int fd_parent = -EBADF; - struct stat st_parent; + struct statx stx_parent; /* If we already are at the top, then going up will not change anything. This is - * in-line with how the kernel handles this. */ - if (empty_or_root(done) && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) { - if (FLAGS_SET(flags, CHASE_STEP)) - goto chased_one; - continue; + * in-line with how the kernel handles this. We check this both by path and by + * inode/mount identity check. The latter is load-bearing if concurrent access of the + * root tree we operate in is allowed, where an inode is moved up the tree while we + * look at it, and thus get the current path wrong and think we are deeper down than + * we actually are. + * + * The path-based fast path is only valid when the caller started at the root fd: + * otherwise 'done' being empty just means we haven't descended past the starting + * dir_fd, not that we're at the chroot boundary. */ + if (root_fd != XAT_FDROOT) { + bool is_root = root_fd == dir_fd && empty_or_root(done); + if (!is_root && statx_inode_same(&stx, &root_stx)) { + r = statx_mount_same(&stx, &root_stx); + if (r < 0) + return r; + + is_root = r > 0; + } + if (is_root) { + if (FLAGS_SET(flags, CHASE_STEP)) + goto chased_one; + continue; + } } fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY); if (fd_parent < 0) return -errno; - if (fstat(fd_parent, &st_parent) < 0) - return -errno; + r = chase_statx(fd_parent, &stx_parent); + if (r < 0) + return r; /* If we opened the same directory, that _may_ indicate that we're at the host root * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so, * going up won't change anything. */ - if (stat_inode_same(&st_parent, &st)) { + if (statx_inode_same(&stx_parent, &stx)) { r = dir_fd_is_root(fd); if (r < 0) return r; @@ -394,18 +413,19 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int assert(!need_absolute); done = mfree(done); } else if (r == -EADDRNOTAVAIL) { - /* 'done' is "/". This branch should be already handled in the above. */ - assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)); + /* 'done' is "/". This branch should already be handled above via the + * is_root check. */ assert_not_reached(); } else if (r == -EINVAL) { - /* 'done' is an empty string, ends with '..', or an invalid path. */ + /* 'done' is empty (we haven't descended past the starting dir_fd yet), or + * ends with '..'. In both cases we're traversing above the starting point + * (valid when root_fd is XAT_FDROOT, or when dir_fd was below root_fd to + * start with), so record another '..' in 'done'. */ assert(!need_absolute); - assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)); - if (!path_is_valid(done)) + if (!isempty(done) && !path_is_valid(done)) return -EINVAL; - /* If we're at the top of "dir_fd", start appending ".." to "done". */ if (!path_extend(&done, "..")) return -ENOMEM; } else @@ -414,41 +434,53 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (FLAGS_SET(flags, CHASE_STEP)) goto chased_one; - if (FLAGS_SET(flags, CHASE_SAFE) && - unsafe_transition(&st, &st_parent)) - return log_unsafe_transition(fd, fd_parent, path, flags); + if (FLAGS_SET(flags, CHASE_SAFE)) { + r = statx_unsafe_transition(&stx, &stx_parent); + if (r < 0) + return r; + if (r > 0) + return log_unsafe_transition(fd, fd_parent, path, flags); + } /* If the path ends on a "..", and CHASE_PARENT is specified then our current 'fd' is * the child of the returned normalized path, not the parent as requested. To correct * this we have to go *two* levels up. */ if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) { _cleanup_close_ int fd_grandparent = -EBADF; - struct stat st_grandparent; + struct statx stx_grandparent; fd_grandparent = openat(fd_parent, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY); if (fd_grandparent < 0) return -errno; - if (fstat(fd_grandparent, &st_grandparent) < 0) - return -errno; + r = chase_statx(fd_grandparent, &stx_grandparent); + if (r < 0) + return r; - if (FLAGS_SET(flags, CHASE_SAFE) && - unsafe_transition(&st_parent, &st_grandparent)) - return log_unsafe_transition(fd_parent, fd_grandparent, path, flags); + if (FLAGS_SET(flags, CHASE_SAFE)) { + r = statx_unsafe_transition(&stx_parent, &stx_grandparent); + if (r < 0) + return r; + if (r > 0) + return log_unsafe_transition(fd_parent, fd_grandparent, path, flags); + } - st = st_grandparent; + stx = stx_grandparent; close_and_replace(fd, fd_grandparent); break; } /* update fd and stat */ - st = st_parent; + stx = stx_parent; close_and_replace(fd, fd_parent); continue; } /* Otherwise let's pin it by file descriptor, via O_PATH. */ - child = r = openat_opath_with_automount(fd, first, /* automount= */ FLAGS_SET(flags, CHASE_TRIGGER_AUTOFS)); + child = r = xopenat_full(fd, first, + O_PATH|O_NOFOLLOW|O_CLOEXEC, + FLAGS_SET(flags, CHASE_TRIGGER_AUTOFS) ? XO_TRIGGER_AUTOMOUNT : 0, + MODE_INVALID); if (r < 0) { if (r != -ENOENT) return r; @@ -477,29 +509,28 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int return r; } - /* ... and then check what it actually is. */ - if (fstat(child, &st_child) < 0) - return -errno; + r = chase_statx(child, &stx_child); + if (r < 0) + return r; - if (FLAGS_SET(flags, CHASE_SAFE) && - unsafe_transition(&st, &st_child)) - return log_unsafe_transition(fd, child, path, flags); + if (FLAGS_SET(flags, CHASE_SAFE)) { + r = statx_unsafe_transition(&stx, &stx_child); + if (r < 0) + return r; + if (r > 0) + return log_unsafe_transition(fd, child, path, flags); + } if (FLAGS_SET(flags, CHASE_NO_AUTOFS) && fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0) return log_autofs_mount_point(child, path, flags); - if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) { + if (S_ISLNK(stx_child.stx_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) { _cleanup_free_ char *destination = NULL; if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS)) return log_prohibited_symlink(child, flags); - /* This is a symlink, in this case read the destination. But let's make sure we - * don't follow symlinks without bounds. */ - if (--max_follow <= 0) - return -ELOOP; - r = readlinkat_malloc(fd, first, &destination); if (r < 0) return r; @@ -516,16 +547,19 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd < 0) return fd; - if (fstat(fd, &st) < 0) - return -errno; + r = chase_statx(fd, &stx); + if (r < 0) + return r; - if (FLAGS_SET(flags, CHASE_SAFE) && - unsafe_transition(&st_child, &st)) - return log_unsafe_transition(child, fd, path, flags); + if (FLAGS_SET(flags, CHASE_SAFE)) { + r = statx_unsafe_transition(&stx_child, &stx); + if (r < 0) + return r; + if (r > 0) + return log_unsafe_transition(child, fd, path, flags); + } - /* When CHASE_AT_RESOLVE_IN_ROOT is not set, now the chased path may be - * outside of the specified dir_fd. Let's make the result absolute. */ - if (!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) + if (dir_fd != root_fd) need_absolute = true; r = free_and_strdup(&done, need_absolute ? "/" : NULL); @@ -555,26 +589,25 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int break; /* And iterate again, but go one directory further down. */ - st = st_child; + stx = stx_child; close_and_replace(fd, child); } -success: if (exists) { if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) { - r = stat_verify_directory(&st); + r = statx_verify_directory(&stx); if (r < 0) return r; } if (FLAGS_SET(flags, CHASE_MUST_BE_REGULAR)) { - r = stat_verify_regular(&st); + r = statx_verify_regular(&stx); if (r < 0) return r; } if (FLAGS_SET(flags, CHASE_MUST_BE_SOCKET)) { - r = stat_verify_socket(&st); + r = statx_verify_socket(&stx); if (r < 0) return r; } @@ -667,14 +700,9 @@ int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, return r; /* A root directory of "/" or "" is identical to "/". */ - if (empty_or_root(root)) { + if (empty_or_root(root)) root = "/"; - - /* When the root directory is "/", we will drop CHASE_AT_RESOLVE_IN_ROOT in chaseat(), - * hence below is not necessary, but let's shortcut. */ - flags &= ~CHASE_AT_RESOLVE_IN_ROOT; - - } else { + else { r = path_make_absolute_cwd(root, &root_abs); if (r < 0) return r; @@ -691,8 +719,6 @@ int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, if (!absolute) return -ENOMEM; } - - flags |= CHASE_AT_RESOLVE_IN_ROOT; } if (!absolute) { @@ -716,7 +742,7 @@ int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, return -errno; } - r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL); + r = chaseat(fd, fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL); if (r < 0) return r; @@ -839,36 +865,34 @@ int chase_and_open( _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL, *fname = NULL; + const char *open_name = NULL; int r; assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); - XOpenFlags xopen_flags = 0; - if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY)) - open_flags |= O_DIRECTORY; - if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR)) - xopen_flags |= XO_REGULAR; - if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) /* Shortcut this call if none of the special features of this call are requested */ - return xopenat_full(AT_FDCWD, path, - open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), - xopen_flags, - MODE_INVALID); + return chase_xopenat(AT_FDCWD, path, chase_flags, open_flags, /* xopen_flags= */ 0); r = chase(path, root, (CHASE_PARENT|chase_flags)&~CHASE_MUST_BE_REGULAR, &p, &path_fd); if (r < 0) return r; assert(path_fd >= 0); - if (!FLAGS_SET(chase_flags, CHASE_PARENT) && - !FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) { - r = chase_extract_filename(p, root, &fname); - if (r < 0) - return r; + if (!FLAGS_SET(chase_flags, CHASE_PARENT)) { + if (FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) + /* chase() with CHASE_EXTRACT_FILENAME already returns just the filename in + * p — use it directly without redundant extraction. */ + open_name = p; + else { + r = chase_extract_filename(p, root, &fname); + if (r < 0) + return r; + open_name = fname; + } } - r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, xopen_flags, MODE_INVALID); + r = chase_xopenat(path_fd, strempty(open_name), chase_flags, open_flags|O_NOFOLLOW, /* xopen_flags= */ 0); if (r < 0) return r; @@ -922,8 +946,10 @@ int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, c assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); assert(ret_stat); - if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) - /* Shortcut this call if none of the special features of this call are requested */ + if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0) + /* Shortcut this call if none of the special features of this call are requested. We can't + * take the shortcut if CHASE_MUST_BE_* is set because fstatat() alone does not verify the + * inode type. */ return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); @@ -949,8 +975,8 @@ int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, assert(path); assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); - if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) - /* Shortcut this call if none of the special features of this call are requested */ + if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0) + /* Shortcut this call if none of the special features of this call are requested. */ return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); @@ -1010,7 +1036,7 @@ int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int r; assert(path); - assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); + assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT|CHASE_MUST_BE_SOCKET|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_DIRECTORY|CHASE_EXTRACT_FILENAME|CHASE_MKDIR_0755))); fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); if (fd < 0) @@ -1042,6 +1068,7 @@ int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_f } int chase_and_openat( + int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, @@ -1050,39 +1077,33 @@ int chase_and_openat( _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL, *fname = NULL; + const char *open_name = NULL; int r; assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_MUST_BE_SOCKET))); - XOpenFlags xopen_flags = 0; - if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY)) - open_flags |= O_DIRECTORY; - if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR)) - xopen_flags |= XO_REGULAR; - - if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) + if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) /* Shortcut this call if none of the special features of this call are requested */ - return xopenat_full(dir_fd, path, - open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), - xopen_flags, - MODE_INVALID); + return chase_xopenat(dir_fd, path, chase_flags, open_flags, /* xopen_flags= */ 0); - r = chaseat(dir_fd, path, (chase_flags|CHASE_PARENT)&~CHASE_MUST_BE_REGULAR, &p, &path_fd); + r = chaseat(root_fd, dir_fd, path, (chase_flags|CHASE_PARENT)&~CHASE_MUST_BE_REGULAR, &p, &path_fd); if (r < 0) return r; if (!FLAGS_SET(chase_flags, CHASE_PARENT)) { - r = path_extract_filename(p, &fname); - if (r < 0 && r != -EADDRNOTAVAIL) - return r; + if (FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) + /* chaseat() with CHASE_EXTRACT_FILENAME already returns just the filename in + * p — use it directly without redundant extraction. */ + open_name = p; + else { + r = path_extract_filename(p, &fname); + if (r < 0 && r != -EADDRNOTAVAIL) + return r; + open_name = fname; + } } - r = xopenat_full( - path_fd, - strempty(fname), - open_flags|O_NOFOLLOW, - xopen_flags, - MODE_INVALID); + r = chase_xopenat(path_fd, strempty(open_name), chase_flags, open_flags|O_NOFOLLOW, /* xopen_flags= */ 0); if (r < 0) return r; @@ -1092,7 +1113,7 @@ int chase_and_openat( return r; } -int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) { +int chase_and_opendirat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) { _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL; DIR *d; @@ -1101,7 +1122,7 @@ int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, ch assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_SOCKET))); assert(ret_dir); - if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) { + if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) { /* Shortcut this call if none of the special features of this call are requested */ d = opendir(path); if (!d) @@ -1111,7 +1132,7 @@ int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, ch return 0; } - r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); + r = chaseat(root_fd, dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); if (r < 0) return r; assert(path_fd >= 0); @@ -1127,7 +1148,7 @@ int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, ch return 0; } -int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) { +int chase_and_statat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) { _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL; int r; @@ -1136,12 +1157,14 @@ int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); assert(ret_stat); - if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) - /* Shortcut this call if none of the special features of this call are requested */ + if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0) + /* Shortcut this call if none of the special features of this call are requested. We can't + * take the shortcut if CHASE_MUST_BE_* is set because fstatat() alone does not verify the + * inode type. */ return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); - r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); + r = chaseat(root_fd, dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); if (r < 0) return r; assert(path_fd >= 0); @@ -1155,7 +1178,7 @@ int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char return 0; } -int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) { +int chase_and_accessat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) { _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL; int r; @@ -1163,12 +1186,12 @@ int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int assert(path); assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); - if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) - /* Shortcut this call if none of the special features of this call are requested */ + if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0) + /* Shortcut this call if none of the special features of this call are requested. */ return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); - r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); + r = chaseat(root_fd, dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); if (r < 0) return r; assert(path_fd >= 0); @@ -1184,6 +1207,7 @@ int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int } int chase_and_fopenat_unlocked( + int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, @@ -1204,7 +1228,7 @@ int chase_and_fopenat_unlocked( if (mode_flags < 0) return mode_flags; - fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL); + fd = chase_and_openat(root_fd, dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL); if (fd < 0) return fd; @@ -1218,15 +1242,15 @@ int chase_and_fopenat_unlocked( return 0; } -int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) { +int chase_and_unlinkat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) { _cleanup_free_ char *p = NULL, *fname = NULL; _cleanup_close_ int fd = -EBADF; int r; assert(path); - assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); + assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT|CHASE_MUST_BE_SOCKET|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_DIRECTORY|CHASE_EXTRACT_FILENAME|CHASE_MKDIR_0755))); - fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); + fd = chase_and_openat(root_fd, dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); if (fd < 0) return fd; @@ -1243,12 +1267,12 @@ int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int return 0; } -int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) { +int chase_and_open_parent_at(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) { int pfd, r; assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); - r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd); + r = chaseat(root_fd, dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd); if (r < 0) return r; diff --git a/src/basic/chase.h b/src/basic/chase.h index d0674aae73c99..afb50fa61ec7c 100644 --- a/src/basic/chase.h +++ b/src/basic/chase.h @@ -15,27 +15,26 @@ typedef enum ChaseFlags { * right-most component refers to symlink, return O_PATH fd of the symlink. */ CHASE_WARN = 1 << 8, /* Emit an appropriate warning when an error is encountered. * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */ - CHASE_AT_RESOLVE_IN_ROOT = 1 << 9, /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved - * relative to the given directory fd instead of root. */ - CHASE_PROHIBIT_SYMLINKS = 1 << 10, /* Refuse all symlinks */ - CHASE_PARENT = 1 << 11, /* Chase the parent directory of the given path. Note that the + CHASE_PROHIBIT_SYMLINKS = 1 << 9, /* Refuse all symlinks */ + CHASE_PARENT = 1 << 10, /* Chase the parent directory of the given path. Note that the * full path is still stored in ret_path and only the returned * file descriptor will point to the parent directory. Note that * the result path is the root or '.', then the file descriptor * also points to the result path even if this flag is set. * When this specified, chase() will succeed with 1 even if the * file points to the last path component does not exist. */ - CHASE_MKDIR_0755 = 1 << 12, /* Create any missing directories in the given path. */ - CHASE_EXTRACT_FILENAME = 1 << 13, /* Only return the last component of the resolved path */ - CHASE_MUST_BE_DIRECTORY = 1 << 14, /* Fail if returned inode fd is not a dir */ - CHASE_MUST_BE_REGULAR = 1 << 15, /* Fail if returned inode fd is not a regular file */ - CHASE_MUST_BE_SOCKET = 1 << 16, /* Fail if returned inode fd is not a socket */ + CHASE_MKDIR_0755 = 1 << 11, /* Create any missing directories in the given path. */ + CHASE_EXTRACT_FILENAME = 1 << 12, /* Only return the last component of the resolved path */ + CHASE_MUST_BE_DIRECTORY = 1 << 13, /* Fail if returned inode fd is not a dir */ + CHASE_MUST_BE_REGULAR = 1 << 14, /* Fail if returned inode fd is not a regular file */ + CHASE_MUST_BE_SOCKET = 1 << 15, /* Fail if returned inode fd is not a socket */ } ChaseFlags; -bool unsafe_transition(const struct stat *a, const struct stat *b); +int statx_unsafe_transition(const struct statx *a, const struct statx *b); +bool stat_unsafe_transition(const struct stat *a, const struct stat *b); /* How many iterations to execute before returning -ELOOP */ -#define CHASE_MAX 32 +#define CHASE_MAX 128U int chase(const char *path_with_prefix, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd); @@ -50,12 +49,12 @@ int chase_and_fopen_unlocked(const char *path, const char *root, ChaseFlags chas int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path); int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename); -int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd); +int chaseat(int root_fd, int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd); -int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path); -int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir); -int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat); -int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path); -int chase_and_fopenat_unlocked(int dir_fd, const char *path, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file); -int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path); -int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename); +int chase_and_openat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path); +int chase_and_opendirat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir); +int chase_and_statat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat); +int chase_and_accessat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path); +int chase_and_fopenat_unlocked(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file); +int chase_and_unlinkat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path); +int chase_and_open_parent_at(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename); diff --git a/src/basic/cleanup-util.h b/src/basic/cleanup-util.h index 9fd48dbf29733..041c37530aaa9 100644 --- a/src/basic/cleanup-util.h +++ b/src/basic/cleanup-util.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "assert-fundamental.h" #include "cleanup-fundamental.h" /* IWYU pragma: export */ typedef void (*free_func_t)(void *p); @@ -89,3 +90,16 @@ typedef void* (*mfree_func_t)(void *p); #define DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(type, name, free_func) \ DEFINE_PUBLIC_TRIVIAL_REF_FUNC(type, name); \ DEFINE_PUBLIC_TRIVIAL_UNREF_FUNC(type, name, free_func); + +typedef void (*void_func_t)(void); + +static inline void dispatch_void_func(void_func_t *f) { + assert(f); + assert(*f); + (*f)(); +} + +/* Inspired by Go's "defer" construct, but much more basic. This basically just calls a void function when + * the current scope is left. Doesn't do function parameters (i.e. no closures). */ +#define DEFER_VOID_CALL(x) _DEFER_VOID_CALL(UNIQ, x) +#define _DEFER_VOID_CALL(uniq, x) _unused_ _cleanup_(dispatch_void_func) void_func_t UNIQ_T(defer, uniq) = (x) diff --git a/src/basic/compress.c b/src/basic/compress.c index 82d856aa06783..8386bdb1b1df7 100644 --- a/src/basic/compress.c +++ b/src/basic/compress.c @@ -1,18 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include -#include #include #include +#if HAVE_XZ +#include +#endif + #if HAVE_LZ4 #include -#include #include -#endif - -#if HAVE_XZ -#include +#include #endif #if HAVE_ZSTD @@ -20,17 +20,47 @@ #include #endif +#if HAVE_ZLIB +#include +#endif + +#if HAVE_BZIP2 +#include +#endif + +#include "sd-dlopen.h" + #include "alloc-util.h" #include "bitfield.h" #include "compress.h" #include "dlfcn-util.h" -#include "fileio.h" #include "io-util.h" #include "log.h" #include "string-table.h" -#include "string-util.h" #include "unaligned.h" +#if HAVE_XZ +static void *lzma_dl = NULL; + +static DLSYM_PROTOTYPE(lzma_code) = NULL; +static DLSYM_PROTOTYPE(lzma_easy_encoder) = NULL; +static DLSYM_PROTOTYPE(lzma_end) = NULL; +static DLSYM_PROTOTYPE(lzma_stream_buffer_encode) = NULL; +static DLSYM_PROTOTYPE(lzma_stream_decoder) = NULL; +static DLSYM_PROTOTYPE(lzma_lzma_preset) = NULL; + +/* We can’t just do _cleanup_(sym_lzma_end) because a compiler bug makes + * this fail with: + * ../src/basic/compress.c: In function ‘decompress_blob_xz’: + * ../src/basic/compress.c:304:9: error: cleanup argument not a function + * 304 | _cleanup_(sym_lzma_end) lzma_stream s = LZMA_STREAM_INIT; + * | ^~~~~~~~~ + */ +static inline void lzma_end_wrapper(lzma_stream *ls) { + sym_lzma_end(ls); +} +#endif + #if HAVE_LZ4 static void *lz4_dl = NULL; @@ -45,14 +75,14 @@ static DLSYM_PROTOTYPE(LZ4F_freeCompressionContext) = NULL; static DLSYM_PROTOTYPE(LZ4F_freeDecompressionContext) = NULL; static DLSYM_PROTOTYPE(LZ4F_isError) = NULL; static DLSYM_PROTOTYPE(LZ4_compress_HC) = NULL; -/* These are used in test-compress.c so we don't make them static. */ -DLSYM_PROTOTYPE(LZ4_compress_default) = NULL; -DLSYM_PROTOTYPE(LZ4_decompress_safe) = NULL; -DLSYM_PROTOTYPE(LZ4_decompress_safe_partial) = NULL; -DLSYM_PROTOTYPE(LZ4_versionNumber) = NULL; +static DLSYM_PROTOTYPE(LZ4_compress_default) = NULL; +static DLSYM_PROTOTYPE(LZ4_decompress_safe) = NULL; +static DLSYM_PROTOTYPE(LZ4_decompress_safe_partial) = NULL; +static DLSYM_PROTOTYPE(LZ4_versionNumber) = NULL; -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(LZ4F_compressionContext_t, sym_LZ4F_freeCompressionContext, LZ4F_freeCompressionContextp, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(LZ4F_decompressionContext_t, sym_LZ4F_freeDecompressionContext, LZ4F_freeDecompressionContextp, NULL); +static const LZ4F_preferences_t lz4_preferences = { + .frameInfo.blockSizeID = 5, +}; #endif #if HAVE_ZSTD @@ -75,7 +105,6 @@ static DLSYM_PROTOTYPE(ZSTD_getErrorName) = NULL; static DLSYM_PROTOTYPE(ZSTD_getFrameContentSize) = NULL; static DLSYM_PROTOTYPE(ZSTD_isError) = NULL; -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ZSTD_CCtx*, sym_ZSTD_freeCCtx, ZSTD_freeCCtxp, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ZSTD_DCtx*, sym_ZSTD_freeDCtx, ZSTD_freeDCtxp, NULL); static int zstd_ret_to_errno(size_t ret) { @@ -90,53 +119,146 @@ static int zstd_ret_to_errno(size_t ret) { } #endif -#if HAVE_XZ -static void *lzma_dl = NULL; +#if HAVE_ZLIB +static void *zlib_dl = NULL; -static DLSYM_PROTOTYPE(lzma_code) = NULL; -static DLSYM_PROTOTYPE(lzma_easy_encoder) = NULL; -static DLSYM_PROTOTYPE(lzma_end) = NULL; -static DLSYM_PROTOTYPE(lzma_stream_buffer_encode) = NULL; -static DLSYM_PROTOTYPE(lzma_stream_decoder) = NULL; -static DLSYM_PROTOTYPE(lzma_lzma_preset) = NULL; +static DLSYM_PROTOTYPE(deflateInit2_) = NULL; +static DLSYM_PROTOTYPE(deflate) = NULL; +static DLSYM_PROTOTYPE(deflateEnd) = NULL; +static DLSYM_PROTOTYPE(inflateInit2_) = NULL; +static DLSYM_PROTOTYPE(inflate) = NULL; +static DLSYM_PROTOTYPE(inflateEnd) = NULL; -/* We can't just do _cleanup_(sym_lzma_end) because a compiler bug makes - * this fail with: - * ../src/basic/compress.c: In function ‘decompress_blob_xz’: - * ../src/basic/compress.c:304:9: error: cleanup argument not a function - * 304 | _cleanup_(sym_lzma_end) lzma_stream s = LZMA_STREAM_INIT; - * | ^~~~~~~~~ - */ -static inline void lzma_end_wrapper(lzma_stream *ls) { - sym_lzma_end(ls); +static inline void deflateEnd_wrapper(z_stream *s) { + sym_deflateEnd(s); +} + +static inline void inflateEnd_wrapper(z_stream *s) { + sym_inflateEnd(s); +} +#endif + +#if HAVE_BZIP2 +static void *bzip2_dl = NULL; + +static DLSYM_PROTOTYPE(BZ2_bzCompressInit) = NULL; +static DLSYM_PROTOTYPE(BZ2_bzCompress) = NULL; +static DLSYM_PROTOTYPE(BZ2_bzCompressEnd) = NULL; +static DLSYM_PROTOTYPE(BZ2_bzDecompressInit) = NULL; +static DLSYM_PROTOTYPE(BZ2_bzDecompress) = NULL; +static DLSYM_PROTOTYPE(BZ2_bzDecompressEnd) = NULL; + +static inline void BZ2_bzCompressEnd_wrapper(bz_stream *s) { + sym_BZ2_bzCompressEnd(s); } + +static inline void BZ2_bzDecompressEnd_wrapper(bz_stream *s) { + sym_BZ2_bzDecompressEnd(s); +} +#endif + +/* Opaque Compressor/Decompressor struct definition */ +struct Compressor { + Compression type; + bool encoding; + union { +#if HAVE_XZ + lzma_stream xz; +#endif +#if HAVE_LZ4 + struct { + LZ4F_compressionContext_t c_lz4; + void *lz4_header; /* stashed frame header from LZ4F_compressBegin */ + size_t lz4_header_size; + }; + LZ4F_decompressionContext_t d_lz4; +#endif +#if HAVE_ZSTD + ZSTD_CCtx *c_zstd; + ZSTD_DCtx *d_zstd; +#endif +#if HAVE_ZLIB + z_stream gzip; +#endif +#if HAVE_BZIP2 + bz_stream bzip2; #endif + }; +}; #define ALIGN_8(l) ALIGN_TO(l, sizeof(size_t)) +/* zlib windowBits value for gzip format: MAX_WBITS (15) + 16 to enable gzip header detection/generation */ +#define ZLIB_WBITS_GZIP (15 + 16) + static const char* const compression_table[_COMPRESSION_MAX] = { - [COMPRESSION_NONE] = "NONE", - [COMPRESSION_XZ] = "XZ", - [COMPRESSION_LZ4] = "LZ4", - [COMPRESSION_ZSTD] = "ZSTD", + [COMPRESSION_NONE] = "uncompressed", /* backwards compatibility with importd */ + [COMPRESSION_XZ] = "xz", + [COMPRESSION_LZ4] = "lz4", + [COMPRESSION_ZSTD] = "zstd", + [COMPRESSION_GZIP] = "gzip", + [COMPRESSION_BZIP2] = "bzip2", +}; + +static const char* const compression_uppercase_table[_COMPRESSION_MAX] = { + [COMPRESSION_NONE] = "NONE", /* backwards compatibility with SYSTEMD_JOURNAL_COMPRESS=NONE */ + [COMPRESSION_XZ] = "XZ", + [COMPRESSION_LZ4] = "LZ4", + [COMPRESSION_ZSTD] = "ZSTD", + [COMPRESSION_GZIP] = "GZIP", + [COMPRESSION_BZIP2] = "BZIP2", }; -static const char* const compression_lowercase_table[_COMPRESSION_MAX] = { - [COMPRESSION_NONE] = "none", - [COMPRESSION_XZ] = "xz", - [COMPRESSION_LZ4] = "lz4", - [COMPRESSION_ZSTD] = "zstd", +static const char* const compression_extension_table[_COMPRESSION_MAX] = { + [COMPRESSION_NONE] = "", + [COMPRESSION_XZ] = ".xz", + [COMPRESSION_LZ4] = ".lz4", + [COMPRESSION_ZSTD] = ".zst", + [COMPRESSION_GZIP] = ".gz", + [COMPRESSION_BZIP2] = ".bz2", }; DEFINE_STRING_TABLE_LOOKUP(compression, Compression); -DEFINE_STRING_TABLE_LOOKUP(compression_lowercase, Compression); +DEFINE_STRING_TABLE_LOOKUP(compression_uppercase, Compression); +DEFINE_STRING_TABLE_LOOKUP(compression_extension, Compression); + +Compression compression_from_string_harder(const char *s) { + Compression c; + + assert(s); + + c = compression_from_string(s); + if (c >= 0) + return c; + + return compression_uppercase_from_string(s); +} + +Compression compression_from_filename(const char *filename) { + Compression c; + const char *e; + + assert(filename); + + e = strrchr(filename, '.'); + if (!e) + return COMPRESSION_NONE; + + c = compression_extension_from_string(e); + if (c < 0) + return COMPRESSION_NONE; + + return c; +} bool compression_supported(Compression c) { static const unsigned supported = (1U << COMPRESSION_NONE) | (1U << COMPRESSION_XZ) * HAVE_XZ | (1U << COMPRESSION_LZ4) * HAVE_LZ4 | - (1U << COMPRESSION_ZSTD) * HAVE_ZSTD; + (1U << COMPRESSION_ZSTD) * HAVE_ZSTD | + (1U << COMPRESSION_GZIP) * HAVE_ZLIB | + (1U << COMPRESSION_BZIP2) * HAVE_BZIP2; assert(c >= 0); assert(c < _COMPRESSION_MAX); @@ -144,16 +266,35 @@ bool compression_supported(Compression c) { return BIT_SET(supported, c); } -int dlopen_lzma(void) { +Compression compression_detect_from_magic(const uint8_t data[static COMPRESSION_MAGIC_BYTES_MAX]) { + /* Magic signatures per RFC 1952 (gzip), tukaani.org/xz/xz-file-format.txt (xz), + * RFC 8878 (zstd), lz4/doc/lz4_Frame_format.md (lz4), and the bzip2 file format. + * Make sure to update COMPRESSION_MAGIC_BYTES_MAX if needed when adding a new magic. */ + if (memcmp(data, (const uint8_t[]) { 0x1f, 0x8b }, 2) == 0) + return COMPRESSION_GZIP; + if (memcmp(data, (const uint8_t[]) { 0xfd, '7', 'z', 'X', 'Z', 0x00 }, 6) == 0) + return COMPRESSION_XZ; + if (memcmp(data, (const uint8_t[]) { 0x28, 0xb5, 0x2f, 0xfd }, 4) == 0) + return COMPRESSION_ZSTD; + if (memcmp(data, (const uint8_t[]) { 0x04, 0x22, 0x4d, 0x18 }, 4) == 0) + return COMPRESSION_LZ4; + if (memcmp(data, (const uint8_t[]) { 'B', 'Z', 'h' }, 3) == 0) + return COMPRESSION_BZIP2; + + return _COMPRESSION_INVALID; +} + +int dlopen_xz(int log_level) { #if HAVE_XZ - ELF_NOTE_DLOPEN("lzma", + SD_ELF_NOTE_DLOPEN( + "lzma", "Support lzma compression in journal and coredump files", COMPRESSION_PRIORITY_XZ, "liblzma.so.5"); return dlopen_many_sym_or_warn( &lzma_dl, - "liblzma.so.5", LOG_DEBUG, + "liblzma.so.5", log_level, DLSYM_ARG(lzma_code), DLSYM_ARG(lzma_easy_encoder), DLSYM_ARG(lzma_end), @@ -161,12 +302,129 @@ int dlopen_lzma(void) { DLSYM_ARG(lzma_lzma_preset), DLSYM_ARG(lzma_stream_decoder)); #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "lzma support is not compiled in."); +#endif +} + +int dlopen_lz4(int log_level) { +#if HAVE_LZ4 + SD_ELF_NOTE_DLOPEN( + "lz4", + "Support lz4 compression in journal and coredump files", + COMPRESSION_PRIORITY_LZ4, + "liblz4.so.1"); + + return dlopen_many_sym_or_warn( + &lz4_dl, + "liblz4.so.1", log_level, + DLSYM_ARG(LZ4F_compressBegin), + DLSYM_ARG(LZ4F_compressBound), + DLSYM_ARG(LZ4F_compressEnd), + DLSYM_ARG(LZ4F_compressUpdate), + DLSYM_ARG(LZ4F_createCompressionContext), + DLSYM_ARG(LZ4F_createDecompressionContext), + DLSYM_ARG(LZ4F_decompress), + DLSYM_ARG(LZ4F_freeCompressionContext), + DLSYM_ARG(LZ4F_freeDecompressionContext), + DLSYM_ARG(LZ4F_isError), + DLSYM_ARG(LZ4_compress_default), + DLSYM_ARG(LZ4_compress_HC), + DLSYM_ARG(LZ4_decompress_safe), + DLSYM_ARG(LZ4_decompress_safe_partial), + DLSYM_ARG(LZ4_versionNumber)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "lz4 support is not compiled in."); +#endif +} + +int dlopen_zstd(int log_level) { +#if HAVE_ZSTD + SD_ELF_NOTE_DLOPEN( + "zstd", + "Support zstd compression in journal and coredump files", + COMPRESSION_PRIORITY_ZSTD, + "libzstd.so.1"); + + return dlopen_many_sym_or_warn( + &zstd_dl, + "libzstd.so.1", log_level, + DLSYM_ARG(ZSTD_getErrorCode), + DLSYM_ARG(ZSTD_compress), + DLSYM_ARG(ZSTD_getFrameContentSize), + DLSYM_ARG(ZSTD_decompressStream), + DLSYM_ARG(ZSTD_getErrorName), + DLSYM_ARG(ZSTD_DStreamOutSize), + DLSYM_ARG(ZSTD_CStreamInSize), + DLSYM_ARG(ZSTD_CStreamOutSize), + DLSYM_ARG(ZSTD_CCtx_setParameter), + DLSYM_ARG(ZSTD_compressStream2), + DLSYM_ARG(ZSTD_DStreamInSize), + DLSYM_ARG(ZSTD_freeCCtx), + DLSYM_ARG(ZSTD_freeDCtx), + DLSYM_ARG(ZSTD_isError), + DLSYM_ARG(ZSTD_createDCtx), + DLSYM_ARG(ZSTD_createCCtx)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "zstd support is not compiled in."); +#endif +} + +int dlopen_zlib(int log_level) { +#if HAVE_ZLIB + SD_ELF_NOTE_DLOPEN( + "zlib", + "Support gzip compression and decompression", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libz.so.1"); + + return dlopen_many_sym_or_warn( + &zlib_dl, + "libz.so.1", log_level, + DLSYM_ARG(deflateInit2_), + DLSYM_ARG(deflate), + DLSYM_ARG(deflateEnd), + DLSYM_ARG(inflateInit2_), + DLSYM_ARG(inflate), + DLSYM_ARG(inflateEnd)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "zlib support is not compiled in."); +#endif +} + +int dlopen_bzip2(int log_level) { +#if HAVE_BZIP2 + SD_ELF_NOTE_DLOPEN( + "bzip2", + "Support bzip2 compression and decompression", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libbz2.so.1"); + + return dlopen_many_sym_or_warn( + &bzip2_dl, + "libbz2.so.1", log_level, + DLSYM_ARG(BZ2_bzCompressInit), + DLSYM_ARG(BZ2_bzCompress), + DLSYM_ARG(BZ2_bzCompressEnd), + DLSYM_ARG(BZ2_bzDecompressInit), + DLSYM_ARG(BZ2_bzDecompress), + DLSYM_ARG(BZ2_bzDecompressEnd)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "bzip2 support is not compiled in."); #endif } -int compress_blob_xz(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { +static int compress_blob_xz( + const void *src, + uint64_t src_size, + void *dst, + size_t dst_alloc_size, + size_t *dst_size, + int level) { assert(src); assert(src_size > 0); @@ -187,7 +445,7 @@ int compress_blob_xz(const void *src, uint64_t src_size, size_t out_pos = 0; int r; - r = dlopen_lzma(); + r = dlopen_xz(LOG_DEBUG); if (r < 0) return r; @@ -215,38 +473,13 @@ int compress_blob_xz(const void *src, uint64_t src_size, #endif } -int dlopen_lz4(void) { -#if HAVE_LZ4 - ELF_NOTE_DLOPEN("lz4", - "Support lz4 compression in journal and coredump files", - COMPRESSION_PRIORITY_LZ4, - "liblz4.so.1"); - - return dlopen_many_sym_or_warn( - &lz4_dl, - "liblz4.so.1", LOG_DEBUG, - DLSYM_ARG(LZ4F_compressBegin), - DLSYM_ARG(LZ4F_compressBound), - DLSYM_ARG(LZ4F_compressEnd), - DLSYM_ARG(LZ4F_compressUpdate), - DLSYM_ARG(LZ4F_createCompressionContext), - DLSYM_ARG(LZ4F_createDecompressionContext), - DLSYM_ARG(LZ4F_decompress), - DLSYM_ARG(LZ4F_freeCompressionContext), - DLSYM_ARG(LZ4F_freeDecompressionContext), - DLSYM_ARG(LZ4F_isError), - DLSYM_ARG(LZ4_compress_default), - DLSYM_ARG(LZ4_compress_HC), - DLSYM_ARG(LZ4_decompress_safe), - DLSYM_ARG(LZ4_decompress_safe_partial), - DLSYM_ARG(LZ4_versionNumber)); -#else - return -EOPNOTSUPP; -#endif -} - -int compress_blob_lz4(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { +static int compress_blob_lz4( + const void *src, + uint64_t src_size, + void *dst, + size_t dst_alloc_size, + size_t *dst_size, + int level) { assert(src); assert(src_size > 0); @@ -257,7 +490,7 @@ int compress_blob_lz4(const void *src, uint64_t src_size, #if HAVE_LZ4 int r; - r = dlopen_lz4(); + r = dlopen_lz4(LOG_DEBUG); if (r < 0) return r; /* Returns < 0 if we couldn't compress the data or the @@ -266,6 +499,11 @@ int compress_blob_lz4(const void *src, uint64_t src_size, if (src_size < 9) return -ENOBUFS; + if (src_size > INT_MAX) + return -EFBIG; + if (dst_alloc_size > INT_MAX) + dst_alloc_size = INT_MAX; + if (level <= 0) r = sym_LZ4_compress_default(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8); else @@ -282,40 +520,13 @@ int compress_blob_lz4(const void *src, uint64_t src_size, #endif } -int dlopen_zstd(void) { -#if HAVE_ZSTD - ELF_NOTE_DLOPEN("zstd", - "Support zstd compression in journal and coredump files", - COMPRESSION_PRIORITY_ZSTD, - "libzstd.so.1"); - - return dlopen_many_sym_or_warn( - &zstd_dl, - "libzstd.so.1", LOG_DEBUG, - DLSYM_ARG(ZSTD_getErrorCode), - DLSYM_ARG(ZSTD_compress), - DLSYM_ARG(ZSTD_getFrameContentSize), - DLSYM_ARG(ZSTD_decompressStream), - DLSYM_ARG(ZSTD_getErrorName), - DLSYM_ARG(ZSTD_DStreamOutSize), - DLSYM_ARG(ZSTD_CStreamInSize), - DLSYM_ARG(ZSTD_CStreamOutSize), - DLSYM_ARG(ZSTD_CCtx_setParameter), - DLSYM_ARG(ZSTD_compressStream2), - DLSYM_ARG(ZSTD_DStreamInSize), - DLSYM_ARG(ZSTD_freeCCtx), - DLSYM_ARG(ZSTD_freeDCtx), - DLSYM_ARG(ZSTD_isError), - DLSYM_ARG(ZSTD_createDCtx), - DLSYM_ARG(ZSTD_createCCtx)); -#else - return -EOPNOTSUPP; -#endif -} - -int compress_blob_zstd( - const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { +static int compress_blob_zstd( + const void *src, + uint64_t src_size, + void *dst, + size_t dst_alloc_size, + size_t *dst_size, + int level) { assert(src); assert(src_size > 0); @@ -327,7 +538,7 @@ int compress_blob_zstd( size_t k; int r; - r = dlopen_zstd(); + r = dlopen_zstd(LOG_DEBUG); if (r < 0) return r; @@ -342,55 +553,171 @@ int compress_blob_zstd( #endif } -int decompress_blob_xz( - const void *src, - uint64_t src_size, - void **dst, - size_t* dst_size, - size_t dst_max) { +static int compress_blob_gzip(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { assert(src); assert(src_size > 0); assert(dst); + assert(dst_alloc_size > 0); assert(dst_size); -#if HAVE_XZ +#if HAVE_ZLIB int r; - r = dlopen_lzma(); + r = dlopen_zlib(LOG_DEBUG); if (r < 0) return r; - _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; - lzma_ret ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0); - if (ret != LZMA_OK) - return -ENOMEM; - - size_t space = MIN(src_size * 2, dst_max ?: SIZE_MAX); - if (!greedy_realloc(dst, space, 1)) + if (src_size > UINT_MAX) + return -EFBIG; + if (dst_alloc_size > UINT_MAX) + dst_alloc_size = UINT_MAX; + + _cleanup_(deflateEnd_wrapper) z_stream s = {}; + + r = sym_deflateInit2_(&s, level < 0 ? Z_DEFAULT_COMPRESSION : level, + /* method= */ Z_DEFLATED, + /* windowBits= */ ZLIB_WBITS_GZIP, + /* memLevel= */ 8, + /* strategy= */ Z_DEFAULT_STRATEGY, + ZLIB_VERSION, (int) sizeof(s)); + if (r != Z_OK) return -ENOMEM; - s.next_in = src; + s.next_in = (void*) src; s.avail_in = src_size; + s.next_out = dst; + s.avail_out = dst_alloc_size; - s.next_out = *dst; - s.avail_out = space; + r = sym_deflate(&s, Z_FINISH); + if (r != Z_STREAM_END) + return -ENOBUFS; - for (;;) { - size_t used; + *dst_size = dst_alloc_size - s.avail_out; + return 0; +#else + return -EPROTONOSUPPORT; +#endif +} - ret = sym_lzma_code(&s, LZMA_FINISH); - if (ret == LZMA_STREAM_END) - break; - if (ret != LZMA_OK) - return -ENOMEM; +static int compress_blob_bzip2( + const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { - if (dst_max > 0 && (space - s.avail_out) >= dst_max) - break; - if (dst_max > 0 && space == dst_max) - return -ENOBUFS; + assert(src); + assert(src_size > 0); + assert(dst); + assert(dst_alloc_size > 0); + assert(dst_size); + +#if HAVE_BZIP2 + int r; + + r = dlopen_bzip2(LOG_DEBUG); + if (r < 0) + return r; + + if (src_size > UINT_MAX) + return -EFBIG; + if (dst_alloc_size > UINT_MAX) + dst_alloc_size = UINT_MAX; + + _cleanup_(BZ2_bzCompressEnd_wrapper) bz_stream s = {}; + + r = sym_BZ2_bzCompressInit(&s, level < 0 ? 9 : level, /* verbosity= */ 0, /* workFactor= */ 0); + if (r != BZ_OK) + return -ENOMEM; + + s.next_in = (char*) src; + s.avail_in = src_size; + s.next_out = (char*) dst; + s.avail_out = dst_alloc_size; + + r = sym_BZ2_bzCompress(&s, BZ_FINISH); + + if (r != BZ_STREAM_END) + return -ENOBUFS; + + *dst_size = dst_alloc_size - s.avail_out; + return 0; +#else + return -EPROTONOSUPPORT; +#endif +} + +int compress_blob( + Compression compression, + const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { + + switch (compression) { + case COMPRESSION_XZ: + return compress_blob_xz(src, src_size, dst, dst_alloc_size, dst_size, level); + case COMPRESSION_LZ4: + return compress_blob_lz4(src, src_size, dst, dst_alloc_size, dst_size, level); + case COMPRESSION_ZSTD: + return compress_blob_zstd(src, src_size, dst, dst_alloc_size, dst_size, level); + case COMPRESSION_GZIP: + return compress_blob_gzip(src, src_size, dst, dst_alloc_size, dst_size, level); + case COMPRESSION_BZIP2: + return compress_blob_bzip2(src, src_size, dst, dst_alloc_size, dst_size, level); + default: + return -EOPNOTSUPP; + } +} + +static int decompress_blob_xz( + const void *src, + uint64_t src_size, + void **dst, + size_t *dst_size, + size_t dst_max) { + + assert(src); + assert(src_size > 0); + assert(dst); + assert(dst_size); + +#if HAVE_XZ + int r; + + r = dlopen_xz(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; + lzma_ret ret = sym_lzma_stream_decoder(&s, UINT64_MAX, /* flags= */ 0); + if (ret != LZMA_OK) + return -ENOMEM; + + size_t space = MIN(src_size * 2, dst_max ?: SIZE_MAX); + if (!greedy_realloc(dst, space, 1)) + return -ENOMEM; + + s.next_in = src; + s.avail_in = src_size; + + s.next_out = *dst; + s.avail_out = space; + + for (;;) { + size_t used; + + ret = sym_lzma_code(&s, LZMA_FINISH); + if (ret == LZMA_STREAM_END) + break; + if (ret != LZMA_OK) + return -ENOMEM; + + if (dst_max > 0 && (space - s.avail_out) >= dst_max) + break; + if (dst_max > 0 && space == dst_max) + return -ENOBUFS; used = space - s.avail_out; + /* Silence static analyzers, space is bounded by allocation size */ + assert(space <= SIZE_MAX / 2); space = MIN(2 * space, dst_max ?: SIZE_MAX); if (!greedy_realloc(dst, space, 1)) return -ENOMEM; @@ -406,11 +733,11 @@ int decompress_blob_xz( #endif } -int decompress_blob_lz4( +static int decompress_blob_lz4( const void *src, uint64_t src_size, void **dst, - size_t* dst_size, + size_t *dst_size, size_t dst_max) { assert(src); @@ -422,16 +749,21 @@ int decompress_blob_lz4( char* out; int r, size; /* LZ4 uses int for size */ - r = dlopen_lz4(); + r = dlopen_lz4(LOG_DEBUG); if (r < 0) return r; if (src_size <= 8) return -EBADMSG; + if (src_size - 8 > INT_MAX) + return -EFBIG; + size = unaligned_read_le64(src); if (size < 0 || (unsigned) size != unaligned_read_le64(src)) return -EFBIG; + if (dst_max > 0 && (size_t) size > dst_max) + return -ENOBUFS; out = greedy_realloc(dst, size, 1); if (!out) return -ENOMEM; @@ -447,7 +779,7 @@ int decompress_blob_lz4( #endif } -int decompress_blob_zstd( +static int decompress_blob_zstd( const void *src, uint64_t src_size, void **dst, @@ -463,7 +795,7 @@ int decompress_blob_zstd( uint64_t size; int r; - r = dlopen_zstd(); + r = dlopen_zstd(LOG_DEBUG); if (r < 0) return r; @@ -505,12 +837,146 @@ int decompress_blob_zstd( #endif } +static int decompress_blob_gzip( + const void *src, + uint64_t src_size, + void **dst, + size_t *dst_size, + size_t dst_max) { + + assert(src); + assert(src_size > 0); + assert(dst); + assert(dst_size); + +#if HAVE_ZLIB + int r; + + r = dlopen_zlib(LOG_DEBUG); + if (r < 0) + return r; + + if (src_size > UINT_MAX) + return -EFBIG; + + _cleanup_(inflateEnd_wrapper) z_stream s = {}; + + r = sym_inflateInit2_(&s, /* windowBits= */ ZLIB_WBITS_GZIP, ZLIB_VERSION, (int) sizeof(s)); + if (r != Z_OK) + return -ENOMEM; + + size_t space = MIN3(src_size * 2, dst_max ?: SIZE_MAX, (size_t) UINT_MAX); + if (!greedy_realloc(dst, space, 1)) + return -ENOMEM; + + s.next_in = (void*) src; + s.avail_in = src_size; + s.next_out = *dst; + s.avail_out = space; + + for (;;) { + size_t used; + + r = sym_inflate(&s, Z_NO_FLUSH); + if (r == Z_STREAM_END) + break; + if (!IN_SET(r, Z_OK, Z_BUF_ERROR)) + return -EBADMSG; + + if (dst_max > 0 && (space - s.avail_out) >= dst_max) + break; + if (dst_max > 0 && space == dst_max) + return -ENOBUFS; + + used = space - s.avail_out; + space = MIN3(2 * space, dst_max ?: SIZE_MAX, UINT_MAX); + if (!greedy_realloc(dst, space, 1)) + return -ENOMEM; + + s.avail_out = space - used; + s.next_out = *(uint8_t**)dst + used; + } + + *dst_size = space - s.avail_out; + return 0; +#else + return -EPROTONOSUPPORT; +#endif +} + +static int decompress_blob_bzip2( + const void *src, + uint64_t src_size, + void **dst, + size_t *dst_size, + size_t dst_max) { + + assert(src); + assert(src_size > 0); + assert(dst); + assert(dst_size); + +#if HAVE_BZIP2 + int r; + + r = dlopen_bzip2(LOG_DEBUG); + if (r < 0) + return r; + + if (src_size > UINT_MAX) + return -EFBIG; + + _cleanup_(BZ2_bzDecompressEnd_wrapper) bz_stream s = {}; + + r = sym_BZ2_bzDecompressInit(&s, /* verbosity= */ 0, /* small= */ 0); + if (r != BZ_OK) + return -ENOMEM; + + size_t space = MIN3(src_size * 2, dst_max ?: SIZE_MAX, (size_t) UINT_MAX); + if (!greedy_realloc(dst, space, 1)) + return -ENOMEM; + + s.next_in = (char*) src; + s.avail_in = src_size; + s.next_out = (char*) *dst; + s.avail_out = space; + + for (;;) { + size_t used; + + r = sym_BZ2_bzDecompress(&s); + if (r == BZ_STREAM_END) + break; + if (r != BZ_OK) + return -EBADMSG; + + if (dst_max > 0 && (space - s.avail_out) >= dst_max) + break; + if (dst_max > 0 && space == dst_max) + return -ENOBUFS; + + used = space - s.avail_out; + space = MIN3(2 * space, dst_max ?: SIZE_MAX, (size_t) UINT_MAX); + if (!greedy_realloc(dst, space, 1)) + return -ENOMEM; + + s.avail_out = space - used; + s.next_out = (char*) *dst + used; + } + + *dst_size = space - s.avail_out; + return 0; +#else + return -EPROTONOSUPPORT; +#endif +} + int decompress_blob( Compression compression, const void *src, uint64_t src_size, void **dst, - size_t* dst_size, + size_t *dst_size, size_t dst_max) { switch (compression) { @@ -526,12 +992,62 @@ int decompress_blob( return decompress_blob_zstd( src, src_size, dst, dst_size, dst_max); + case COMPRESSION_GZIP: + return decompress_blob_gzip( + src, src_size, + dst, dst_size, dst_max); + case COMPRESSION_BZIP2: + return decompress_blob_bzip2( + src, src_size, + dst, dst_size, dst_max); default: return -EPROTONOSUPPORT; } } -int decompress_startswith_xz( +int decompress_zlib_raw( + const void *src, + uint64_t src_size, + void *dst, + size_t dst_size, + int wbits) { + +#if HAVE_ZLIB + int r; + + r = dlopen_zlib(LOG_DEBUG); + if (r < 0) + return r; + + if (src_size > UINT_MAX) + return -EFBIG; + if (dst_size > UINT_MAX) + return -EFBIG; + + _cleanup_(inflateEnd_wrapper) z_stream s = { + .next_in = (void*) src, + .avail_in = src_size, + .next_out = dst, + .avail_out = dst_size, + }; + + r = sym_inflateInit2_(&s, /* windowBits= */ wbits, ZLIB_VERSION, (int) sizeof(s)); + if (r != Z_OK) + return -EIO; + + r = sym_inflate(&s, Z_FINISH); + size_t produced = (uint8_t*) s.next_out - (uint8_t*) dst; + + if (r != Z_STREAM_END || produced != dst_size) + return -EBADMSG; + + return 0; +#else + return -EPROTONOSUPPORT; +#endif +} + +static int decompress_startswith_xz( const void *src, uint64_t src_size, void **buffer, @@ -550,12 +1066,12 @@ int decompress_startswith_xz( #if HAVE_XZ int r; - r = dlopen_lzma(); + r = dlopen_xz(LOG_DEBUG); if (r < 0) return r; _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; - lzma_ret ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0); + lzma_ret ret = sym_lzma_stream_decoder(&s, UINT64_MAX, /* flags= */ 0); if (ret != LZMA_OK) return -EBADMSG; @@ -597,7 +1113,7 @@ int decompress_startswith_xz( #endif } -int decompress_startswith_lz4( +static int decompress_startswith_lz4( const void *src, uint64_t src_size, void **buffer, @@ -617,13 +1133,16 @@ int decompress_startswith_lz4( size_t allocated; int r; - r = dlopen_lz4(); + r = dlopen_lz4(LOG_DEBUG); if (r < 0) return r; if (src_size <= 8) return -EBADMSG; + if (src_size - 8 > INT_MAX) + return -EFBIG; + if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1))) return -ENOMEM; allocated = MALLOC_SIZEOF_SAFE(*buffer); @@ -670,7 +1189,7 @@ int decompress_startswith_lz4( #endif } -int decompress_startswith_zstd( +static int decompress_startswith_zstd( const void *src, uint64_t src_size, void **buffer, @@ -686,7 +1205,7 @@ int decompress_startswith_zstd( #if HAVE_ZSTD int r; - r = dlopen_zstd(); + r = dlopen_zstd(LOG_DEBUG); if (r < 0) return r; @@ -715,11 +1234,10 @@ int decompress_startswith_zstd( size_t k; k = sym_ZSTD_decompressStream(dctx, &output, &input); - if (sym_ZSTD_isError(k)) { - log_debug("ZSTD decoder failed: %s", sym_ZSTD_getErrorName(k)); - return zstd_ret_to_errno(k); - } - assert(output.pos >= prefix_len + 1); + if (sym_ZSTD_isError(k)) + return log_debug_errno(zstd_ret_to_errno(k), "ZSTD decoder failed: %s", sym_ZSTD_getErrorName(k)); + if (output.pos < prefix_len + 1) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "ZSTD decoded less data than indicated, probably corrupted stream."); return memcmp(*buffer, prefix, prefix_len) == 0 && ((const uint8_t*) *buffer)[prefix_len] == extra; @@ -728,8 +1246,7 @@ int decompress_startswith_zstd( #endif } -int decompress_startswith( - Compression compression, +static int decompress_startswith_gzip( const void *src, uint64_t src_size, void **buffer, @@ -737,594 +1254,1303 @@ int decompress_startswith( size_t prefix_len, uint8_t extra) { - switch (compression) { - - case COMPRESSION_XZ: - return decompress_startswith_xz( - src, src_size, - buffer, - prefix, prefix_len, - extra); - - case COMPRESSION_LZ4: - return decompress_startswith_lz4( - src, src_size, - buffer, - prefix, prefix_len, - extra); - case COMPRESSION_ZSTD: - return decompress_startswith_zstd( - src, src_size, - buffer, - prefix, prefix_len, - extra); - default: - return -EBADMSG; - } -} - -int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { - assert(fdf >= 0); - assert(fdt >= 0); + assert(src); + assert(src_size > 0); + assert(buffer); + assert(prefix); -#if HAVE_XZ +#if HAVE_ZLIB int r; - r = dlopen_lzma(); + r = dlopen_zlib(LOG_DEBUG); if (r < 0) return r; - _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; - lzma_ret ret = sym_lzma_easy_encoder(&s, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); - if (ret != LZMA_OK) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to initialize XZ encoder: code %u", - ret); + if (src_size > UINT_MAX) + return -EFBIG; - uint8_t buf[BUFSIZ], out[BUFSIZ]; - lzma_action action = LZMA_RUN; - for (;;) { - if (s.avail_in == 0 && action == LZMA_RUN) { - size_t m = sizeof(buf); - ssize_t n; - - if (max_bytes != UINT64_MAX && (uint64_t) m > max_bytes) - m = (size_t) max_bytes; - - n = read(fdf, buf, m); - if (n < 0) - return -errno; - if (n == 0) - action = LZMA_FINISH; - else { - s.next_in = buf; - s.avail_in = n; - - if (max_bytes != UINT64_MAX) { - assert(max_bytes >= (uint64_t) n); - max_bytes -= n; - } - } - } + _cleanup_(inflateEnd_wrapper) z_stream s = {}; - if (s.avail_out == 0) { - s.next_out = out; - s.avail_out = sizeof(out); - } + r = sym_inflateInit2_(&s, /* windowBits= */ ZLIB_WBITS_GZIP, ZLIB_VERSION, (int) sizeof(s)); + if (r != Z_OK) + return -EBADMSG; - ret = sym_lzma_code(&s, action); - if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END)) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "Compression failed: code %u", - ret); + if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1))) + return -ENOMEM; - if (s.avail_out == 0 || ret == LZMA_STREAM_END) { - ssize_t n, k; + size_t allocated = MALLOC_SIZEOF_SAFE(*buffer); - n = sizeof(out) - s.avail_out; + s.next_in = (void*) src; + s.avail_in = src_size; - k = loop_write(fdt, out, n); - if (k < 0) - return k; + s.next_out = *buffer; + s.avail_out = MIN(allocated, (size_t) UINT_MAX); - if (ret == LZMA_STREAM_END) { - if (ret_uncompressed_size) - *ret_uncompressed_size = s.total_in; + for (;;) { + r = sym_inflate(&s, Z_FINISH); - if (s.total_in == 0) - log_debug("XZ compression finished (no input data)"); - else - log_debug("XZ compression finished (%"PRIu64" -> %"PRIu64" bytes, %.1f%%)", - s.total_in, s.total_out, - (double) s.total_out / s.total_in * 100); + if (!IN_SET(r, Z_OK, Z_STREAM_END, Z_BUF_ERROR)) + return -EBADMSG; - return 0; - } - } + if (allocated - s.avail_out >= prefix_len + 1) + return memcmp(*buffer, prefix, prefix_len) == 0 && + ((const uint8_t*) *buffer)[prefix_len] == extra; + + if (r == Z_STREAM_END) + return 0; + + size_t used = allocated - s.avail_out; + + if (!(greedy_realloc(buffer, allocated * 2, 1))) + return -ENOMEM; + + allocated = MALLOC_SIZEOF_SAFE(*buffer); + s.avail_out = MIN(allocated - used, (size_t) UINT_MAX); + s.next_out = *(uint8_t**)buffer + used; } #else return -EPROTONOSUPPORT; #endif } -#define LZ4_BUFSIZE (512*1024u) +static int decompress_startswith_bzip2( + const void *src, + uint64_t src_size, + void **buffer, + const void *prefix, + size_t prefix_len, + uint8_t extra) { -int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { + assert(src); + assert(src_size > 0); + assert(buffer); + assert(prefix); -#if HAVE_LZ4 - LZ4F_errorCode_t c; - _cleanup_(LZ4F_freeCompressionContextp) LZ4F_compressionContext_t ctx = NULL; - _cleanup_free_ void *in_buff = NULL; - _cleanup_free_ char *out_buff = NULL; - size_t out_allocsize, n, offset = 0, frame_size; - uint64_t total_in = 0, total_out; +#if HAVE_BZIP2 int r; - static const LZ4F_preferences_t preferences = { - .frameInfo.blockSizeID = 5, - }; - r = dlopen_lz4(); + r = dlopen_bzip2(LOG_DEBUG); if (r < 0) return r; - c = sym_LZ4F_createCompressionContext(&ctx, LZ4F_VERSION); - if (sym_LZ4F_isError(c)) - return -ENOMEM; - - frame_size = sym_LZ4F_compressBound(LZ4_BUFSIZE, &preferences); - out_allocsize = frame_size + 64*1024; /* add some space for header and trailer */ - out_buff = malloc(out_allocsize); - if (!out_buff) - return -ENOMEM; + if (src_size > UINT_MAX) + return -EFBIG; - in_buff = malloc(LZ4_BUFSIZE); - if (!in_buff) - return -ENOMEM; + _cleanup_(BZ2_bzDecompressEnd_wrapper) bz_stream s = {}; - n = offset = total_out = sym_LZ4F_compressBegin(ctx, out_buff, out_allocsize, &preferences); - if (sym_LZ4F_isError(n)) - return -EINVAL; + r = sym_BZ2_bzDecompressInit(&s, /* verbosity= */ 0, /* small= */ 0); + if (r != BZ_OK) + return -EBADMSG; - log_debug("Buffer size is %zu bytes, header size %zu bytes.", out_allocsize, n); + if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1))) + return -ENOMEM; - for (;;) { - ssize_t k; + size_t allocated = MALLOC_SIZEOF_SAFE(*buffer); - k = loop_read(fdf, in_buff, LZ4_BUFSIZE, true); - if (k < 0) - return k; - if (k == 0) - break; - n = sym_LZ4F_compressUpdate(ctx, out_buff + offset, out_allocsize - offset, - in_buff, k, NULL); - if (sym_LZ4F_isError(n)) - return -ENOTRECOVERABLE; + s.next_in = (char*) src; + s.avail_in = src_size; - total_in += k; - offset += n; - total_out += n; + s.next_out = *buffer; + s.avail_out = MIN(allocated, (size_t) UINT_MAX); - if (max_bytes != UINT64_MAX && total_out > (size_t) max_bytes) - return log_debug_errno(SYNTHETIC_ERRNO(EFBIG), - "Compressed stream longer than %" PRIu64 " bytes", max_bytes); + for (;;) { + r = sym_BZ2_bzDecompress(&s); - if (out_allocsize - offset < frame_size + 4) { - k = loop_write(fdt, out_buff, offset); - if (k < 0) - return k; - offset = 0; - } - } + if (!IN_SET(r, BZ_OK, BZ_STREAM_END)) + return -EBADMSG; - n = sym_LZ4F_compressEnd(ctx, out_buff + offset, out_allocsize - offset, NULL); - if (sym_LZ4F_isError(n)) - return -ENOTRECOVERABLE; + if (allocated - s.avail_out >= prefix_len + 1) + return memcmp(*buffer, prefix, prefix_len) == 0 && + ((const uint8_t*) *buffer)[prefix_len] == extra; - offset += n; - total_out += n; - r = loop_write(fdt, out_buff, offset); - if (r < 0) - return r; + if (r == BZ_STREAM_END) + return 0; - if (ret_uncompressed_size) - *ret_uncompressed_size = total_in; + size_t used = allocated - s.avail_out; - if (total_in == 0) - log_debug("LZ4 compression finished (no input data)"); - else - log_debug("LZ4 compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", - total_in, total_out, - (double) total_out / total_in * 100); + if (!(greedy_realloc(buffer, allocated * 2, 1))) + return -ENOMEM; - return 0; + allocated = MALLOC_SIZEOF_SAFE(*buffer); + s.avail_out = MIN(allocated - used, (size_t) UINT_MAX); + s.next_out = (char*) *buffer + used; + } #else return -EPROTONOSUPPORT; #endif } -int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { - assert(fdf >= 0); - assert(fdt >= 0); +int decompress_startswith( + Compression compression, + const void *src, uint64_t src_size, + void **buffer, + const void *prefix, size_t prefix_len, + uint8_t extra) { -#if HAVE_XZ + switch (compression) { + case COMPRESSION_XZ: + return decompress_startswith_xz(src, src_size, buffer, prefix, prefix_len, extra); + case COMPRESSION_LZ4: + return decompress_startswith_lz4(src, src_size, buffer, prefix, prefix_len, extra); + case COMPRESSION_ZSTD: + return decompress_startswith_zstd(src, src_size, buffer, prefix, prefix_len, extra); + case COMPRESSION_GZIP: + return decompress_startswith_gzip(src, src_size, buffer, prefix, prefix_len, extra); + case COMPRESSION_BZIP2: + return decompress_startswith_bzip2(src, src_size, buffer, prefix, prefix_len, extra); + default: + return -EOPNOTSUPP; + } +} + +int compress_stream( + Compression type, + int fdf, int fdt, + uint64_t max_bytes, + uint64_t *ret_uncompressed_size) { + + _cleanup_(compressor_freep) Compressor *c = NULL; + _cleanup_free_ void *buf = NULL; + _cleanup_free_ uint8_t *input = NULL; + size_t buf_size = 0, buf_alloc = 0; + uint64_t total_in = 0, total_out = 0; int r; - r = dlopen_lzma(); + assert(fdf >= 0); + assert(fdt >= 0); + + r = compressor_new(&c, type); if (r < 0) return r; - _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; - lzma_ret ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0); - if (ret != LZMA_OK) - return log_debug_errno(SYNTHETIC_ERRNO(ENOMEM), - "Failed to initialize XZ decoder: code %u", - ret); + input = new(uint8_t, COMPRESS_PIPE_BUFFER_SIZE); + if (!input) + return -ENOMEM; - uint8_t buf[BUFSIZ], out[BUFSIZ]; - lzma_action action = LZMA_RUN; for (;;) { - if (s.avail_in == 0 && action == LZMA_RUN) { - ssize_t n; - - n = read(fdf, buf, sizeof(buf)); - if (n < 0) - return -errno; - if (n == 0) - action = LZMA_FINISH; - else { - s.next_in = buf; - s.avail_in = n; + size_t m = COMPRESS_PIPE_BUFFER_SIZE; + ssize_t n; + + if (max_bytes != UINT64_MAX && (uint64_t) m > max_bytes) + m = (size_t) max_bytes; + + n = read(fdf, input, m); + if (n < 0) + return -errno; + + if (n == 0) { + r = compressor_finish(c, &buf, &buf_size, &buf_alloc); + if (r < 0) + return r; + + if (buf_size > 0) { + r = loop_write(fdt, buf, buf_size); + if (r < 0) + return r; + total_out += buf_size; } + break; } - if (s.avail_out == 0) { - s.next_out = out; - s.avail_out = sizeof(out); + total_in += n; + if (max_bytes != UINT64_MAX) { + assert(max_bytes >= (uint64_t) n); + max_bytes -= n; } - ret = sym_lzma_code(&s, action); - if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END)) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), - "Decompression failed: code %u", - ret); - - if (s.avail_out == 0 || ret == LZMA_STREAM_END) { - ssize_t n, k; - - n = sizeof(out) - s.avail_out; - - if (max_bytes != UINT64_MAX) { - if (max_bytes < (uint64_t) n) - return -EFBIG; + r = compressor_start(c, input, n, &buf, &buf_size, &buf_alloc); + if (r < 0) + return r; - max_bytes -= n; - } + if (buf_size > 0) { + r = loop_write(fdt, buf, buf_size); + if (r < 0) + return r; + total_out += buf_size; + } + } - k = loop_write(fdt, out, n); - if (k < 0) - return k; + if (ret_uncompressed_size) + *ret_uncompressed_size = total_in; - if (ret == LZMA_STREAM_END) { - if (s.total_in == 0) - log_debug("XZ decompression finished (no input data)"); - else - log_debug("XZ decompression finished (%"PRIu64" -> %"PRIu64" bytes, %.1f%%)", - s.total_in, s.total_out, - (double) s.total_out / s.total_in * 100); + if (total_in == 0) + log_debug("%s compression finished (no input data)", compression_to_string(type)); + else + log_debug("%s compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", + compression_to_string(type), total_in, total_out, (double) total_out / total_in * 100); - return 0; - } - } - } -#else - return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), - "Cannot decompress file. Compiled without XZ support."); -#endif + return 0; } -int decompress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) { -#if HAVE_LZ4 - size_t c; - _cleanup_(LZ4F_freeDecompressionContextp) LZ4F_decompressionContext_t ctx = NULL; - _cleanup_free_ char *buf = NULL; - char *src; +/* Determine whether sparse writes should be used for this fd. Sparse writes are only safe on + * regular files without O_APPEND (O_APPEND ignores lseek position, which would collapse holes). */ +static int should_sparse(int fd) { struct stat st; - int r; - size_t total_in = 0, total_out = 0; - r = dlopen_lz4(); - if (r < 0) - return r; + assert(fd >= 0); - c = sym_LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION); - if (sym_LZ4F_isError(c)) - return -ENOMEM; + if (fstat(fd, &st) < 0) + return -errno; - if (fstat(fdf, &st) < 0) - return log_debug_errno(errno, "fstat() failed: %m"); + int flags = fcntl(fd, F_GETFL); + if (flags < 0) + return -errno; - if (file_offset_beyond_memory_size(st.st_size)) - return -EFBIG; + return S_ISREG(st.st_mode) && !FLAGS_SET(flags, O_APPEND); +} - buf = malloc(LZ4_BUFSIZE); - if (!buf) - return -ENOMEM; +/* After sparse decompression, set the file size to the current position to account for + * trailing holes that sparse_write() created via lseek but never extended the file size for. */ +static int finalize_sparse(int fd) { + off_t pos; + + assert(fd >= 0); + + pos = lseek(fd, 0, SEEK_CUR); + if (pos < 0) + return -errno; - src = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fdf, 0); - if (src == MAP_FAILED) + if (ftruncate(fd, pos) < 0) return -errno; - while (total_in < (size_t) st.st_size) { - size_t produced = LZ4_BUFSIZE; - size_t used = st.st_size - total_in; + return 0; +} - c = sym_LZ4F_decompress(ctx, buf, &produced, src + total_in, &used, NULL); - if (sym_LZ4F_isError(c)) { - r = -EBADMSG; - goto cleanup; - } +/* Common helper for decompress_stream_*() wrappers */ - total_in += used; - total_out += produced; +struct decompress_stream_userdata { + int fd; + uint64_t max_bytes; + uint64_t total_out; + bool sparse; +}; - if (max_bytes != UINT64_MAX && total_out > (size_t) max_bytes) { - log_debug("Decompressed stream longer than %"PRIu64" bytes", max_bytes); - r = -EFBIG; - goto cleanup; - } +static int decompress_stream_write_callback(const void *data, size_t size, void *userdata) { + struct decompress_stream_userdata *u = ASSERT_PTR(userdata); - r = loop_write(fdt, buf, produced); - if (r < 0) - goto cleanup; + if (u->max_bytes != UINT64_MAX) { + if (u->max_bytes < size) + return -EFBIG; + u->max_bytes -= size; } - if (total_in == 0) - log_debug("LZ4 decompression finished (no input data)"); - else - log_debug("LZ4 decompression finished (%zu -> %zu bytes, %.1f%%)", - total_in, total_out, - (double) total_out / total_in * 100); - r = 0; - cleanup: - munmap(src, st.st_size); - return r; -#else - return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), - "Cannot decompress file. Compiled without LZ4 support."); -#endif -} + u->total_out += size; -int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { - assert(fdf >= 0); - assert(fdt >= 0); + if (u->sparse) { + /* Note: sparse_write() does not retry on EINTR and converts short writes to -EIO. + * This is fine here since sparse mode is only used on regular files, where short + * writes and EINTR are not expected in practice. */ + ssize_t k = sparse_write(u->fd, data, size, 64); + if (k < 0) + return (int) k; + return 0; + } -#if HAVE_ZSTD - _cleanup_(ZSTD_freeCCtxp) ZSTD_CCtx *cctx = NULL; - _cleanup_free_ void *in_buff = NULL, *out_buff = NULL; - size_t in_allocsize, out_allocsize; - size_t z; - uint64_t left = max_bytes, in_bytes = 0; + return loop_write(u->fd, data, size); +} + +static int decompressor_new(Decompressor **ret, Compression type) { +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 int r; +#endif - r = dlopen_zstd(); - if (r < 0) - return r; + assert(ret); - /* Create the context and buffers */ - in_allocsize = sym_ZSTD_CStreamInSize(); - out_allocsize = sym_ZSTD_CStreamOutSize(); - in_buff = malloc(in_allocsize); - out_buff = malloc(out_allocsize); - cctx = sym_ZSTD_createCCtx(); - if (!cctx || !out_buff || !in_buff) + _cleanup_(compressor_freep) Decompressor *c = new0(Decompressor, 1); + if (!c) return -ENOMEM; - z = sym_ZSTD_CCtx_setParameter(cctx, ZSTD_c_checksumFlag, 1); - if (sym_ZSTD_isError(z)) - log_debug("Failed to enable ZSTD checksum, ignoring: %s", sym_ZSTD_getErrorName(z)); + c->type = _COMPRESSION_INVALID; - /* This loop read from the input file, compresses that entire chunk, - * and writes all output produced to the output file. - */ - for (;;) { - bool is_last_chunk; - ZSTD_inBuffer input = { - .src = in_buff, - .size = 0, - .pos = 0 - }; - ssize_t red; + switch (type) { - red = loop_read(fdf, in_buff, in_allocsize, true); - if (red < 0) - return red; - is_last_chunk = red == 0; +#if HAVE_XZ + case COMPRESSION_XZ: + r = dlopen_xz(LOG_DEBUG); + if (r < 0) + return r; - in_bytes += (size_t) red; - input.size = (size_t) red; + if (sym_lzma_stream_decoder(&c->xz, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK | LZMA_CONCATENATED) != LZMA_OK) + return -EIO; + break; +#endif - for (bool finished = false; !finished;) { - ZSTD_outBuffer output = { - .dst = out_buff, - .size = out_allocsize, - .pos = 0 - }; - size_t remaining; - ssize_t wrote; - - /* Compress into the output buffer and write all of the - * output to the file so we can reuse the buffer next - * iteration. - */ - remaining = sym_ZSTD_compressStream2( - cctx, &output, &input, - is_last_chunk ? ZSTD_e_end : ZSTD_e_continue); - - if (sym_ZSTD_isError(remaining)) { - log_debug("ZSTD encoder failed: %s", sym_ZSTD_getErrorName(remaining)); - return zstd_ret_to_errno(remaining); - } +#if HAVE_LZ4 + case COMPRESSION_LZ4: { + r = dlopen_lz4(LOG_DEBUG); + if (r < 0) + return r; - if (left < output.pos) - return -EFBIG; + size_t rc = sym_LZ4F_createDecompressionContext(&c->d_lz4, LZ4F_VERSION); + if (sym_LZ4F_isError(rc)) + return -ENOMEM; - wrote = loop_write_full(fdt, output.dst, output.pos, USEC_INFINITY); - if (wrote < 0) - return wrote; + break; + } +#endif - left -= output.pos; +#if HAVE_ZSTD + case COMPRESSION_ZSTD: + r = dlopen_zstd(LOG_DEBUG); + if (r < 0) + return r; - /* If we're on the last chunk we're finished when zstd - * returns 0, which means its consumed all the input AND - * finished the frame. Otherwise, we're finished when - * we've consumed all the input. - */ - finished = is_last_chunk ? (remaining == 0) : (input.pos == input.size); - } + c->d_zstd = sym_ZSTD_createDCtx(); + if (!c->d_zstd) + return -ENOMEM; + break; +#endif - /* zstd only returns 0 when the input is completely consumed */ - assert(input.pos == input.size); - if (is_last_chunk) - break; - } +#if HAVE_ZLIB + case COMPRESSION_GZIP: + r = dlopen_zlib(LOG_DEBUG); + if (r < 0) + return r; - if (ret_uncompressed_size) - *ret_uncompressed_size = in_bytes; + r = sym_inflateInit2_(&c->gzip, /* windowBits= */ ZLIB_WBITS_GZIP, ZLIB_VERSION, (int) sizeof(c->gzip)); + if (r != Z_OK) + return -EIO; + break; +#endif - if (in_bytes == 0) - log_debug("ZSTD compression finished (no input data)"); - else - log_debug("ZSTD compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", - in_bytes, max_bytes - left, (double) (max_bytes - left) / in_bytes * 100); +#if HAVE_BZIP2 + case COMPRESSION_BZIP2: + r = dlopen_bzip2(LOG_DEBUG); + if (r < 0) + return r; - return 0; -#else - return -EPROTONOSUPPORT; + r = sym_BZ2_bzDecompressInit(&c->bzip2, /* verbosity= */ 0, /* small= */ 0); + if (r != BZ_OK) + return -EIO; + break; #endif + + default: + return -EOPNOTSUPP; + } + + c->type = type; + c->encoding = false; + *ret = TAKE_PTR(c); + return 0; } -int decompress_stream_zstd(int fdf, int fdt, uint64_t max_bytes) { - assert(fdf >= 0); - assert(fdt >= 0); +int decompress_stream( + Compression type, + int fdf, int fdt, + uint64_t max_bytes) { -#if HAVE_ZSTD - _cleanup_(ZSTD_freeDCtxp) ZSTD_DCtx *dctx = NULL; - _cleanup_free_ void *in_buff = NULL, *out_buff = NULL; - size_t in_allocsize, out_allocsize; - size_t last_result = 0; - uint64_t left = max_bytes, in_bytes = 0; + _cleanup_(compressor_freep) Decompressor *c = NULL; + _cleanup_free_ uint8_t *buf = NULL; + uint64_t total_in = 0; int r; - r = dlopen_zstd(); + assert(fdf >= 0); + assert(fdt >= 0); + + r = decompressor_new(&c, type); if (r < 0) return r; - /* Create the context and buffers */ - in_allocsize = sym_ZSTD_DStreamInSize(); - out_allocsize = sym_ZSTD_DStreamOutSize(); - in_buff = malloc(in_allocsize); - out_buff = malloc(out_allocsize); - dctx = sym_ZSTD_createDCtx(); - if (!dctx || !out_buff || !in_buff) + + struct decompress_stream_userdata userdata = { + .fd = fdt, + .max_bytes = max_bytes, + .sparse = should_sparse(fdt) > 0, + }; + + buf = new(uint8_t, COMPRESS_PIPE_BUFFER_SIZE); + if (!buf) return -ENOMEM; - /* This loop assumes that the input file is one or more concatenated - * zstd streams. This example won't work if there is trailing non-zstd - * data at the end, but streaming decompression in general handles this - * case. ZSTD_decompressStream() returns 0 exactly when the frame is - * completed, and doesn't consume input after the frame. - */ for (;;) { - bool has_error = false; - ZSTD_inBuffer input = { - .src = in_buff, - .size = 0, - .pos = 0 - }; - ssize_t red; + ssize_t n; - red = loop_read(fdf, in_buff, in_allocsize, true); - if (red < 0) - return red; - if (red == 0) + n = read(fdf, buf, COMPRESS_PIPE_BUFFER_SIZE); + if (n < 0) + return -errno; + if (n == 0) break; - in_bytes += (size_t) red; - input.size = (size_t) red; - input.pos = 0; + total_in += n; - /* Given a valid frame, zstd won't consume the last byte of the - * frame until it has flushed all of the decompressed data of - * the frame. So input.pos < input.size means frame is not done - * or there is still output available. - */ - while (input.pos < input.size) { - ZSTD_outBuffer output = { - .dst = out_buff, - .size = out_allocsize, - .pos = 0 - }; - ssize_t wrote; - /* The return code is zero if the frame is complete, but - * there may be multiple frames concatenated together. - * Zstd will automatically reset the context when a - * frame is complete. Still, calling ZSTD_DCtx_reset() - * can be useful to reset the context to a clean state, - * for instance if the last decompression call returned - * an error. - */ - last_result = sym_ZSTD_decompressStream(dctx, &output, &input); - if (sym_ZSTD_isError(last_result)) { - has_error = true; - break; - } + r = decompressor_push(c, buf, n, decompress_stream_write_callback, &userdata); + if (r < 0) + return r; + } + + if (total_in == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%s decompression failed: no data read", + compression_to_string(type)); + + if (userdata.sparse) { + r = finalize_sparse(fdt); + if (r < 0) + return r; + } + + log_debug("%s decompression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", + compression_to_string(type), total_in, userdata.total_out, + (double) userdata.total_out / total_in * 100); + + return 0; +} + +int decompress_stream_by_filename(const char *filename, int fdf, int fdt, uint64_t max_bytes) { + Compression c = compression_from_filename(filename); + if (c == COMPRESSION_NONE) + return -EPROTONOSUPPORT; + + return decompress_stream(c, fdf, fdt, max_bytes); +} + +/* Push-based streaming compression/decompression context API */ - if (left < output.pos) - return -EFBIG; +Compressor* compressor_free(Compressor *c) { + if (!c) + return NULL; - wrote = loop_write_full(fdt, output.dst, output.pos, USEC_INFINITY); - if (wrote < 0) - return wrote; + switch (c->type) { + +#if HAVE_XZ + case COMPRESSION_XZ: + sym_lzma_end(&c->xz); + break; +#endif - left -= output.pos; +#if HAVE_LZ4 + case COMPRESSION_LZ4: + if (c->encoding) { + sym_LZ4F_freeCompressionContext(c->c_lz4); + c->c_lz4 = NULL; + c->lz4_header = mfree(c->lz4_header); + } else { + sym_LZ4F_freeDecompressionContext(c->d_lz4); + c->d_lz4 = NULL; } - if (has_error) - break; - } + break; +#endif + +#if HAVE_ZSTD + case COMPRESSION_ZSTD: + if (c->encoding) { + sym_ZSTD_freeCCtx(c->c_zstd); + c->c_zstd = NULL; + } else { + sym_ZSTD_freeDCtx(c->d_zstd); + c->d_zstd = NULL; + } + break; +#endif + +#if HAVE_ZLIB + case COMPRESSION_GZIP: + if (c->encoding) + sym_deflateEnd(&c->gzip); + else + sym_inflateEnd(&c->gzip); + break; +#endif - if (in_bytes == 0) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "ZSTD decoder failed: no data read"); +#if HAVE_BZIP2 + case COMPRESSION_BZIP2: + if (c->encoding) + sym_BZ2_bzCompressEnd(&c->bzip2); + else + sym_BZ2_bzDecompressEnd(&c->bzip2); + break; +#endif - if (last_result != 0) { - /* The last return value from ZSTD_decompressStream did not end - * on a frame, but we reached the end of the file! We assume - * this is an error, and the input was truncated. - */ - log_debug("ZSTD decoder failed: %s", sym_ZSTD_getErrorName(last_result)); - return zstd_ret_to_errno(last_result); + default: + break; } - if (in_bytes == 0) - log_debug("ZSTD decompression finished (no input data)"); - else - log_debug("ZSTD decompression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", - in_bytes, - max_bytes - left, - (double) (max_bytes - left) / in_bytes * 100); - return 0; -#else - return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), - "Cannot decompress file. Compiled without ZSTD support."); -#endif + return mfree(c); +} + +Compression compressor_type(const Compressor *c) { + return c ? c->type : _COMPRESSION_INVALID; } -int decompress_stream(const char *filename, int fdf, int fdt, uint64_t max_bytes) { +int decompressor_detect(Decompressor **ret, const void *data, size_t size) { +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 + int r; +#endif - if (endswith(filename, ".lz4")) - return decompress_stream_lz4(fdf, fdt, max_bytes); - if (endswith(filename, ".xz")) - return decompress_stream_xz(fdf, fdt, max_bytes); - if (endswith(filename, ".zst")) - return decompress_stream_zstd(fdf, fdt, max_bytes); + assert(ret); - return -EPROTONOSUPPORT; + if (*ret) + return 1; + + if (size < COMPRESSION_MAGIC_BYTES_MAX) + return 0; + + assert(data); + + Compression type = compression_detect_from_magic(data); + + _cleanup_(compressor_freep) Decompressor *c = new0(Decompressor, 1); + if (!c) + return -ENOMEM; + + switch (type) { + +#if HAVE_XZ + case COMPRESSION_XZ: { + r = dlopen_xz(LOG_DEBUG); + if (r < 0) + return r; + + lzma_ret xzr = sym_lzma_stream_decoder(&c->xz, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK | LZMA_CONCATENATED); + if (xzr != LZMA_OK) + return -EIO; + + break; + } +#endif + +#if HAVE_LZ4 + case COMPRESSION_LZ4: { + r = dlopen_lz4(LOG_DEBUG); + if (r < 0) + return r; + + size_t rc = sym_LZ4F_createDecompressionContext(&c->d_lz4, LZ4F_VERSION); + if (sym_LZ4F_isError(rc)) + return -ENOMEM; + + break; + } +#endif + +#if HAVE_ZSTD + case COMPRESSION_ZSTD: { + r = dlopen_zstd(LOG_DEBUG); + if (r < 0) + return r; + + c->d_zstd = sym_ZSTD_createDCtx(); + if (!c->d_zstd) + return -ENOMEM; + + break; + } +#endif + +#if HAVE_ZLIB + case COMPRESSION_GZIP: { + r = dlopen_zlib(LOG_DEBUG); + if (r < 0) + return r; + + r = sym_inflateInit2_(&c->gzip, /* windowBits= */ ZLIB_WBITS_GZIP, ZLIB_VERSION, (int) sizeof(c->gzip)); + if (r != Z_OK) + return -EIO; + + break; + } +#endif + +#if HAVE_BZIP2 + case COMPRESSION_BZIP2: { + r = dlopen_bzip2(LOG_DEBUG); + if (r < 0) + return r; + + r = sym_BZ2_bzDecompressInit(&c->bzip2, /* verbosity= */ 0, /* small= */ 0); + if (r != BZ_OK) + return -EIO; + + break; + } +#endif + + default: + if (type != _COMPRESSION_INVALID) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Detected %s compression, but support is not compiled in.", + compression_to_string(type)); + type = COMPRESSION_NONE; + break; + } + + c->type = type; + c->encoding = false; + + log_debug("Detected compression type: %s", compression_to_string(c->type)); + *ret = TAKE_PTR(c); + return 1; +} + +int decompressor_force_off(Decompressor **ret) { + assert(ret); + + *ret = compressor_free(*ret); + + Decompressor *c = new0(Decompressor, 1); + if (!c) + return -ENOMEM; + + c->type = COMPRESSION_NONE; + c->encoding = false; + *ret = c; + return 0; +} + +int decompressor_push(Decompressor *c, const void *data, size_t size, DecompressorCallback callback, void *userdata) { +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 + _cleanup_free_ uint8_t *buffer = NULL; +#endif + int r; + + assert(c); + assert(callback); + + if (c->encoding) + return -EINVAL; + + if (size == 0) + return 1; + + assert(data); + +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 + if (c->type != COMPRESSION_NONE) { + buffer = new(uint8_t, COMPRESS_PIPE_BUFFER_SIZE); + if (!buffer) + return -ENOMEM; + } +#endif + + switch (c->type) { + + case COMPRESSION_NONE: + r = callback(data, size, userdata); + if (r < 0) + return r; + + break; + +#if HAVE_XZ + case COMPRESSION_XZ: + c->xz.next_in = data; + c->xz.avail_in = size; + + while (c->xz.avail_in > 0) { + c->xz.next_out = buffer; + c->xz.avail_out = COMPRESS_PIPE_BUFFER_SIZE; + + lzma_ret lzr = sym_lzma_code(&c->xz, LZMA_RUN); + if (!IN_SET(lzr, LZMA_OK, LZMA_STREAM_END)) + return -EBADMSG; + + if (c->xz.avail_out < COMPRESS_PIPE_BUFFER_SIZE) { + r = callback(buffer, COMPRESS_PIPE_BUFFER_SIZE - c->xz.avail_out, userdata); + if (r < 0) + return r; + } + } + + break; +#endif + +#if HAVE_LZ4 + case COMPRESSION_LZ4: { + const uint8_t *src = data; + size_t src_remaining = size; + + while (src_remaining > 0) { + size_t produced = COMPRESS_PIPE_BUFFER_SIZE; + size_t consumed = src_remaining; + + size_t rc = sym_LZ4F_decompress(c->d_lz4, buffer, &produced, src, &consumed, NULL); + if (sym_LZ4F_isError(rc)) + return -EBADMSG; + + if (consumed == 0 && produced == 0) + break; /* No progress possible with current input */ + + src += consumed; + src_remaining -= consumed; + + if (produced > 0) { + r = callback(buffer, produced, userdata); + if (r < 0) + return r; + } + } + + break; + } +#endif + +#if HAVE_ZSTD + case COMPRESSION_ZSTD: { + ZSTD_inBuffer input = { + .src = (void*) data, + .size = size, + }; + + while (input.pos < input.size) { + ZSTD_outBuffer output = { + .dst = buffer, + .size = COMPRESS_PIPE_BUFFER_SIZE, + }; + + size_t res = sym_ZSTD_decompressStream(c->d_zstd, &output, &input); + if (sym_ZSTD_isError(res)) + return -EBADMSG; + + if (output.pos > 0) { + r = callback(output.dst, output.pos, userdata); + if (r < 0) + return r; + } + } + + break; + } +#endif + +#if HAVE_ZLIB + case COMPRESSION_GZIP: + if (size > UINT_MAX) + return -EFBIG; + + c->gzip.next_in = (void*) data; + c->gzip.avail_in = size; + + while (c->gzip.avail_in > 0) { + c->gzip.next_out = buffer; + c->gzip.avail_out = COMPRESS_PIPE_BUFFER_SIZE; + + int zr = sym_inflate(&c->gzip, Z_NO_FLUSH); + if (!IN_SET(zr, Z_OK, Z_STREAM_END)) + return -EBADMSG; + + if (c->gzip.avail_out < COMPRESS_PIPE_BUFFER_SIZE) { + r = callback(buffer, COMPRESS_PIPE_BUFFER_SIZE - c->gzip.avail_out, userdata); + if (r < 0) + return r; + } + + if (zr == Z_STREAM_END) + break; + } + + break; +#endif + +#if HAVE_BZIP2 + case COMPRESSION_BZIP2: + if (size > UINT_MAX) + return -EFBIG; + + c->bzip2.next_in = (char*) data; + c->bzip2.avail_in = size; + + while (c->bzip2.avail_in > 0) { + c->bzip2.next_out = (char*) buffer; + c->bzip2.avail_out = COMPRESS_PIPE_BUFFER_SIZE; + + int bzr = sym_BZ2_bzDecompress(&c->bzip2); + if (!IN_SET(bzr, BZ_OK, BZ_STREAM_END)) + return -EBADMSG; + + if (c->bzip2.avail_out < COMPRESS_PIPE_BUFFER_SIZE) { + r = callback(buffer, COMPRESS_PIPE_BUFFER_SIZE - c->bzip2.avail_out, userdata); + if (r < 0) + return r; + } + + if (bzr == BZ_STREAM_END) + break; + } + + break; +#endif + + default: + assert_not_reached(); + } + + return 1; +} + +int compressor_new(Compressor **ret, Compression type) { +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 + int r; +#endif + + assert(ret); + + _cleanup_(compressor_freep) Compressor *c = new0(Compressor, 1); + if (!c) + return -ENOMEM; + + c->type = _COMPRESSION_INVALID; + /* Set encoding early so that compressor_freep calls the correct cleanup (compression vs + * decompression) if any operation in the switch fails after setting c->type. This is safe + * because _COMPRESSION_INVALID hits the default: break case regardless of the encoding flag. */ + c->encoding = true; + + switch (type) { + +#if HAVE_XZ + case COMPRESSION_XZ: { + r = dlopen_xz(LOG_DEBUG); + if (r < 0) + return r; + + lzma_ret xzr = sym_lzma_easy_encoder(&c->xz, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); + if (xzr != LZMA_OK) + return -EIO; + + c->type = COMPRESSION_XZ; + break; + } +#endif + +#if HAVE_LZ4 + case COMPRESSION_LZ4: { + r = dlopen_lz4(LOG_DEBUG); + if (r < 0) + return r; + + size_t rc = sym_LZ4F_createCompressionContext(&c->c_lz4, LZ4F_VERSION); + if (sym_LZ4F_isError(rc)) + return -ENOMEM; + + c->type = COMPRESSION_LZ4; + + /* Generate the frame header and stash it for the first compressor_start call */ + size_t header_bound = sym_LZ4F_compressBound(0, &lz4_preferences); + c->lz4_header = malloc(header_bound); + if (!c->lz4_header) + return -ENOMEM; + + c->lz4_header_size = sym_LZ4F_compressBegin(c->c_lz4, c->lz4_header, header_bound, &lz4_preferences); + if (sym_LZ4F_isError(c->lz4_header_size)) + return -EINVAL; + + break; + } +#endif + +#if HAVE_ZSTD + case COMPRESSION_ZSTD: + r = dlopen_zstd(LOG_DEBUG); + if (r < 0) + return r; + + c->c_zstd = sym_ZSTD_createCCtx(); + if (!c->c_zstd) + return -ENOMEM; + + c->type = COMPRESSION_ZSTD; + + size_t z = sym_ZSTD_CCtx_setParameter(c->c_zstd, ZSTD_c_compressionLevel, ZSTD_CLEVEL_DEFAULT); + if (sym_ZSTD_isError(z)) + return -EIO; + + z = sym_ZSTD_CCtx_setParameter(c->c_zstd, ZSTD_c_checksumFlag, /* enable= */ 1); + if (sym_ZSTD_isError(z)) + log_debug("Failed to enable ZSTD checksum, ignoring: %s", sym_ZSTD_getErrorName(z)); + + break; +#endif + +#if HAVE_ZLIB + case COMPRESSION_GZIP: + r = dlopen_zlib(LOG_DEBUG); + if (r < 0) + return r; + + r = sym_deflateInit2_(&c->gzip, + Z_DEFAULT_COMPRESSION, + /* method= */ Z_DEFLATED, + /* windowBits= */ ZLIB_WBITS_GZIP, + /* memLevel= */ 8, + /* strategy= */ Z_DEFAULT_STRATEGY, + ZLIB_VERSION, (int) sizeof(c->gzip)); + if (r != Z_OK) + return -EIO; + + c->type = COMPRESSION_GZIP; + break; +#endif + +#if HAVE_BZIP2 + case COMPRESSION_BZIP2: + r = dlopen_bzip2(LOG_DEBUG); + if (r < 0) + return r; + + r = sym_BZ2_bzCompressInit(&c->bzip2, /* blockSize100k= */ 9, /* verbosity= */ 0, /* workFactor= */ 0); + if (r != BZ_OK) + return -EIO; + + c->type = COMPRESSION_BZIP2; + break; +#endif + + case COMPRESSION_NONE: + c->type = COMPRESSION_NONE; + break; + + default: + return -EOPNOTSUPP; + } + + *ret = TAKE_PTR(c); + return 0; +} + +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 +static int enlarge_buffer(void **buffer, size_t *buffer_size, size_t *buffer_allocated, size_t need) { + assert(buffer); + assert(buffer_size); + assert(buffer_allocated); + + need = MAX3(need, *buffer_size + 1, (size_t) COMPRESS_PIPE_BUFFER_SIZE); + if (*buffer_allocated >= need) + return 0; + + if (!greedy_realloc(buffer, need, 1)) + return -ENOMEM; + + *buffer_allocated = MALLOC_SIZEOF_SAFE(*buffer); + return 1; +} +#endif + +int compressor_start( + Compressor *c, + const void *data, + size_t size, + void **buffer, + size_t *buffer_size, + size_t *buffer_allocated) { + +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 + int r; +#endif + + assert(c); + assert(buffer); + assert(buffer_size); + assert(buffer_allocated); + + if (!c->encoding) + return -EINVAL; + + if (size == 0) + return 0; + + assert(data); + + *buffer_size = 0; + + switch (c->type) { + +#if HAVE_XZ + case COMPRESSION_XZ: + + c->xz.next_in = data; + c->xz.avail_in = size; + + while (c->xz.avail_in > 0) { + lzma_ret lzr; + + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + c->xz.next_out = (uint8_t*) *buffer + *buffer_size; + c->xz.avail_out = *buffer_allocated - *buffer_size; + + lzr = sym_lzma_code(&c->xz, LZMA_RUN); + if (lzr != LZMA_OK) + return -EIO; + + *buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out; + } + + break; +#endif + +#if HAVE_LZ4 + case COMPRESSION_LZ4: { + /* Prepend any stashed frame header from compressor_new */ + if (c->lz4_header_size > 0) { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, c->lz4_header_size); + if (r < 0) + return r; + + memcpy(*buffer, c->lz4_header, c->lz4_header_size); + *buffer_size = c->lz4_header_size; + c->lz4_header = mfree(c->lz4_header); + c->lz4_header_size = 0; + } + + size_t bound = sym_LZ4F_compressBound(size, &lz4_preferences); + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, *buffer_size + bound); + if (r < 0) + return r; + + size_t n = sym_LZ4F_compressUpdate(c->c_lz4, + (uint8_t*) *buffer + *buffer_size, + *buffer_allocated - *buffer_size, + data, size, NULL); + if (sym_LZ4F_isError(n)) + return -EIO; + + *buffer_size += n; + break; + } +#endif + +#if HAVE_ZSTD + case COMPRESSION_ZSTD: { + ZSTD_inBuffer input = { + .src = data, + .size = size, + }; + + while (input.pos < input.size) { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + ZSTD_outBuffer output = { + .dst = ((uint8_t *) *buffer + *buffer_size), + .size = *buffer_allocated - *buffer_size, + }; + + size_t res = sym_ZSTD_compressStream2(c->c_zstd, &output, &input, ZSTD_e_continue); + if (sym_ZSTD_isError(res)) + return -EIO; + + *buffer_size += output.pos; + } + + break; + } +#endif + +#if HAVE_ZLIB + case COMPRESSION_GZIP: + if (size > UINT_MAX) + return -EFBIG; + + c->gzip.next_in = (void*) data; + c->gzip.avail_in = size; + + while (c->gzip.avail_in > 0) { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + size_t avail = MIN(*buffer_allocated - *buffer_size, (size_t) UINT_MAX); + c->gzip.next_out = (uint8_t*) *buffer + *buffer_size; + c->gzip.avail_out = avail; + + r = sym_deflate(&c->gzip, Z_NO_FLUSH); + if (r != Z_OK) + return -EIO; + + *buffer_size += avail - c->gzip.avail_out; + } + + break; +#endif + +#if HAVE_BZIP2 + case COMPRESSION_BZIP2: + if (size > UINT_MAX) + return -EFBIG; + + c->bzip2.next_in = (void*) data; + c->bzip2.avail_in = size; + + while (c->bzip2.avail_in > 0) { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + size_t avail = MIN(*buffer_allocated - *buffer_size, (size_t) UINT_MAX); + c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size); + c->bzip2.avail_out = avail; + + r = sym_BZ2_bzCompress(&c->bzip2, BZ_RUN); + if (r != BZ_RUN_OK) + return -EIO; + + *buffer_size += avail - c->bzip2.avail_out; + } + + break; +#endif + + case COMPRESSION_NONE: + + if (*buffer_allocated < size) { + void *p; + + p = realloc(*buffer, size); + if (!p) + return -ENOMEM; + + *buffer = p; + *buffer_allocated = size; + } + + memcpy(*buffer, data, size); + *buffer_size = size; + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +int compressor_finish(Compressor *c, void **buffer, size_t *buffer_size, size_t *buffer_allocated) { +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 + int r; +#endif + + assert(c); + assert(buffer); + assert(buffer_size); + assert(buffer_allocated); + + if (!c->encoding) + return -EINVAL; + + *buffer_size = 0; + + switch (c->type) { + +#if HAVE_XZ + case COMPRESSION_XZ: { + lzma_ret lzr; + + c->xz.avail_in = 0; + + do { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + c->xz.next_out = (uint8_t*) *buffer + *buffer_size; + c->xz.avail_out = *buffer_allocated - *buffer_size; + + lzr = sym_lzma_code(&c->xz, LZMA_FINISH); + if (!IN_SET(lzr, LZMA_OK, LZMA_STREAM_END)) + return -EIO; + + *buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out; + } while (lzr != LZMA_STREAM_END); + + break; + } +#endif + +#if HAVE_LZ4 + case COMPRESSION_LZ4: { + size_t bound = sym_LZ4F_compressBound(0, &lz4_preferences); + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, bound); + if (r < 0) + return r; + + size_t n = sym_LZ4F_compressEnd(c->c_lz4, *buffer, *buffer_allocated, NULL); + if (sym_LZ4F_isError(n)) + return -EIO; + + *buffer_size = n; + break; + } +#endif + +#if HAVE_ZSTD + case COMPRESSION_ZSTD: { + ZSTD_inBuffer input = {}; + size_t res; + + do { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + ZSTD_outBuffer output = { + .dst = ((uint8_t *) *buffer + *buffer_size), + .size = *buffer_allocated - *buffer_size, + }; + + res = sym_ZSTD_compressStream2(c->c_zstd, &output, &input, ZSTD_e_end); + if (sym_ZSTD_isError(res)) + return -EIO; + + *buffer_size += output.pos; + } while (res != 0); + + break; + } +#endif + +#if HAVE_ZLIB + case COMPRESSION_GZIP: + c->gzip.avail_in = 0; + + do { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + size_t avail = MIN(*buffer_allocated - *buffer_size, (size_t) UINT_MAX); + c->gzip.next_out = (uint8_t*) *buffer + *buffer_size; + c->gzip.avail_out = avail; + + r = sym_deflate(&c->gzip, Z_FINISH); + if (!IN_SET(r, Z_OK, Z_STREAM_END)) + return -EIO; + + *buffer_size += avail - c->gzip.avail_out; + } while (r != Z_STREAM_END); + + break; +#endif + +#if HAVE_BZIP2 + case COMPRESSION_BZIP2: + c->bzip2.avail_in = 0; + + do { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + size_t avail = MIN(*buffer_allocated - *buffer_size, (size_t) UINT_MAX); + c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size); + c->bzip2.avail_out = avail; + + r = sym_BZ2_bzCompress(&c->bzip2, BZ_FINISH); + if (!IN_SET(r, BZ_FINISH_OK, BZ_STREAM_END)) + return -EIO; + + *buffer_size += avail - c->bzip2.avail_out; + } while (r != BZ_STREAM_END); + + break; +#endif + + case COMPRESSION_NONE: + break; + + default: + return -EOPNOTSUPP; + } + + return 0; } diff --git a/src/basic/compress.h b/src/basic/compress.h index 43885a7eedb5a..45584b7a6d13d 100644 --- a/src/basic/compress.h +++ b/src/basic/compress.h @@ -8,103 +8,84 @@ typedef enum Compression { COMPRESSION_XZ, COMPRESSION_LZ4, COMPRESSION_ZSTD, + COMPRESSION_GZIP, + COMPRESSION_BZIP2, _COMPRESSION_MAX, _COMPRESSION_INVALID = -EINVAL, } Compression; DECLARE_STRING_TABLE_LOOKUP(compression, Compression); -DECLARE_STRING_TABLE_LOOKUP(compression_lowercase, Compression); +DECLARE_STRING_TABLE_LOOKUP(compression_uppercase, Compression); +DECLARE_STRING_TABLE_LOOKUP(compression_extension, Compression); + +/* Try the lowercase string table first, fall back to the uppercase one. Useful for parsing user input + * where both forms (e.g. "xz" and "XZ") have historically been accepted. */ +Compression compression_from_string_harder(const char *s); + +/* Derives the compression type from a filename's extension, defaulting to COMPRESSION_NONE if the + * filename does not carry a recognized compression suffix. */ +Compression compression_from_filename(const char *filename); bool compression_supported(Compression c); -int compress_blob_xz(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level); -int compress_blob_lz4(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level); -int compress_blob_zstd(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level); - -int decompress_blob_xz(const void *src, uint64_t src_size, - void **dst, size_t* dst_size, size_t dst_max); -int decompress_blob_lz4(const void *src, uint64_t src_size, - void **dst, size_t* dst_size, size_t dst_max); -int decompress_blob_zstd(const void *src, uint64_t src_size, - void **dst, size_t* dst_size, size_t dst_max); +/* Buffer size used by streaming compression APIs and pipeline stages that feed into them. Sized to + * match the typical Linux pipe buffer so that pipeline stages don't lose throughput due to small + * intermediate buffers. */ +#define COMPRESS_PIPE_BUFFER_SIZE (128U*1024U) + +#define COMPRESSION_MAGIC_BYTES_MAX 6U +Compression compression_detect_from_magic(const uint8_t data[static COMPRESSION_MAGIC_BYTES_MAX]); + +/* Compressor / Decompressor — opaque push-based streaming compression context */ + +typedef struct Compressor Compressor; +typedef Compressor Decompressor; + +typedef int (*DecompressorCallback)(const void *data, size_t size, void *userdata); + +Compressor* compressor_free(Compressor *c); +DEFINE_TRIVIAL_CLEANUP_FUNC(Compressor*, compressor_free); + +int compressor_new(Compressor **ret, Compression type); +int compressor_start(Compressor *c, const void *data, size_t size, void **buffer, size_t *buffer_size, size_t *buffer_allocated); +int compressor_finish(Compressor *c, void **buffer, size_t *buffer_size, size_t *buffer_allocated); + +int decompressor_detect(Decompressor **ret, const void *data, size_t size); +int decompressor_force_off(Decompressor **ret); +int decompressor_push(Decompressor *c, const void *data, size_t size, DecompressorCallback callback, void *userdata); + +Compression compressor_type(const Compressor *c); + +/* Blob compression/decompression */ + +int compress_blob(Compression compression, + const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size, int level); int decompress_blob(Compression compression, const void *src, uint64_t src_size, - void **dst, size_t* dst_size, size_t dst_max); - -int decompress_startswith_xz(const void *src, uint64_t src_size, - void **buffer, - const void *prefix, size_t prefix_len, - uint8_t extra); -int decompress_startswith_lz4(const void *src, uint64_t src_size, - void **buffer, - const void *prefix, size_t prefix_len, - uint8_t extra); -int decompress_startswith_zstd(const void *src, uint64_t src_size, - void **buffer, - const void *prefix, size_t prefix_len, - uint8_t extra); + void **dst, size_t *dst_size, size_t dst_max); + +int decompress_zlib_raw(const void *src, uint64_t src_size, + void *dst, size_t dst_size, int wbits); + int decompress_startswith(Compression compression, const void *src, uint64_t src_size, void **buffer, const void *prefix, size_t prefix_len, uint8_t extra); -int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size); -int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size); -int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size); - -int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes); -int decompress_stream_lz4(int fdf, int fdt, uint64_t max_bytes); -int decompress_stream_zstd(int fdf, int fdt, uint64_t max_bytes); - -int dlopen_lz4(void); -int dlopen_zstd(void); -int dlopen_lzma(void); - -static inline int compress_blob( - Compression compression, - const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { - - switch (compression) { - case COMPRESSION_ZSTD: - return compress_blob_zstd(src, src_size, dst, dst_alloc_size, dst_size, level); - case COMPRESSION_LZ4: - return compress_blob_lz4(src, src_size, dst, dst_alloc_size, dst_size, level); - case COMPRESSION_XZ: - return compress_blob_xz(src, src_size, dst, dst_alloc_size, dst_size, level); - default: - return -EOPNOTSUPP; - } -} +/* Stream compression/decompression (fd-to-fd) */ -static inline int compress_stream(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { - switch (DEFAULT_COMPRESSION) { - case COMPRESSION_ZSTD: - return compress_stream_zstd(fdf, fdt, max_bytes, ret_uncompressed_size); - case COMPRESSION_LZ4: - return compress_stream_lz4(fdf, fdt, max_bytes, ret_uncompressed_size); - case COMPRESSION_XZ: - return compress_stream_xz(fdf, fdt, max_bytes, ret_uncompressed_size); - default: - return -EOPNOTSUPP; - } -} +int compress_stream(Compression type, int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size); +int decompress_stream(Compression type, int fdf, int fdt, uint64_t max_bytes); +int decompress_stream_by_filename(const char *filename, int fdf, int fdt, uint64_t max_bytes); + +int dlopen_xz(int log_level); +int dlopen_lz4(int log_level); +int dlopen_zstd(int log_level); +int dlopen_zlib(int log_level); +int dlopen_bzip2(int log_level); static inline const char* default_compression_extension(void) { - switch (DEFAULT_COMPRESSION) { - case COMPRESSION_ZSTD: - return ".zst"; - case COMPRESSION_LZ4: - return ".lz4"; - case COMPRESSION_XZ: - return ".xz"; - default: - return ""; - } + return compression_extension_to_string(DEFAULT_COMPRESSION) ?: ""; } - -int decompress_stream(const char *filename, int fdf, int fdt, uint64_t max_bytes); diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c index cd91c5bc1ab54..d4ff194d4cce4 100644 --- a/src/basic/conf-files.c +++ b/src/basic/conf-files.c @@ -33,12 +33,7 @@ ConfFile* conf_file_free(ConfFile *c) { return mfree(c); } -void conf_file_free_many(ConfFile **array, size_t n) { - FOREACH_ARRAY(i, array, n) - conf_file_free(*i); - - free(array); -} +DEFINE_POINTER_ARRAY_FREE_FUNC(ConfFile*, conf_file_free); static int conf_files_log_level(ConfFilesFlags flags) { return FLAGS_SET(flags, CONF_FILES_WARN) ? LOG_WARNING : LOG_DEBUG; @@ -133,7 +128,7 @@ static bool conf_files_need_stat(ConfFilesFlags flags) { } static ChaseFlags conf_files_chase_flags(ConfFilesFlags flags) { - ChaseFlags chase_flags = CHASE_AT_RESOLVE_IN_ROOT; + ChaseFlags chase_flags = 0; if (!conf_files_need_stat(flags) || FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK)) /* Even if no verification is requested, let's unconditionally call chaseat(), @@ -169,7 +164,7 @@ static int conf_file_chase_and_verify( root = empty_to_root(root); - r = chaseat(rfd, path, conf_files_chase_flags(flags), &resolved_path, &fd); + r = chaseat(rfd, rfd, path, conf_files_chase_flags(flags), &resolved_path, &fd); if (r < 0) return log_full_errno(log_level, r, "Failed to chase '%s%s': %m", root, skip_leading_slash(original_path)); @@ -311,7 +306,7 @@ int conf_file_new_at( if (r < 0 && r != -EDESTADDRREQ) return log_full_errno(log_level, r, "Failed to extract directory from '%s': %m", path); if (r >= 0) { - r = chaseat(rfd, dirpath, + r = chaseat(rfd, rfd, dirpath, CHASE_MUST_BE_DIRECTORY | conf_files_chase_flags(flags), &resolved_dirpath, /* ret_fd= */ NULL); if (r < 0) @@ -485,7 +480,7 @@ static int dump_files(Hashmap *fh, const char *root, ConfFilesFlags flags, ConfF size_t n_files = 0; int r; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); assert(ret_files); assert(ret_n_files); @@ -528,7 +523,7 @@ static int copy_and_sort_files_from_hashmap( int log_level = conf_files_log_level(flags); /* The entries in the array given by hashmap_dump_sorted() are still owned by the hashmap. - * Hence, do not use conf_file_free_many() for 'entries' */ + * Hence, do not use conf_file_free_array() for 'entries' */ r = hashmap_dump_sorted(fh, (void***) &files, &n_files); if (r < 0) return log_oom_full(log_level); @@ -642,7 +637,7 @@ static int conf_files_list_impl( _cleanup_closedir_ DIR *dir = NULL; _cleanup_free_ char *path = NULL; - r = chase_and_opendirat(rfd, *p, CHASE_AT_RESOLVE_IN_ROOT, &path, &dir); + r = chase_and_opendirat(rfd, rfd, *p, 0, &path, &dir); if (r < 0) { if (r != -ENOENT) log_full_errno(conf_files_log_level(flags), r, diff --git a/src/basic/conf-files.h b/src/basic/conf-files.h index 031463172abbf..e6c248a59a1e8 100644 --- a/src/basic/conf-files.h +++ b/src/basic/conf-files.h @@ -29,7 +29,7 @@ typedef struct ConfFile { ConfFile* conf_file_free(ConfFile *c); DEFINE_TRIVIAL_CLEANUP_FUNC(ConfFile*, conf_file_free); -void conf_file_free_many(ConfFile **array, size_t n); +void conf_file_free_array(ConfFile **array, size_t n); int conf_file_new_at(const char *path, const char *root, int rfd, ConfFilesFlags flags, ConfFile **ret); int conf_file_new(const char *path, const char *root, ConfFilesFlags flags, ConfFile **ret); diff --git a/src/basic/device-nodes.c b/src/basic/device-nodes.c index 8d4e38ec0638b..15abe31236968 100644 --- a/src/basic/device-nodes.c +++ b/src/basic/device-nodes.c @@ -6,6 +6,8 @@ #include "device-nodes.h" #include "path-util.h" +#include "stat-util.h" +#include "stdio-util.h" #include "string-util.h" #include "utf8.h" @@ -38,10 +40,10 @@ int encode_devnode_name(const char *str, char *str_enc, size_t len) { } else if (str[i] == '\\' || !allow_listed_char_for_devnode(str[i], NULL)) { - if (len-j < 4) + if (len-j < 5) return -EINVAL; - sprintf(&str_enc[j], "\\x%02x", (unsigned char) str[i]); + assert_se(snprintf_ok(&str_enc[j], 5, "\\x%02x", (unsigned char) str[i])); j += 4; } else { @@ -62,6 +64,7 @@ int encode_devnode_name(const char *str, char *str_enc, size_t len) { int devnode_same(const char *a, const char *b) { struct stat sa, sb; + int r; assert(a); assert(b); @@ -71,13 +74,15 @@ int devnode_same(const char *a, const char *b) { if (stat(a, &sa) < 0) return -errno; + r = stat_verify_device_node(&sa); + if (r < 0) + return r; + if (stat(b, &sb) < 0) return -errno; - - if (!S_ISBLK(sa.st_mode) && !S_ISCHR(sa.st_mode)) - return -ENODEV; - if (!S_ISBLK(sb.st_mode) && !S_ISCHR(sb.st_mode)) - return -ENODEV; + r = stat_verify_device_node(&sb); + if (r < 0) + return r; if (((sa.st_mode ^ sb.st_mode) & S_IFMT) != 0) /* both inode same device node type? */ return false; diff --git a/src/basic/devnum-util.c b/src/basic/devnum-util.c index 99c20662df684..92ba078dc5584 100644 --- a/src/basic/devnum-util.c +++ b/src/basic/devnum-util.c @@ -18,6 +18,8 @@ int parse_devnum(const char *s, dev_t *ret) { size_t n; int r; + assert(ret); + n = strspn(s, DIGITS); if (n == 0) return -EINVAL; diff --git a/src/basic/dirent-util.c b/src/basic/dirent-util.c index a1508747777a0..91a7040e408e7 100644 --- a/src/basic/dirent-util.c +++ b/src/basic/dirent-util.c @@ -27,6 +27,7 @@ int dirent_ensure_type(int dir_fd, struct dirent *de) { r = xstatx_full(dir_fd, de->d_name, AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT, + /* xstatx_flags= */ 0, /* mandatory_mask= */ STATX_TYPE, /* optional_mask= */ STATX_INO, /* mandatory_attributes= */ 0, diff --git a/src/basic/dlfcn-util.c b/src/basic/dlfcn-util.c index 8574e99546b81..86ec2d28fd5a0 100644 --- a/src/basic/dlfcn-util.c +++ b/src/basic/dlfcn-util.c @@ -47,21 +47,38 @@ int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) { return r; } -int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) { +int dlopen_verbose(void **dlp, const char *filename, int log_level) { int r; + assert(dlp); + if (*dlp) return 0; /* Already loaded */ _cleanup_(dlclosep) void *dl = NULL; const char *dle = NULL; r = dlopen_safe(filename, &dl, &dle); - if (r < 0) { - log_debug_errno(r, "Shared library '%s' is not available: %s", filename, dle ?: STRERROR(r)); - return -EOPNOTSUPP; /* Turn into recognizable error */ - } + if (r < 0) + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "Shared library '%s' is not available: %s", filename, dle ?: STRERROR(r)); log_debug("Loaded shared library '%s' via dlopen().", filename); + *dlp = TAKE_PTR(dl); + return 1; +} + +int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) { + int r; + + assert(dlp); + + if (*dlp) + return 0; /* Already loaded */ + + _cleanup_(dlclosep) void *dl = NULL; + r = dlopen_verbose(&dl, filename, log_level); + if (r < 0) + return r; va_list ap; va_start(ap, log_level); diff --git a/src/basic/dlfcn-util.h b/src/basic/dlfcn-util.h index 367c47ddb8610..ccf0ec6be3b26 100644 --- a/src/basic/dlfcn-util.h +++ b/src/basic/dlfcn-util.h @@ -11,6 +11,7 @@ static inline void dlclosep(void **dlp) { safe_dlclose(*dlp); } +int dlopen_verbose(void **dlp, const char *filename, int log_level); int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) _sentinel_; int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) _sentinel_; @@ -32,47 +33,6 @@ int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_l #define DLSYM_ARG_FORCE(arg) \ &sym_##arg, STRINGIFY(arg) -#define ELF_NOTE_DLOPEN_VENDOR "FDO" -#define ELF_NOTE_DLOPEN_TYPE UINT32_C(0x407c0c0a) -#define ELF_NOTE_DLOPEN_PRIORITY_REQUIRED "required" -#define ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED "recommended" -#define ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED "suggested" - -/* Add an ".note.dlopen" ELF note to our binary that declares our weak dlopen() dependency. This - * information can be read from an ELF file via "readelf -p .note.dlopen" or an equivalent command. */ -#define _ELF_NOTE_DLOPEN(json, variable_name) \ - __attribute__((used, section(".note.dlopen"))) _Alignas(sizeof(uint32_t)) static const struct { \ - struct { \ - uint32_t n_namesz, n_descsz, n_type; \ - } nhdr; \ - char name[sizeof(ELF_NOTE_DLOPEN_VENDOR)]; \ - _Alignas(sizeof(uint32_t)) char dlopen_json[sizeof(json)]; \ - } variable_name = { \ - .nhdr = { \ - .n_namesz = sizeof(ELF_NOTE_DLOPEN_VENDOR), \ - .n_descsz = sizeof(json), \ - .n_type = ELF_NOTE_DLOPEN_TYPE, \ - }, \ - .name = ELF_NOTE_DLOPEN_VENDOR, \ - .dlopen_json = json, \ - } - -#define _SONAME_ARRAY1(a) "[\""a"\"]" -#define _SONAME_ARRAY2(a, b) "[\""a"\",\""b"\"]" -#define _SONAME_ARRAY3(a, b, c) "[\""a"\",\""b"\",\""c"\"]" -#define _SONAME_ARRAY4(a, b, c, d) "[\""a"\",\""b"\",\""c"\"",\""d"\"]" -#define _SONAME_ARRAY5(a, b, c, d, e) "[\""a"\",\""b"\",\""c"\"",\""d"\",\""e"\"]" -#define _SONAME_ARRAY_GET(_1,_2,_3,_4,_5,NAME,...) NAME -#define _SONAME_ARRAY(...) _SONAME_ARRAY_GET(__VA_ARGS__, _SONAME_ARRAY5, _SONAME_ARRAY4, _SONAME_ARRAY3, _SONAME_ARRAY2, _SONAME_ARRAY1)(__VA_ARGS__) - -/* The 'priority' must be one of 'required', 'recommended' or 'suggested' as per specification, use the - * macro defined above to specify it. - * Multiple sonames can be passed and they will be automatically constructed into a json array (but note that - * due to preprocessor language limitations if more than the limit defined above is used, a new - * _SONAME_ARRAY will need to be added). */ -#define ELF_NOTE_DLOPEN(feature, description, priority, ...) \ - _ELF_NOTE_DLOPEN("[{\"feature\":\"" feature "\",\"description\":\"" description "\",\"priority\":\"" priority "\",\"soname\":" _SONAME_ARRAY(__VA_ARGS__) "}]", UNIQ_T(s, UNIQ)) - /* If called dlopen_many_sym_or_warn() will fail with EPERM. This can be used to block lazy loading of shared * libs, if we transfer a process into a different namespace. Note that this does not work for all calls of * dlopen(), just those through our dlopen_safe() wrapper (which we use comprehensively in our diff --git a/src/basic/env-file.c b/src/basic/env-file.c index 2e15e7eeb7d57..587618614e66d 100644 --- a/src/basic/env-file.c +++ b/src/basic/env-file.c @@ -420,6 +420,40 @@ int parse_env_file_fd_sentinel( return r; } +int parse_env_datav( + const char *data, + size_t size, + const char *fname, /* only used for logging */ + va_list ap) { + + assert(data); + + if (size == SIZE_MAX) + size = strlen_ptr(data); + + _cleanup_fclose_ FILE *f = fmemopen_unlocked((void*) data, size, "r"); + if (!f) + return -ENOMEM; + + return parse_env_filev(f, fname, ap); +} + +int parse_env_data_sentinel( + const char *data, + size_t size, + const char *fname, /* only used for logging */ + ...) { + + va_list ap; + int r; + + va_start(ap, fname); + r = parse_env_datav(data, size, fname, ap); + va_end(ap); + + return r; +} + static int load_env_file_push( const char *filename, unsigned line, const char *key, char *value, diff --git a/src/basic/env-file.h b/src/basic/env-file.h index 78d47f4980857..4d74245915bd7 100644 --- a/src/basic/env-file.h +++ b/src/basic/env-file.h @@ -9,6 +9,11 @@ int parse_env_file_sentinel(FILE *f, const char *fname, ...) _sentinel_; #define parse_env_file(f, fname, ...) parse_env_file_sentinel(f, fname, __VA_ARGS__, NULL) int parse_env_file_fd_sentinel(int fd, const char *fname, ...) _sentinel_; #define parse_env_file_fd(fd, fname, ...) parse_env_file_fd_sentinel(fd, fname, __VA_ARGS__, NULL) + +int parse_env_datav(const char *data, size_t size, const char *fname, va_list ap); +int parse_env_data_sentinel(const char *data, size_t size, const char *fname, ...) _sentinel_; +#define parse_env_data(text, size, fname, ...) parse_env_data_sentinel(text, size, fname, __VA_ARGS__, NULL) + int load_env_file(FILE *f, const char *fname, char ***ret); int load_env_file_pairs(FILE *f, const char *fname, char ***ret); int load_env_file_pairs_fd(int fd, const char *fname, char ***ret); diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 7964a368db265..4f6240e546519 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -19,7 +19,7 @@ /* We follow bash for the character set. Different shells have different rules. */ #define VALID_BASH_ENV_NAME_CHARS \ - DIGITS LETTERS \ + ALPHANUMERICAL \ "_" size_t sc_arg_max(void) { diff --git a/src/basic/escape.c b/src/basic/escape.c index e1771bf432278..6b6612d33bc45 100644 --- a/src/basic/escape.c +++ b/src/basic/escape.c @@ -106,6 +106,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, assert(p); assert(ret); + assert(eight_bit); /* Unescapes C style. Returns the unescaped character in ret. * Sets *eight_bit to true if the escaped sequence either fits in @@ -447,10 +448,10 @@ char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFl FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS)); } -char* octescape(const char *s, size_t len) { +char* octescape_full(const char *s, size_t len, const char *bad) { char *buf, *t; - /* Escapes \ and " chars, in \nnn style escaping. */ + /* Escapes all chars in bad, in addition to \ and " chars, in \nnn octal style escaping. */ assert(s || len == 0); @@ -467,7 +468,7 @@ char* octescape(const char *s, size_t len) { for (size_t i = 0; i < len; i++) { uint8_t u = (uint8_t) s[i]; - if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"')) { + if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"') || (bad && strchr(bad, u))) { *(t++) = '\\'; *(t++) = '0' + (u >> 6); *(t++) = '0' + ((u >> 3) & 7); diff --git a/src/basic/escape.h b/src/basic/escape.h index a8b68fa75c277..625758f2f4c9f 100644 --- a/src/basic/escape.h +++ b/src/basic/escape.h @@ -59,7 +59,10 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, XEscape static inline char* xescape(const char *s, const char *bad) { return xescape_full(s, bad, SIZE_MAX, 0); } -char* octescape(const char *s, size_t len); +char* octescape_full(const char *s, size_t len, const char *bad); +static inline char* octescape(const char *s, size_t len) { + return octescape_full(s, len, NULL); +} char* decescape(const char *s, size_t len, const char *bad) _nonnull_if_nonzero_(1, 2); char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags); diff --git a/src/basic/ether-addr-util.c b/src/basic/ether-addr-util.c index 375c044415738..2919422bd2e1a 100644 --- a/src/basic/ether-addr-util.c +++ b/src/basic/ether-addr-util.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include "ether-addr-util.h" @@ -68,6 +69,29 @@ bool hw_addr_is_null(const struct hw_addr_data *addr) { return addr->length == 0 || memeqzero(addr->bytes, addr->length); } +bool hw_addr_is_valid(const struct hw_addr_data *addr, uint16_t iftype) { + assert(addr); + + switch (iftype) { + case ARPHRD_ETHER: + /* Refuse all zero and all 0xFF. */ + if (addr->length != ETH_ALEN) + return false; + + return !ether_addr_is_null(&addr->ether) && !ether_addr_is_broadcast(&addr->ether); + + case ARPHRD_INFINIBAND: + /* The last 8 bytes cannot be zero. */ + if (addr->length != INFINIBAND_ALEN) + return false; + + return !memeqzero(addr->bytes + INFINIBAND_ALEN - 8, 8); + + default: + return false; + } +} + DEFINE_HASH_OPS(hw_addr_hash_ops, struct hw_addr_data, hw_addr_hash_func, hw_addr_compare); DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(hw_addr_hash_ops_free, struct hw_addr_data, hw_addr_hash_func, hw_addr_compare, free); @@ -91,10 +115,15 @@ char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR } int ether_addr_compare(const struct ether_addr *a, const struct ether_addr *b) { + assert(a); + assert(b); + return memcmp(a, b, ETH_ALEN); } static void ether_addr_hash_func(const struct ether_addr *p, struct siphash *state) { + assert(p); + siphash24_compress_typesafe(*p, state); } diff --git a/src/basic/ether-addr-util.h b/src/basic/ether-addr-util.h index cf014a95273e8..d342b5621e07e 100644 --- a/src/basic/ether-addr-util.h +++ b/src/basic/ether-addr-util.h @@ -57,6 +57,7 @@ static inline bool hw_addr_equal(const struct hw_addr_data *a, const struct hw_a return hw_addr_compare(a, b) == 0; } bool hw_addr_is_null(const struct hw_addr_data *addr) _pure_; +bool hw_addr_is_valid(const struct hw_addr_data *addr, uint16_t iftype); extern const struct hash_ops hw_addr_hash_ops; extern const struct hash_ops hw_addr_hash_ops_free; diff --git a/src/basic/extract-word.c b/src/basic/extract-word.c index 99de8740d1320..5ebf67437c2c7 100644 --- a/src/basic/extract-word.c +++ b/src/basic/extract-word.c @@ -204,6 +204,9 @@ int extract_first_word_and_warn( const char *save; int r; + assert(p); + assert(ret); + save = *p; r = extract_first_word(p, ret, separators, flags); if (r >= 0) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 99eff99874d14..c2d46aae155a2 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -166,6 +166,15 @@ int fd_nonblock(int fd, bool nonblock) { return 1; } +void nonblock_resetp(int *fd) { + assert(fd); + + PROTECT_ERRNO; + + if (*fd >= 0) + (void) fd_nonblock(*fd, false); +} + int stdio_disable_nonblock(void) { int ret = 0; @@ -868,6 +877,7 @@ int fd_reopen_condition( assert(fd >= 0); assert(!FLAGS_SET(flags, O_CREAT)); + assert(ret_new_fd); /* Invokes fd_reopen(fd, flags), but only if the existing F_GETFL flags don't match the specified * flags (masked by the specified mask). This is useful for converting O_PATH fds into real fds if @@ -1061,10 +1071,6 @@ int path_is_root_at(int dir_fd, const char *path) { dir_fd = fd; } - _cleanup_close_ int root_fd = open("/", O_PATH|O_DIRECTORY|O_CLOEXEC); - if (root_fd < 0) - return -errno; - /* Even if the root directory has the same inode as our fd, the fd may not point to the root * directory "/", and we also need to check that the mount ids are the same. Otherwise, a construct * like the following could be used to trick us: @@ -1073,10 +1079,10 @@ int path_is_root_at(int dir_fd, const char *path) { * $ mount --bind / /tmp/x */ - return fds_are_same_mount(dir_fd, root_fd); + return fds_inode_and_mount_same(dir_fd, XAT_FDROOT); } -int fds_are_same_mount(int fd1, int fd2) { +int fds_inode_and_mount_same(int fd1, int fd2) { struct statx sx1, sx2; int r; @@ -1089,13 +1095,20 @@ int fds_are_same_mount(int fd1, int fd2) { if (r < 0) return r; + if (fd1 == fd2) /* Shortcut things if fds are the same (only after validating the fd) */ + return true; + r = xstatx(fd2, /* path = */ NULL, AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sx2); if (r < 0) return r; - return statx_inode_same(&sx1, &sx2) && statx_mount_same(&sx1, &sx2); + r = statx_mount_same(&sx1, &sx2); + if (r <= 0) + return r; + + return statx_inode_same(&sx1, &sx2); } int resolve_xat_fdroot(int *fd, const char **path, char **ret_buffer) { diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index ee1dc870df859..c15ce7fddde4a 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -112,6 +112,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL); int fd_nonblock(int fd, bool nonblock); int stdio_disable_nonblock(void); +void nonblock_resetp(int *fd); + int fd_cloexec(int fd, bool cloexec); int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec); @@ -179,7 +181,7 @@ static inline int dir_fd_is_root_or_cwd(int dir_fd) { return IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT) ? true : path_is_root_at(dir_fd, NULL); } -int fds_are_same_mount(int fd1, int fd2); +int fds_inode_and_mount_same(int fd1, int fd2); int resolve_xat_fdroot(int *fd, const char **path, char **ret_buffer); diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 90436f6ecf820..661667a6b2a1e 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -7,12 +7,15 @@ #include #include "alloc-util.h" +#include "chase.h" #include "errno-util.h" #include "extract-word.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" #include "hexdecoct.h" +#include "io-util.h" +#include "iovec-util.h" #include "label.h" #include "log.h" #include "mkdir.h" @@ -1367,6 +1370,8 @@ int read_timestamp_file(const char *fn, usec_t *ret) { uint64_t t; int r; + assert(ret); + r = read_one_line_file(fn, &ln); if (r < 0) return r; @@ -1655,3 +1660,63 @@ int warn_file_is_world_accessible(const char *filename, struct stat *st, const c filename, st->st_mode & 07777); return 0; } + +int write_data_file_atomic_at( + int dir_fd, + const char *path, + const struct iovec *iovec, + WriteDataFileFlags flags) { + + int r; + + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); + + /* This is a cousin of write_string_file_atomic(), but operates with arbitrary struct iovec binary + * data (rather than strings), works without FILE* streams, and does direct syscalls instead. */ + + _cleanup_free_ char *dn = NULL, *fn = NULL; + r = path_split_prefix_filename(path, &dn, &fn); + if (IN_SET(r, -EADDRNOTAVAIL, O_DIRECTORY)) + return -EISDIR; /* path refers to "." or "/" (which are dirs, which we cannot write), or is suffixed with "/" */ + if (r < 0) + return r; + + _cleanup_close_ int mfd = -EBADF; + if (dn) { + /* If there's a directory component, readjust our position */ + r = chaseat(XAT_FDROOT, + dir_fd, + dn, + FLAGS_SET(flags, WRITE_DATA_FILE_MKDIR_0755) ? CHASE_MKDIR_0755 : 0, + /* ret_path= */ NULL, + &mfd); + if (r < 0) + return r; + + dir_fd = mfd; + } + + _cleanup_free_ char *t = NULL; + _cleanup_close_ int fd = open_tmpfile_linkable_at(dir_fd, fn, O_WRONLY|O_CLOEXEC, &t); + if (fd < 0) + return fd; + + CLEANUP_TMPFILE_AT(dir_fd, t); + + if (iovec_is_set(iovec)) { + r = loop_write(fd, iovec->iov_base, iovec->iov_len); + if (r < 0) + return r; + } + + r = fchmod_umask(fd, FLAGS_SET(flags, WRITE_DATA_FILE_MODE_0400) ? 0400 : 0644); + if (r < 0) + return r; + + r = link_tmpfile_at(fd, dir_fd, t, fn, LINK_TMPFILE_REPLACE); + if (r < 0) + return r; + + t = mfree(t); /* disarm CLEANUP_TMPFILE_AT */ + return 0; +} diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 578c16c0ee394..274fdfbd7c89a 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -163,3 +163,10 @@ int safe_fgetc(FILE *f, char *ret); int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line); int fopen_mode_to_flags(const char *mode); + +typedef enum WriteDataFileFlags { + WRITE_DATA_FILE_MKDIR_0755 = 1 << 0, + WRITE_DATA_FILE_MODE_0400 = 1 << 1, +} WriteDataFileFlags; + +int write_data_file_atomic_at(int dir_fd, const char *path, const struct iovec *iovec, WriteDataFileFlags flags); diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index f790ca4e136b5..3960938309fcd 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "alloc-util.h" @@ -1130,6 +1131,45 @@ int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, b } } +static int openat_with_automount(int dir_fd, const char *path, int open_flags, mode_t mode) { + /* When XO_TRIGGER_AUTOMOUNT is set we want to trigger automounts on the path. open() with O_PATH + * does not do that, so we use open_tree() without OPEN_TREE_CLONE which is equivalent to open() with + * O_PATH except that it does trigger automounts. Some sandboxes reject open_tree() with EPERM or + * ENOSYS, in which case we fall back to plain openat(): autofs wouldn't work inside a restricted + * mount namespace anyway. */ + + static bool can_open_tree = true; + int r; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(path); + + if (can_open_tree) { + r = RET_NERRNO(open_tree(dir_fd, path, + OPEN_TREE_CLOEXEC | + (FLAGS_SET(open_flags, O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0))); + if (r >= 0) { + /* open_tree() doesn't honor O_DIRECTORY, so enforce it ourselves to match + * the openat() fallback's behavior. */ + if (FLAGS_SET(open_flags, O_DIRECTORY)) { + int q = fd_verify_directory(r); + if (q < 0) { + safe_close(r); + return q; + } + } + + return r; + } + if (r != -EPERM && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return r; + + can_open_tree = false; + } + + return RET_NERRNO(openat(dir_fd, path, open_flags, mode)); +} + int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) { _cleanup_close_ int fd = -EBADF; bool made_dir = false, made_file = false; @@ -1137,8 +1177,22 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); - /* An inode cannot be both a directory and a regular file at the same time. */ + /* An inode can only be one of a directory, a regular file or a socket at the same time. */ assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_REGULAR))); + assert(!(FLAGS_SET(xopen_flags, XO_REGULAR) && FLAGS_SET(xopen_flags, XO_SOCKET))); + assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_SOCKET))); + /* Sockets cannot be open()ed, only pinned via O_PATH. */ + assert(!FLAGS_SET(xopen_flags, XO_SOCKET) || FLAGS_SET(open_flags, O_PATH)); + /* XO_TRIGGER_AUTOMOUNT requires O_PATH and does not support creating inodes. XO_SUBVOLUME + * requires O_CREAT, and XO_NOCOW needs a writable fd for its chattr ioctl, so neither is + * compatible with XO_TRIGGER_AUTOMOUNT. */ + assert(!FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) || + (FLAGS_SET(open_flags, O_PATH) && !FLAGS_SET(open_flags, O_CREAT))); + assert(!(FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) && FLAGS_SET(xopen_flags, XO_SUBVOLUME))); + assert(!(FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) && FLAGS_SET(xopen_flags, XO_NOCOW))); + + /* Don't specify an access mode if you want auto mode. */ + assert(!FLAGS_SET(xopen_flags, XO_AUTO_RW_RO) || (open_flags & O_ACCMODE_STRICT) == 0); /* This is like openat(), but has a few tricks up its sleeves, extending behaviour: * @@ -1153,16 +1207,33 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ * * • if XO_REGULAR is specified will return an error if inode is not a regular file. * + * • if XO_SOCKET is specified will return an error if inode is not a socket. + * + * • if XO_TRIGGER_AUTOMOUNT is specified O_PATH fds will trigger automounts. + * * • If mode is specified as MODE_INVALID, we'll use 0755 for dirs, and 0644 for regular files. * * • The dir fd can be passed as XAT_FDROOT, in which case any relative paths will be taken relative to the root fs. + * + * • If XO_AUTO_RW_RO is specified and the file cannot be opened in O_RDWR mode due to EACCES/EROFS or similar, retry in O_RDONLY mode. */ if (mode == MODE_INVALID) mode = (open_flags & O_DIRECTORY) ? 0755 : 0644; + if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) { + if (open_flags & O_DIRECTORY) { + /* Directories can only be opened in read-only mode */ + xopen_flags &= ~XO_AUTO_RW_RO; + open_flags |= O_RDONLY; + } else if (open_flags & O_PATH) + /* O_PATH is incompatible with O_RDONLY/O_RDWR → fail */ + return -EINVAL; + } + if (isempty(path)) { assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL)); + open_flags &= ~O_NOFOLLOW; if (FLAGS_SET(xopen_flags, XO_REGULAR)) { r = fd_verify_regular(dir_fd); @@ -1170,7 +1241,22 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ return r; } - return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW); + if (FLAGS_SET(xopen_flags, XO_SOCKET)) { + r = fd_verify_socket(dir_fd); + if (r < 0) + return r; + } + + if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) { + /* First try: in r/w mode */ + fd = fd_reopen(dir_fd, open_flags|O_RDWR); + if (!ERRNO_IS_NEG_FS_WRITE_REFUSED(fd) && fd != -EISDIR) + return TAKE_FD(fd); + + open_flags |= O_RDONLY; + } + + return fd_reopen(dir_fd, open_flags); } _cleanup_close_ int _dir_fd = -EBADF; @@ -1217,9 +1303,11 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ * first */ if (FLAGS_SET(open_flags, O_PATH)) { - fd = openat(dir_fd, path, open_flags, mode); + fd = FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) ? + openat_with_automount(dir_fd, path, open_flags, mode) : + RET_NERRNO(openat(dir_fd, path, open_flags, mode)); if (fd < 0) { - r = -errno; + r = fd; goto error; } @@ -1229,10 +1317,23 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ } else if (FLAGS_SET(open_flags, O_CREAT|O_EXCL)) { /* In O_EXCL mode we can just create the thing, everything is dealt with for us */ - fd = openat(dir_fd, path, open_flags, mode); + + if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) { + fd = RET_NERRNO(openat(dir_fd, path, open_flags|O_RDWR, mode)); + if (ERRNO_IS_NEG_FS_WRITE_REFUSED(fd)) + open_flags |= O_RDONLY; + else if (fd < 0) { + r = fd; + goto error; + } + } + if (fd < 0) { - r = -errno; - goto error; + fd = openat(dir_fd, path, open_flags, mode); + if (fd < 0) { + r = -errno; + goto error; + } } made_file = true; @@ -1246,10 +1347,24 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ } /* Doesn't exist yet, then try to create it */ - fd = openat(dir_fd, path, open_flags|O_CREAT|O_EXCL, mode); + open_flags |= O_EXCL; + + if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) { + fd = RET_NERRNO(openat(dir_fd, path, open_flags|O_RDWR, mode)); + if (ERRNO_IS_NEG_FS_WRITE_REFUSED(fd)) + open_flags |= O_RDONLY; + else if (fd < 0) { + r = fd; + goto error; + } + } + if (fd < 0) { - r = -errno; - goto error; + fd = openat(dir_fd, path, open_flags, mode); + if (fd < 0) { + r = -errno; + goto error; + } } made_file = true; @@ -1259,19 +1374,59 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ if (r < 0) goto error; - fd = fd_reopen(inode_fd, open_flags & ~(O_NOFOLLOW|O_CREAT)); + open_flags &= ~(O_NOFOLLOW|O_CREAT); + + if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) { + fd = fd_reopen(inode_fd, open_flags|O_RDWR); + if (ERRNO_IS_NEG_FS_WRITE_REFUSED(fd)) + open_flags |= O_RDONLY; + else if (fd < 0) { + r = fd; + goto error; + } + } + if (fd < 0) { - r = fd; - goto error; + fd = fd_reopen(inode_fd, open_flags); + if (fd < 0) { + r = fd; + goto error; + } } } } - } else { - fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file); + } else if (FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT)) { + fd = openat_with_automount(dir_fd, path, open_flags, mode); if (fd < 0) { r = fd; goto error; } + } else { + /* XO_SOCKET also lands here: it requires O_PATH (see asserts above) so openat() pins + * the inode without connecting, and fd_verify_socket() below enforces the type. */ + if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) { + fd = openat_report_new(dir_fd, path, O_RDWR|open_flags, mode, &made_file); + if (ERRNO_IS_NEG_FS_WRITE_REFUSED(fd) || fd == -EISDIR) + open_flags |= O_RDONLY; + else if (fd < 0) { + r = fd; + goto error; + } + } + + if (fd < 0) { + fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file); + if (fd < 0) { + r = fd; + goto error; + } + } + } + + if (FLAGS_SET(xopen_flags, XO_SOCKET)) { + r = fd_verify_socket(fd); + if (r < 0) + goto error; } if (call_label_ops_post) { diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index d75c253dbb46a..32283d4c1fbc5 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -109,10 +109,13 @@ int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size); int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char **ret_path); typedef enum XOpenFlags { - XO_LABEL = 1 << 0, /* When creating: relabel */ - XO_SUBVOLUME = 1 << 1, /* When creating as directory: make it a subvolume */ - XO_NOCOW = 1 << 2, /* Enable NOCOW mode after opening */ - XO_REGULAR = 1 << 3, /* Fail if the inode is not a regular file */ + XO_LABEL = 1 << 0, /* When creating: relabel */ + XO_SUBVOLUME = 1 << 1, /* When creating as directory: make it a subvolume */ + XO_NOCOW = 1 << 2, /* Enable NOCOW mode after opening */ + XO_REGULAR = 1 << 3, /* Fail if the inode is not a regular file */ + XO_SOCKET = 1 << 4, /* Fail if the inode is not a socket */ + XO_TRIGGER_AUTOMOUNT = 1 << 5, /* Trigger automounts via open_tree(). Requires O_PATH. */ + XO_AUTO_RW_RO = 1 << 6, /* Open in O_RDWR mode if possible, O_RDONLY if not */ } XOpenFlags; int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode); diff --git a/src/basic/gcrypt-util.c b/src/basic/gcrypt-util.c index ebbc3e33bc736..34444811ad3af 100644 --- a/src/basic/gcrypt-util.c +++ b/src/basic/gcrypt-util.c @@ -2,7 +2,10 @@ #include +#include "sd-dlopen.h" + #include "gcrypt-util.h" +#include "log.h" /* IWYU pragma: keep */ #if HAVE_GCRYPT @@ -42,16 +45,17 @@ DLSYM_PROTOTYPE(gcry_randomize) = NULL; DLSYM_PROTOTYPE(gcry_strerror) = NULL; #endif -int dlopen_gcrypt(void) { +int dlopen_gcrypt(int log_level) { #if HAVE_GCRYPT - ELF_NOTE_DLOPEN("gcrypt", + SD_ELF_NOTE_DLOPEN( + "gcrypt", "Support for journald forward-sealing", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libgcrypt.so.20"); return dlopen_many_sym_or_warn( &gcrypt_dl, - "libgcrypt.so.20", LOG_DEBUG, + "libgcrypt.so.20", log_level, DLSYM_ARG(gcry_control), DLSYM_ARG(gcry_check_version), DLSYM_ARG(gcry_md_close), @@ -85,7 +89,8 @@ int dlopen_gcrypt(void) { DLSYM_ARG(gcry_randomize), DLSYM_ARG(gcry_strerror)); #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "gcrypt support is not compiled in."); #endif } @@ -93,7 +98,7 @@ int initialize_libgcrypt(bool secmem) { #if HAVE_GCRYPT int r; - r = dlopen_gcrypt(); + r = dlopen_gcrypt(LOG_DEBUG); if (r < 0) return r; diff --git a/src/basic/gcrypt-util.h b/src/basic/gcrypt-util.h index 147a174da0026..0f45fad205b28 100644 --- a/src/basic/gcrypt-util.h +++ b/src/basic/gcrypt-util.h @@ -4,7 +4,7 @@ #include "basic-forward.h" -int dlopen_gcrypt(void); +int dlopen_gcrypt(int log_level); int initialize_libgcrypt(bool secmem); diff --git a/src/basic/glob-util.c b/src/basic/glob-util.c index a2b2edd21089c..eadfbb0221517 100644 --- a/src/basic/glob-util.c +++ b/src/basic/glob-util.c @@ -11,6 +11,9 @@ #ifndef __GLIBC__ static bool safe_glob_verify(const char *p, const char *prefix) { + POINTER_MAY_BE_NULL(p); + POINTER_MAY_BE_NULL(prefix); + if (isempty(p)) return false; /* should not happen, but for safey. */ @@ -162,6 +165,9 @@ int glob_extend(char ***strv, const char *path, int flags) { } int glob_non_glob_prefix(const char *path, char **ret) { + assert(path); + assert(ret); + /* Return the path of the path that has no glob characters. */ size_t n = strcspn(path, GLOB_CHARS); diff --git a/src/basic/gunicode.c b/src/basic/gunicode.c index 5b5e06c025359..7c33a7c87251a 100644 --- a/src/basic/gunicode.c +++ b/src/basic/gunicode.c @@ -27,6 +27,8 @@ char * utf8_prev_char (const char *p) { + assert(p); + for (;;) { p--; diff --git a/src/basic/hash-funcs.c b/src/basic/hash-funcs.c index 5c0af6fe5a326..d4adc98e945a6 100644 --- a/src/basic/hash-funcs.c +++ b/src/basic/hash-funcs.c @@ -103,10 +103,15 @@ DEFINE_HASH_OPS_FULL( void, free); void uint64_hash_func(const uint64_t *p, struct siphash *state) { + assert(p); + siphash24_compress_typesafe(*p, state); } int uint64_compare_func(const uint64_t *a, const uint64_t *b) { + assert(a); + assert(b); + return CMP(*a, *b); } @@ -119,6 +124,8 @@ DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( #if SIZEOF_DEV_T != 8 void devt_hash_func(const dev_t *p, struct siphash *state) { + assert(p); + siphash24_compress_typesafe(*p, state); } #endif @@ -126,6 +133,9 @@ void devt_hash_func(const dev_t *p, struct siphash *state) { int devt_compare_func(const dev_t *a, const dev_t *b) { int r; + assert(a); + assert(b); + r = CMP(major(*a), major(*b)); if (r != 0) return r; diff --git a/src/basic/hashmap.c b/src/basic/hashmap.c index 30c0517a57cb6..451bb0d1ec25a 100644 --- a/src/basic/hashmap.c +++ b/src/basic/hashmap.c @@ -848,6 +848,8 @@ int set_ensure_allocated(Set **s, const struct hash_ops *hash_ops) { int hashmap_ensure_put(Hashmap **h, const struct hash_ops *hash_ops, const void *key, void *value) { int r; + assert(h); + r = hashmap_ensure_allocated(h, hash_ops); if (r < 0) return r; @@ -858,6 +860,8 @@ int hashmap_ensure_put(Hashmap **h, const struct hash_ops *hash_ops, const void int ordered_hashmap_ensure_put(OrderedHashmap **h, const struct hash_ops *hash_ops, const void *key, void *value) { int r; + assert(h); + r = ordered_hashmap_ensure_allocated(h, hash_ops); if (r < 0) return r; @@ -868,6 +872,8 @@ int ordered_hashmap_ensure_put(OrderedHashmap **h, const struct hash_ops *hash_o int ordered_hashmap_ensure_replace(OrderedHashmap **h, const struct hash_ops *hash_ops, const void *key, void *value) { int r; + assert(h); + r = ordered_hashmap_ensure_allocated(h, hash_ops); if (r < 0) return r; @@ -878,6 +884,8 @@ int ordered_hashmap_ensure_replace(OrderedHashmap **h, const struct hash_ops *ha int hashmap_ensure_replace(Hashmap **h, const struct hash_ops *hash_ops, const void *key, void *value) { int r; + assert(h); + r = hashmap_ensure_allocated(h, hash_ops); if (r < 0) return r; @@ -1283,6 +1291,8 @@ int set_put(Set *s, const void *key) { int set_ensure_put(Set **s, const struct hash_ops *hash_ops, const void *key) { int r; + assert(s); + r = set_ensure_allocated(s, hash_ops); if (r < 0) return r; @@ -1843,6 +1853,8 @@ int set_consume(Set *s, void *value) { int hashmap_put_strdup_full(Hashmap **h, const struct hash_ops *hash_ops, const char *k, const char *v) { int r; + assert(h); + r = hashmap_ensure_allocated(h, hash_ops); if (r < 0) return r; @@ -2187,6 +2199,8 @@ int _hashmap_dump_keys_sorted(HashmapBase *h, void ***ret, size_t *ret_n) { size_t n; int r; + assert(ret); + r = _hashmap_dump_entries_sorted(h, &entries, &n); if (r < 0) return r; @@ -2206,6 +2220,8 @@ int _hashmap_dump_sorted(HashmapBase *h, void ***ret, size_t *ret_n) { size_t n; int r; + assert(ret); + r = _hashmap_dump_entries_sorted(h, &entries, &n); if (r < 0) return r; diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c index a00c6289cca02..bbe624cc4f2ed 100644 --- a/src/basic/hexdecoct.c +++ b/src/basic/hexdecoct.c @@ -33,8 +33,7 @@ int undecchar(char c) { } char hexchar(int x) { - static const char table[] = "0123456789abcdef"; - + const char *table = LOWERCASE_HEXDIGITS; return table[x & 15]; } @@ -165,9 +164,7 @@ int unhexmem_full( * useful when representing NSEC3 hashes, as one can then verify the * order of hashes directly from their representation. */ char base32hexchar(int x) { - static const char table[] = "0123456789" - "ABCDEFGHIJKLMNOPQRSTUV"; - + const char *table = DIGITS "ABCDEFGHIJKLMNOPQRSTUV"; return table[x & 31]; } @@ -516,9 +513,7 @@ int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *_l /* https://tools.ietf.org/html/rfc4648#section-4 */ char base64char(int x) { - static const char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; + const char *table = UPPERCASE_LETTERS LOWERCASE_LETTERS DIGITS "+/"; return table[x & 63]; } @@ -526,9 +521,7 @@ char base64char(int x) { * since we don't want "/" appear in interface names (since interfaces appear in sysfs as filenames). * See section #5 of RFC 4648. */ char urlsafe_base64char(int x) { - static const char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789-_"; + const char *table = UPPERCASE_LETTERS LOWERCASE_LETTERS DIGITS "-_"; return table[x & 63]; } diff --git a/src/basic/hmac.c b/src/basic/hmac.c index d70b874e2cd36..72cf59d468f23 100644 --- a/src/basic/hmac.c +++ b/src/basic/hmac.c @@ -3,6 +3,7 @@ #include #include "hmac.h" +#include "memory-util.h" #include "sha256.h" #define HMAC_BLOCK_SIZE 64 @@ -16,9 +17,13 @@ void hmac_sha256(const void *key, uint8_t res[static SHA256_DIGEST_SIZE]) { uint8_t inner_padding[HMAC_BLOCK_SIZE] = { }; + CLEANUP_ERASE(inner_padding); /* carries key ^ 0x36, trivially reversible to the original key */ uint8_t outer_padding[HMAC_BLOCK_SIZE] = { }; + CLEANUP_ERASE(outer_padding); /* carries key ^ 0x5c, trivially reversible to the original key */ uint8_t replacement_key[SHA256_DIGEST_SIZE]; + CLEANUP_ERASE(replacement_key); /* SHA-256 of the key when key_size > block size */ struct sha256_ctx hash; + CLEANUP_ERASE(hash); /* intermediate state derived from key material */ assert(key); assert(key_size > 0); diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index a47bdf7149121..fedf631f7d827 100644 --- a/src/basic/in-addr-util.c +++ b/src/basic/in-addr-util.c @@ -559,20 +559,18 @@ int in_addr_port_ifindex_name_to_string(int family, const union in_addr_union *u if (port > 0) { if (family == AF_INET6) { if (ifindex > 0) - r = asprintf(&x, "[%s]:%"PRIu16"%%%i%s%s", ip_str, port, ifindex, separator, server_name); + x = asprintf_safe("[%s]:%"PRIu16"%%%i%s%s", ip_str, port, ifindex, separator, server_name); else - r = asprintf(&x, "[%s]:%"PRIu16"%s%s", ip_str, port, separator, server_name); + x = asprintf_safe("[%s]:%"PRIu16"%s%s", ip_str, port, separator, server_name); } else - r = asprintf(&x, "%s:%"PRIu16"%s%s", ip_str, port, separator, server_name); + x = asprintf_safe("%s:%"PRIu16"%s%s", ip_str, port, separator, server_name); } else { if (ifindex > 0) - r = asprintf(&x, "%s%%%i%s%s", ip_str, ifindex, separator, server_name); - else { + x = asprintf_safe("%s%%%i%s%s", ip_str, ifindex, separator, server_name); + else x = strjoin(ip_str, separator, server_name); - r = x ? 0 : -ENOMEM; - } } - if (r < 0) + if (!x) return -ENOMEM; *ret = TAKE_PTR(x); @@ -843,6 +841,8 @@ int in_addr_parse_prefixlen(int family, const char *p, unsigned char *ret) { uint8_t u; int r; + assert(ret); + if (!IN_SET(family, AF_INET, AF_INET6)) return -EAFNOSUPPORT; diff --git a/src/basic/io-util.c b/src/basic/io-util.c index 0b54464cc404d..103aa2a7cde03 100644 --- a/src/basic/io-util.c +++ b/src/basic/io-util.c @@ -248,6 +248,8 @@ int fd_wait_for_event(int fd, int event, usec_t timeout) { static size_t nul_length(const uint8_t *p, size_t sz) { size_t n = 0; + assert(p); + while (sz > 0) { if (*p != 0) break; diff --git a/src/basic/iovec-util.c b/src/basic/iovec-util.c index ec8af57e6424f..dab734b9010f1 100644 --- a/src/basic/iovec-util.c +++ b/src/basic/iovec-util.c @@ -21,35 +21,39 @@ size_t iovec_total_size(const struct iovec *iovec, size_t n) { assert(iovec || n == 0); - FOREACH_ARRAY(j, iovec, n) + FOREACH_ARRAY(j, iovec, n) { + if (j->iov_len > SIZE_MAX - sum) + return SIZE_MAX; /* Indicate overflow. */ sum += j->iov_len; + } return sum; } -bool iovec_increment(struct iovec *iovec, size_t n, size_t k) { +bool iovec_inc_many(struct iovec *iovec, size_t n, size_t k) { assert(iovec || n == 0); /* Returns true if there is nothing else to send (bytes written cover all of the iovec), * false if there's still work to do. */ + bool have = false; FOREACH_ARRAY(j, iovec, n) { - size_t sub; - if (j->iov_len == 0) continue; if (k == 0) return false; - sub = MIN(j->iov_len, k); - j->iov_len -= sub; - j->iov_base = (uint8_t*) j->iov_base + sub; + size_t sub = MIN(j->iov_len, k); + iovec_inc(j, sub); k -= sub; + + have = have || iovec_is_set(j); } assert(k == 0); /* Anything else would mean that we wrote more bytes than available, * or the kernel reported writing more bytes than sent. */ - return true; + + return !have; } struct iovec* iovec_make_string(struct iovec *iovec, const char *s) { diff --git a/src/basic/iovec-util.h b/src/basic/iovec-util.h index 0d1d4a7a94d86..c8261861a0ff7 100644 --- a/src/basic/iovec-util.h +++ b/src/basic/iovec-util.h @@ -11,7 +11,7 @@ extern const struct iovec iovec_empty; /* Points to an empty, but valid (i.e. size_t iovec_total_size(const struct iovec *iovec, size_t n) _nonnull_if_nonzero_(1, 2); -bool iovec_increment(struct iovec *iovec, size_t n, size_t k) _nonnull_if_nonzero_(1, 2); +bool iovec_inc_many(struct iovec *iovec, size_t n, size_t k) _nonnull_if_nonzero_(1, 2); struct iovec* iovec_make_string(struct iovec *iovec, const char *s); @@ -24,6 +24,12 @@ struct iovec* iovec_make_string(struct iovec *iovec, const char *s); .iov_len = STRLEN(s), \ } +#define IOVEC_MAKE_BYTE(c) \ + (const struct iovec) { \ + .iov_base = (char*) ((const char[]) { c }), \ + .iov_len = 1, \ + } + void iovec_done_erase(struct iovec *iovec); char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value); @@ -32,6 +38,9 @@ char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const ch void iovec_array_free(struct iovec *iovec, size_t n_iovec) _nonnull_if_nonzero_(1, 2); int iovec_memcmp(const struct iovec *a, const struct iovec *b) _pure_; +static inline bool iovec_equal(const struct iovec *a, const struct iovec *b) { + return iovec_memcmp(a, b) == 0; +} struct iovec* iovec_memdup(const struct iovec *source, struct iovec *ret); diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index b4519f808d5e9..59b1addaaf266 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -7,10 +7,6 @@ #include "iovec-wrapper.h" #include "string-util.h" -struct iovec_wrapper *iovw_new(void) { - return new0(struct iovec_wrapper, 1); -} - void iovw_done(struct iovec_wrapper *iovw) { assert(iovw); @@ -27,20 +23,26 @@ void iovw_done_free(struct iovec_wrapper *iovw) { iovw_done(iovw); } -struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw) { - if (!iovw) - return NULL; +int iovw_compare(const struct iovec_wrapper *a, const struct iovec_wrapper *b) { + int r; - iovw_done_free(iovw); - return mfree(iovw); -} + if (a == b) + return 0; -struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw) { - if (!iovw) - return NULL; + if (!a || !b) + return CMP(!!a, !!b); - iovw_done(iovw); - return mfree(iovw); + /* Note, this performs structural (element-by-element) comparison, not content-based comparison. + * Two wrappers with identical concatenated content but different element boundaries + * (e.g., ["fo","o"] vs ["f","oo"]) will not compare as equal. */ + + for (size_t i = 0, n = MIN(a->count, b->count); i < n; i++) { + r = iovec_memcmp(a->iovec + i, b->iovec + i); + if (r != 0) + return r; + } + + return CMP(a->count, b->count); } int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) { @@ -58,7 +60,124 @@ int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) { return -ENOMEM; iovw->iovec[iovw->count++] = IOVEC_MAKE(data, len); + return 1; +} + +int iovw_put_iov(struct iovec_wrapper *iovw, const struct iovec *iov) { + assert(iovw); + + if (!iov) + return 0; + + return iovw_put(iovw, iov->iov_base, iov->iov_len); +} + +int iovw_put_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source) { + assert(iovw); + + if (iovw_isempty(source)) + return 0; + + /* We will reallocate iovw->iovec, hence the source cannot point to the same object. */ + if (iovw == source) + return -EINVAL; + + if (iovw->count > SIZE_MAX - source->count) + return -E2BIG; + if (iovw->count + source->count > IOV_MAX) + return -E2BIG; + + if (!GREEDY_REALLOC_APPEND(iovw->iovec, iovw->count, source->iovec, source->count)) + return -ENOMEM; + + return 0; +} + +int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { + /* Move data into iovw or free on error */ + int r; + + r = iovw_put(iovw, data, len); + if (r <= 0) + free(data); + + return r; +} + +int iovw_consume_iov(struct iovec_wrapper *iovw, struct iovec *iov) { + int r; + + assert(iovw); + + if (!iov) + return 0; + + r = iovw_put_iov(iovw, iov); + if (r <= 0) + iovec_done(iov); + else + /* On success, iov->iov_base is now owned by iovw. Let's emptify iov, but do not call + * iovec_done(), of course. */ + *iov = (struct iovec) {}; + + return r; +} + +int iovw_extend(struct iovec_wrapper *iovw, const void *data, size_t len) { + if (len == 0) + return 0; + + void *c = memdup(data, len); + if (!c) + return -ENOMEM; + + return iovw_consume(iovw, c, len); +} + +int iovw_extend_iov(struct iovec_wrapper *iovw, const struct iovec *iov) { + assert(iovw); + + if (!iovec_is_set(iov)) + return 0; + + return iovw_extend(iovw, iov->iov_base, iov->iov_len); +} + +int iovw_extend_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source) { + int r; + + assert(iovw); + + /* This duplicates the source and merges it into the iovw. */ + + if (iovw_isempty(source)) + return 0; + + /* iovw->iovec will be reallocated in the loop below, hence source cannot point to the same object. */ + if (iovw == source) + return -EINVAL; + + if (iovw->count > SIZE_MAX - source->count) + return -E2BIG; + if (iovw->count + source->count > IOV_MAX) + return -E2BIG; + + size_t original_count = iovw->count; + + FOREACH_ARRAY(iovec, source->iovec, source->count) { + r = iovw_extend_iov(iovw, iovec); + if (r < 0) + goto rollback; + } + return 0; + +rollback: + for (size_t i = original_count; i < iovw->count; i++) + iovec_done(iovw->iovec + i); + + iovw->count = original_count; + return r; } int iovw_put_string_field_full(struct iovec_wrapper *iovw, bool replace, const char *field, const char *value) { @@ -118,45 +237,46 @@ void iovw_rebase(struct iovec_wrapper *iovw, void *old, void *new) { } size_t iovw_size(const struct iovec_wrapper *iovw) { - if (!iovw) - return 0; + assert(iovw); return iovec_total_size(iovw->iovec, iovw->count); } -int iovw_append(struct iovec_wrapper *target, const struct iovec_wrapper *source) { - size_t original_count; - int r; +int iovw_concat(const struct iovec_wrapper *iovw, struct iovec *ret) { + assert(iovw); + assert(ret); - assert(target); + /* Squish a series of iovecs into a single iovec. */ - /* This duplicates the source and merges it into the target. */ + size_t len = iovw_size(iovw); + if (len == SIZE_MAX) + return -E2BIG; /* Prevent theoretical overflow */ - if (iovw_isempty(source)) - return 0; + /* Always allocate one more byte to make the result usable as a NUL-terminated string. */ + _cleanup_free_ uint8_t *buf = malloc(len + 1); + if (!buf) + return -ENOMEM; - original_count = target->count; + uint8_t *p = buf; + FOREACH_ARRAY(i, iovw->iovec, iovw->count) + p = mempcpy(p, i->iov_base, i->iov_len); - FOREACH_ARRAY(iovec, source->iovec, source->count) { - void *dup; + *p = 0; - dup = memdup(iovec->iov_base, iovec->iov_len); - if (!dup) { - r = -ENOMEM; - goto rollback; - } + *ret = IOVEC_MAKE(TAKE_PTR(buf), len); + return 0; +} - r = iovw_consume(target, dup, iovec->iov_len); - if (r < 0) - goto rollback; - } +char* iovw_to_cstring(const struct iovec_wrapper *iovw) { + assert(iovw); - return 0; + /* Squish a series of iovecs into a C string. Embedded NULs are not allowed. + * The caller is expected to filter them out when populating the data. */ -rollback: - for (size_t i = original_count; i < target->count; i++) - iovec_done(target->iovec + i); + _cleanup_(iovec_done) struct iovec iov = {}; + if (iovw_concat(iovw, &iov) < 0) + return NULL; - target->count = original_count; - return r; + assert(!memchr(iov.iov_base, 0, iov.iov_len)); + return TAKE_PTR(iov.iov_base); } diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index 94b39feb15250..26b7f5ad4be30 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -8,27 +8,23 @@ struct iovec_wrapper { size_t count; }; -struct iovec_wrapper *iovw_new(void); -struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw); -struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw); - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct iovec_wrapper*, iovw_free_free); - void iovw_done_free(struct iovec_wrapper *iovw); void iovw_done(struct iovec_wrapper *iovw); -int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len); -static inline int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { - /* Move data into iovw or free on error */ - int r; - - r = iovw_put(iovw, data, len); - if (r < 0) - free(data); - - return r; +int iovw_compare(const struct iovec_wrapper *a, const struct iovec_wrapper *b) _pure_; +static inline bool iovw_equal(const struct iovec_wrapper *a, const struct iovec_wrapper *b) { + return iovw_compare(a, b) == 0; } +int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len); +int iovw_put_iov(struct iovec_wrapper *iovw, const struct iovec *iov); +int iovw_put_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source); +int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len); +int iovw_consume_iov(struct iovec_wrapper *iovw, struct iovec *iov); +int iovw_extend(struct iovec_wrapper *iovw, const void *data, size_t len); +int iovw_extend_iov(struct iovec_wrapper *iovw, const struct iovec *iov); +int iovw_extend_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source); + static inline bool iovw_isempty(const struct iovec_wrapper *iovw) { return !iovw || iovw->count == 0; } @@ -46,4 +42,5 @@ int iovw_put_string_fieldf_full(struct iovec_wrapper *iovw, bool replace, const int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value); void iovw_rebase(struct iovec_wrapper *iovw, void *old, void *new); size_t iovw_size(const struct iovec_wrapper *iovw); -int iovw_append(struct iovec_wrapper *target, const struct iovec_wrapper *source); +int iovw_concat(const struct iovec_wrapper *iovw, struct iovec *ret); +char* iovw_to_cstring(const struct iovec_wrapper *iovw); diff --git a/src/basic/limits-util.c b/src/basic/limits-util.c index d48d67dbf7785..732d0c6a6f44b 100644 --- a/src/basic/limits-util.c +++ b/src/basic/limits-util.c @@ -28,7 +28,9 @@ uint64_t physical_memory(void) { assert(sc > 0); ps = page_size(); - mem = (uint64_t) sc * (uint64_t) ps; + /* Physical page count times page size cannot realistically overflow uint64_t, + * but use MUL_SAFE to make this obvious to static analyzers. */ + assert_se(MUL_SAFE(&mem, (uint64_t) sc, (uint64_t) ps)); r = cg_get_root_path(&root); if (r < 0) { diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c index 11e91ab834505..3b869d70f9747 100644 --- a/src/basic/locale-util.c +++ b/src/basic/locale-util.c @@ -16,6 +16,7 @@ #include "path-util.h" #include "process-util.h" #include "set.h" +#include "stat-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -115,8 +116,9 @@ static int add_locales_from_archive(Set *locales) { if (fstat(fd, &st) < 0) return -errno; - if (!S_ISREG(st.st_mode)) - return -EBADMSG; + r = stat_verify_regular(&st); + if (r < 0) + return r; if (st.st_size < (off_t) sizeof(struct locarhead)) return -EBADMSG; diff --git a/src/basic/log.c b/src/basic/log.c index b0b4ba5c5050c..d8b441bfadf21 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -557,7 +557,7 @@ static int write_to_syslog( if (!syslog_is_stream) break; - if (iovec_increment(iovec, ELEMENTSOF(iovec), n)) + if (iovec_inc_many(iovec, ELEMENTSOF(iovec), n)) break; } @@ -950,6 +950,9 @@ int log_format_iovec( const char *format, va_list ap) { + assert(iovec); + assert(n); + while (format && *n + 1 < iovec_len) { va_list aq; char *m; diff --git a/src/basic/login-util.c b/src/basic/login-util.c index 926e482fb0eac..7f9b84be3c180 100644 --- a/src/basic/login-util.c +++ b/src/basic/login-util.c @@ -10,7 +10,7 @@ bool session_id_valid(const char *id) { if (isempty(id)) return false; - return id[strspn(id, LETTERS DIGITS)] == '\0'; + return in_charset(id, ALPHANUMERICAL); } bool logind_running(void) { diff --git a/src/basic/macro.h b/src/basic/macro.h index 7001c331399d6..390a9fab38ca3 100644 --- a/src/basic/macro.h +++ b/src/basic/macro.h @@ -205,16 +205,3 @@ static inline size_t size_add(size_t x, size_t y) { for (typeof(entry) _va_sentinel_[1] = {}, _entries_[] = { __VA_ARGS__ __VA_OPT__(,) _va_sentinel_[0] }, *_current_ = _entries_; \ ((long)(_current_ - _entries_) < (long)(ELEMENTSOF(_entries_) - 1)) && ({ entry = *_current_; true; }); \ _current_++) - -typedef void (*void_func_t)(void); - -static inline void dispatch_void_func(void_func_t *f) { - assert(f); - assert(*f); - (*f)(); -} - -/* Inspired by Go's "defer" construct, but much more basic. This basically just calls a void function when - * the current scope is left. Doesn't do function parameters (i.e. no closures). */ -#define DEFER_VOID_CALL(x) _DEFER_VOID_CALL(UNIQ, x) -#define _DEFER_VOID_CALL(uniq, x) _unused_ _cleanup_(dispatch_void_func) void_func_t UNIQ_T(defer, uniq) = (x) diff --git a/src/basic/memory-util.c b/src/basic/memory-util.c index b39ec725a9967..e091cfb8f16f8 100644 --- a/src/basic/memory-util.c +++ b/src/basic/memory-util.c @@ -20,27 +20,6 @@ size_t page_size(void) { return pgsz; } -bool memeqbyte(uint8_t byte, const void *data, size_t length) { - /* Does the buffer consist entirely of the same specific byte value? - * Copied from https://github.com/systemd/casync/, copied in turn from - * https://github.com/rustyrussell/ccan/blob/master/ccan/mem/mem.c#L92, - * which is licensed CC-0. - */ - - const uint8_t *p = data; - - /* Check first 16 bytes manually */ - for (size_t i = 0; i < 16; i++, length--) { - if (length == 0) - return true; - if (p[i] != byte) - return false; - } - - /* Now we know first 16 bytes match, memcmp() with self. */ - return memcmp(data, p + 16, length) == 0; -} - void* memdup_reverse(const void *mem, size_t size) { assert(mem); assert(size != 0); diff --git a/src/basic/memory-util.h b/src/basic/memory-util.h index 1a609dea98bd8..f16118fbb09c2 100644 --- a/src/basic/memory-util.h +++ b/src/basic/memory-util.h @@ -57,12 +57,6 @@ static inline int memcmp_nn(const void *s1, size_t n1, const void *s2, size_t n2 #define zero(x) (memzero(&(x), sizeof(x))) -bool memeqbyte(uint8_t byte, const void *data, size_t length) _nonnull_if_nonzero_(2, 3); - -#define memeqzero(data, length) memeqbyte(0x00, data, length) - -#define eqzero(x) memeqzero(x, sizeof(x)) - static inline void* mempset(void *s, int c, size_t n) { memset(s, c, n); return (uint8_t*) s + n; diff --git a/src/basic/meson.build b/src/basic/meson.build index b8ffa80244d07..f847b175b61f0 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -99,6 +99,7 @@ basic_sources = files( 'sort-util.c', 'stat-util.c', 'static-destruct.c', + 'stdio-util.c', 'strbuf.c', 'string-table.c', 'string-util.c', @@ -210,12 +211,14 @@ libbasic_static = static_library( fundamental_sources, include_directories : basic_includes, implicit_include_directories : false, - dependencies : [libdl, + dependencies : [libbzip2_cflags, + libdl, libgcrypt_cflags, liblz4_cflags, libm, librt, libxz_cflags, + libz_cflags, libzstd_cflags, threads, userspace], diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c index 837880baa2ec2..7b353542e342f 100644 --- a/src/basic/mkdir.c +++ b/src/basic/mkdir.c @@ -46,7 +46,7 @@ int mkdirat_safe_internal( if ((flags & MKDIR_FOLLOW_SYMLINK) && S_ISLNK(st.st_mode)) { _cleanup_free_ char *p = NULL; - r = chaseat(dir_fd, path, CHASE_NONEXISTENT, &p, NULL); + r = chaseat(XAT_FDROOT, dir_fd, path, CHASE_NONEXISTENT, &p, NULL); if (r < 0) return r; if (r == 0) diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index 06d4d4450a22d..958e34bc5326f 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -84,7 +84,13 @@ int name_to_handle_at_loop( h->handle_bytes = n; if (ret_unique_mnt_id) { - uint64_t mnt_id; + /* Here, explicitly initialize mnt_id, otherwise valgrind complains: + * + * ==175708== Conditional jump or move depends on uninitialised value(s) + * ==175708== at 0x4BC33D1: inode_same_at (stat-util.c:610) + * ==175708== by 0x4BF1972: inode_same (stat-util.h:86) + */ + uint64_t mnt_id = 0; /* The kernel will still use this as uint64_t pointer */ r = name_to_handle_at(fd, path, h, (int *) &mnt_id, flags|AT_HANDLE_MNT_ID_UNIQUE); @@ -250,6 +256,7 @@ int is_mount_point_at(int dir_fd, const char *path, int flags) { at_flags_normalize_nofollow(flags) | AT_NO_AUTOMOUNT | /* don't trigger automounts – mounts are a local concept, hence no need to trigger automounts to determine STATX_ATTR_MOUNT_ROOT */ AT_STATX_DONT_SYNC, /* don't go to the network for this – for similar reasons */ + /* xstatx_flags = */ 0, STATX_TYPE|STATX_INO, /* optional_mask = */ 0, STATX_ATTR_MOUNT_ROOT, @@ -319,6 +326,8 @@ int path_get_mnt_id_at(int dir_fd, const char *path, int *ret) { uint64_t mnt_id; int r; + assert(ret); + r = path_get_mnt_id_at_internal(dir_fd, path, /* unique = */ false, &mnt_id); if (r < 0) return r; @@ -596,6 +605,9 @@ const char* mount_propagation_flag_to_string(unsigned long flags) { int mount_propagation_flag_from_string(const char *name, unsigned long *ret) { + POINTER_MAY_BE_NULL(name); + assert(ret); + if (isempty(name)) *ret = 0; else if (streq(name, "shared")) diff --git a/src/basic/namespace-util.c b/src/basic/namespace-util.c index f156bfdf24994..3f355e082f759 100644 --- a/src/basic/namespace-util.c +++ b/src/basic/namespace-util.c @@ -804,16 +804,19 @@ int process_is_owned_by_uid(const PidRef *pidref, uid_t uid) { uid_t process_uid; r = pidref_get_uid(pidref, &process_uid); if (r < 0) - return r; + return log_debug_errno(r, "Failed to get UID of process " PID_FMT ": %m", pidref->pid); if (process_uid == uid) return true; + log_debug("Process " PID_FMT " has UID " UID_FMT ", which doesn't match expected UID " UID_FMT ", checking user namespace ownership.", + pidref->pid, process_uid, uid); + _cleanup_close_ int userns_fd = -EBADF; userns_fd = pidref_namespace_open_by_type(pidref, NAMESPACE_USER); if (userns_fd == -ENOPKG) /* If userns is not supported, then they don't matter for ownership */ return false; if (userns_fd < 0) - return userns_fd; + return log_debug_errno(userns_fd, "Failed to open user namespace of process " PID_FMT ": %m", pidref->pid); for (unsigned iteration = 0;; iteration++) { uid_t ns_uid; @@ -822,14 +825,21 @@ int process_is_owned_by_uid(const PidRef *pidref, uid_t uid) { * themselves matter. */ r = is_our_namespace(userns_fd, NAMESPACE_USER); if (r < 0) - return r; - if (r > 0) + return log_debug_errno(r, "Failed to check if user namespace of process " PID_FMT " is our own (iteration %u): %m", pidref->pid, iteration); + if (r > 0) { + log_debug("User namespace of process " PID_FMT " is our own namespace (iteration %u), not owned by expected UID.", pidref->pid, iteration); return false; + } if (ioctl(userns_fd, NS_GET_OWNER_UID, &ns_uid) < 0) - return -errno; - if (ns_uid == uid) + return log_debug_errno(errno, "Failed to get owner UID of user namespace of process " PID_FMT " (iteration %u): %m", pidref->pid, iteration); + if (ns_uid == uid) { + log_debug("User namespace of process " PID_FMT " is owned by UID " UID_FMT " (iteration %u), ownership check passed.", pidref->pid, uid, iteration); return true; + } + + log_debug("User namespace of process " PID_FMT " is owned by UID " UID_FMT ", expected UID " UID_FMT " (iteration %u), going up the tree.", + pidref->pid, ns_uid, uid, iteration); /* Paranoia check */ if (iteration > 16) @@ -838,10 +848,12 @@ int process_is_owned_by_uid(const PidRef *pidref, uid_t uid) { /* Go up the tree */ _cleanup_close_ int parent_fd = ioctl(userns_fd, NS_GET_USERNS); if (parent_fd < 0) { - if (errno == EPERM) /* EPERM means we left our own userns */ + if (errno == EPERM) { /* EPERM means we left our own userns */ + log_debug("NS_GET_USERNS ioctl returned EPERM for process " PID_FMT " (iteration %u), left our own userns.", pidref->pid, iteration); return false; + } - return -errno; + return log_debug_errno(errno, "NS_GET_USERNS ioctl failed for process " PID_FMT " (iteration %u): %m", pidref->pid, iteration); } close_and_replace(userns_fd, parent_fd); diff --git a/src/basic/ordered-set.c b/src/basic/ordered-set.c index 09fdc3dfceefe..f5ed009826299 100644 --- a/src/basic/ordered-set.c +++ b/src/basic/ordered-set.c @@ -9,6 +9,8 @@ #include "strv.h" int ordered_set_ensure_allocated(OrderedSet **s, const struct hash_ops *ops) { + assert(s); + if (*s) return 0; @@ -22,6 +24,8 @@ int ordered_set_ensure_allocated(OrderedSet **s, const struct hash_ops *ops) { int ordered_set_ensure_put(OrderedSet **s, const struct hash_ops *ops, void *p) { int r; + assert(s); + r = ordered_set_ensure_allocated(s, ops); if (r < 0) return r; diff --git a/src/basic/os-util.c b/src/basic/os-util.c index 66bab1bcee9d2..06b476f1344a8 100644 --- a/src/basic/os-util.c +++ b/src/basic/os-util.c @@ -172,10 +172,10 @@ int open_os_release_at(int rfd, char **ret_path, int *ret_fd) { e = secure_getenv("SYSTEMD_OS_RELEASE"); if (e) - return chaseat(rfd, e, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd); + return chaseat(rfd, rfd, e, /* flags= */ 0, ret_path, ret_fd); FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") { - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd); + r = chaseat(rfd, rfd, path, /* flags= */ 0, ret_path, ret_fd); if (r != -ENOENT) return r; } @@ -238,7 +238,7 @@ int open_extension_release_at( return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "The extension name %s is invalid.", extension); p = strjoina(image_class_release_info[image_class].release_file_path_prefix, extension); - r = chaseat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd); + r = chaseat(rfd, rfd, p, /* flags= */ 0, ret_path, ret_fd); log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", p); if (r != -ENOENT) return r; @@ -249,7 +249,7 @@ int open_extension_release_at( * xattr is checked to ensure the author of the image considers it OK if names do not match. */ p = image_class_release_info[image_class].release_file_directory; - r = chase_and_opendirat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, &dir_path, &dir); + r = chase_and_opendirat(rfd, rfd, p, /* chase_flags= */ 0, &dir_path, &dir); if (r < 0) return log_debug_errno(r, "Cannot open %s, ignoring: %m", p); diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c index 3ccdb0001d19c..f882124a0e636 100644 --- a/src/basic/parse-util.c +++ b/src/basic/parse-util.c @@ -127,6 +127,8 @@ int parse_mtu(int family, const char *s, uint32_t *ret) { uint64_t u, m; int r; + assert(ret); + r = parse_size(s, 1024, &u); if (r < 0) return r; @@ -376,6 +378,9 @@ int parse_user_shell(const char *s, char **ret_sh, bool *ret_copy) { char *sh; int r; + assert(ret_sh); + assert(ret_copy); + if (path_is_absolute(s) && path_is_normalized(s)) { sh = strdup(s); if (!sh) @@ -476,6 +481,8 @@ int safe_atou_bounded(const char *s, unsigned min, unsigned max, unsigned *ret) unsigned v; int r; + assert(ret); + r = safe_atou(s, &v); if (r < 0) return r; @@ -577,6 +584,8 @@ int safe_atou8_full(const char *s, unsigned base, uint8_t *ret) { unsigned u; int r; + assert(ret); + r = safe_atou_full(s, base, &u); if (r < 0) return r; @@ -591,6 +600,8 @@ int safe_atou16_full(const char *s, unsigned base, uint16_t *ret) { unsigned u; int r; + assert(ret); + r = safe_atou_full(s, base, &u); if (r < 0) return r; @@ -654,6 +665,9 @@ int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) { unsigned val = 0; const char *s; + assert(p); + assert(res); + s = *p; /* accept any number of digits, strtoull is limited to 19 */ @@ -688,6 +702,8 @@ int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) { int parse_nice(const char *s, int *ret) { int n, r; + assert(ret); + r = safe_atoi(s, &n); if (r < 0) return r; @@ -703,6 +719,8 @@ int parse_ip_port(const char *s, uint16_t *ret) { uint16_t l; int r; + assert(ret); + r = safe_atou16_full(s, SAFE_ATO_REFUSE_LEADING_WHITESPACE, &l); if (r < 0) return r; @@ -719,6 +737,9 @@ int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high, bool allow unsigned l, h; int r; + assert(low); + assert(high); + r = parse_range(s, &l, &h); if (r < 0) return r; diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h index d92577c0fbeff..6df08915f639c 100644 --- a/src/basic/parse-util.h +++ b/src/basic/parse-util.h @@ -78,9 +78,13 @@ static inline int safe_atollu(const char *s, unsigned long long *ret_llu) { return safe_atollu_full(s, 0, ret_llu); } -static inline int safe_atou64(const char *s, uint64_t *ret_u) { +static inline int safe_atou64_full(const char *s, unsigned base, uint64_t *ret_u) { assert_cc(sizeof(uint64_t) == sizeof(unsigned long long)); - return safe_atollu(s, (unsigned long long*) ret_u); + return safe_atollu_full(s, base, (unsigned long long*) ret_u); +} + +static inline int safe_atou64(const char *s, uint64_t *ret_u) { + return safe_atou64_full(s, 0, ret_u); } static inline int safe_atoi64(const char *s, int64_t *ret_i) { diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 0394d26420c58..fedb347e4a461 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -17,7 +17,6 @@ #include "stat-util.h" #include "string-util.h" #include "strv.h" -#include "time-util.h" bool is_path(const char *p) { if (!p) /* A NULL pointer is definitely not a path */ @@ -588,6 +587,8 @@ char* path_extend_internal(char **x, ...) { va_list ap; bool slash; + POINTER_MAY_BE_NULL(x); + /* Joins all listed strings until the sentinel and places a "/" between them unless the strings * end/begin already with one so that it is unnecessary. Note that slashes which are already * duplicate won't be removed. The string returned is hence always equal to or longer than the sum of @@ -785,42 +786,6 @@ int find_executable_full( return last_error; } -bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) { - bool changed = false, originally_unset; - - assert(timestamp); - - if (!paths) - return false; - - originally_unset = *timestamp == 0; - - STRV_FOREACH(i, paths) { - struct stat stats; - usec_t u; - - if (stat(*i, &stats) < 0) - continue; - - u = timespec_load(&stats.st_mtim); - - /* check first */ - if (*timestamp >= u) - continue; - - log_debug(originally_unset ? "Loaded timestamp for '%s'." : "Timestamp of '%s' changed.", *i); - - /* update timestamp */ - if (update) { - *timestamp = u; - changed = true; - } else - return true; - } - - return changed; -} - static int executable_is_good(const char *executable) { _cleanup_free_ char *p = NULL, *d = NULL; int r; @@ -867,6 +832,8 @@ int fsck_exists_for_fstype(const char *fstype) { } static const char* skip_slash_or_dot(const char *p) { + POINTER_MAY_BE_NULL(p); + for (; !isempty(p); p++) { if (*p == '/') continue; @@ -970,6 +937,9 @@ int path_find_last_component(const char *path, bool accept_dot_dot, const char * const char *q, *last_end, *last_begin; size_t len; + POINTER_MAY_BE_NULL(next); + POINTER_MAY_BE_NULL(ret); + /* Similar to path_find_first_component(), but search components from the end. * * Examples @@ -1106,6 +1076,8 @@ int path_split_prefix_filename(const char *path, char **ret_dir, char **ret_file const char *c, *next = NULL; int r; + POINTER_MAY_BE_NULL(path); + /* Split the path into dir prefix/filename pair. Returns: * * -EINVAL → if the path is not valid diff --git a/src/basic/path-util.h b/src/basic/path-util.h index d70fc3b1bc686..e2e80ae91f7e7 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -105,8 +105,6 @@ static inline int find_executable(const char *name, char **ret_filename) { return find_executable_full(name, /* root= */ NULL, NULL, true, ret_filename, NULL); } -bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update); - int fsck_exists(void); int fsck_exists_for_fstype(const char *fstype); diff --git a/src/basic/prioq.c b/src/basic/prioq.c index 157c081eb45c0..94ee528556a69 100644 --- a/src/basic/prioq.c +++ b/src/basic/prioq.c @@ -168,6 +168,8 @@ int prioq_put(Prioq *q, void *data, unsigned *idx) { int _prioq_ensure_put(Prioq **q, compare_func_t compare_func, void *data, unsigned *idx) { int r; + assert(q); + r = prioq_ensure_allocated(q, compare_func); if (r < 0) return r; diff --git a/src/basic/proc-cmdline.h b/src/basic/proc-cmdline.h index 42a8ef1eb9c91..6abf57ac3c1ea 100644 --- a/src/basic/proc-cmdline.h +++ b/src/basic/proc-cmdline.h @@ -4,10 +4,15 @@ #include "basic-forward.h" typedef enum ProcCmdlineFlags { - PROC_CMDLINE_STRIP_RD_PREFIX = 1 << 0, /* automatically strip "rd." prefix if it is set (and we are in the initrd, since otherwise we'd not consider it anyway) */ - PROC_CMDLINE_VALUE_OPTIONAL = 1 << 1, /* the value is optional (for boolean switches that can omit the value) */ - PROC_CMDLINE_RD_STRICT = 1 << 2, /* ignore this in the initrd */ - PROC_CMDLINE_TRUE_WHEN_MISSING = 1 << 3, /* default to true when the key is missing for bool */ + PROC_CMDLINE_RD_STRICT = 1 << 0, /* Only look at options with the "rd." prefix when in the initrd and only + * at options without the prefix when not in the initrd. + */ + PROC_CMDLINE_STRIP_RD_PREFIX = 1 << 1, /* Automatically strip "rd." prefix if we are in the initrd. + * When this is specified, the handler function must check for unprefixed + * option names. */ + PROC_CMDLINE_VALUE_OPTIONAL = 1 << 2, /* The value is optional (for boolean switches that can omit the value). */ + PROC_CMDLINE_TRUE_WHEN_MISSING = 1 << 3, /* Make proc_cmdline_get_bool() return true instead of false (the default) + * when the key is not present on the command line. */ } ProcCmdlineFlags; typedef int (*proc_cmdline_parse_t)(const char *key, const char *value, void *data); diff --git a/src/basic/process-util.c b/src/basic/process-util.c index e3d6b3905d457..697498ea5d4b1 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -1151,6 +1151,8 @@ int safe_personality(unsigned long p) { int opinionated_personality(unsigned long *ret) { int current; + assert(ret); + /* Returns the current personality, or PERSONALITY_INVALID if we can't determine it. This function is a bit * opinionated though, and ignores all the finer-grained bits and exotic personalities, only distinguishing the * two most relevant personalities: PER_LINUX and PER_LINUX32. */ @@ -1191,6 +1193,9 @@ void valgrind_summary_hack(void) { int pid_compare_func(const pid_t *a, const pid_t *b) { /* Suitable for usage in qsort() */ + assert(a); + assert(b); + return CMP(*a, *b); } diff --git a/src/basic/psi-util.c b/src/basic/psi-util.c index df1ccbc1b20fb..f2a93e674f0d9 100644 --- a/src/basic/psi-util.c +++ b/src/basic/psi-util.c @@ -10,6 +10,7 @@ #include "fileio.h" #include "parse-util.h" #include "psi-util.h" +#include "string-table.h" #include "string-util.h" #include "strv.h" @@ -104,6 +105,32 @@ int read_resource_pressure(const char *path, PressureType type, ResourcePressure return 0; } +const PressureResourceInfo pressure_resource_info[_PRESSURE_RESOURCE_MAX] = { + [PRESSURE_MEMORY] = { + .name = "memory", + .env_watch = "MEMORY_PRESSURE_WATCH", + .env_write = "MEMORY_PRESSURE_WRITE", + }, + [PRESSURE_CPU] = { + .name = "cpu", + .env_watch = "CPU_PRESSURE_WATCH", + .env_write = "CPU_PRESSURE_WRITE", + }, + [PRESSURE_IO] = { + .name = "io", + .env_watch = "IO_PRESSURE_WATCH", + .env_write = "IO_PRESSURE_WRITE", + }, +}; + +static const char* const pressure_resource_table[_PRESSURE_RESOURCE_MAX] = { + [PRESSURE_MEMORY] = "memory", + [PRESSURE_CPU] = "cpu", + [PRESSURE_IO] = "io", +}; + +DEFINE_STRING_TABLE_LOOKUP(pressure_resource, PressureResource); + int is_pressure_supported(void) { static thread_local int cached = -1; int r; diff --git a/src/basic/psi-util.h b/src/basic/psi-util.h index f5e79960a8159..8716767ca5931 100644 --- a/src/basic/psi-util.h +++ b/src/basic/psi-util.h @@ -9,6 +9,14 @@ typedef enum PressureType { PRESSURE_TYPE_FULL, } PressureType; +typedef enum PressureResource { + PRESSURE_MEMORY, + PRESSURE_CPU, + PRESSURE_IO, + _PRESSURE_RESOURCE_MAX, + _PRESSURE_RESOURCE_INVALID = -EINVAL, +} PressureResource; + /* Averages are stored in fixed-point with 11 bit fractions */ typedef struct ResourcePressure { loadavg_t avg10; @@ -27,7 +35,23 @@ int read_resource_pressure(const char *path, PressureType type, ResourcePressure /* Was the kernel compiled with CONFIG_PSI=y? 1 if yes, 0 if not, negative on error. */ int is_pressure_supported(void); -/* Default parameters for memory pressure watch logic in sd-event and PID 1 */ -#define MEMORY_PRESSURE_DEFAULT_TYPE "some" -#define MEMORY_PRESSURE_DEFAULT_THRESHOLD_USEC (200 * USEC_PER_MSEC) -#define MEMORY_PRESSURE_DEFAULT_WINDOW_USEC (2 * USEC_PER_SEC) +/* Metadata for each pressure resource type, for use in sd-event and PID 1 */ +typedef struct PressureResourceInfo { + const char *name; /* "memory", "cpu", "io" */ + const char *env_watch; /* "MEMORY_PRESSURE_WATCH", etc. */ + const char *env_write; /* "MEMORY_PRESSURE_WRITE", etc. */ +} PressureResourceInfo; + +extern const PressureResourceInfo pressure_resource_info[_PRESSURE_RESOURCE_MAX]; + +static inline const PressureResourceInfo* pressure_resource_get_info(PressureResource resource) { + assert(resource >= 0 && resource < _PRESSURE_RESOURCE_MAX); + return &pressure_resource_info[resource]; +} + +DECLARE_STRING_TABLE_LOOKUP(pressure_resource, PressureResource); + +/* Default parameters for pressure watch logic in sd-event and PID 1 */ +#define PRESSURE_DEFAULT_TYPE "some" +#define PRESSURE_DEFAULT_THRESHOLD_USEC (200 * USEC_PER_MSEC) +#define PRESSURE_DEFAULT_WINDOW_USEC (2 * USEC_PER_SEC) diff --git a/src/basic/random-util.c b/src/basic/random-util.c index d05be6fa501ad..0a562238dd173 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -96,6 +96,19 @@ void random_bytes(void *p, size_t n) { fallback_random_bytes(p, n); } +int random_bytes_allocate_iovec(size_t n, struct iovec *ret) { + assert(ret); + + void *p = malloc(MAX(n, 1U)); + if (!p) + return -ENOMEM; + + random_bytes(p, n); + + *ret = IOVEC_MAKE(TAKE_PTR(p), n); + return 0; +} + int crypto_random_bytes(void *p, size_t n) { assert(p || n == 0); diff --git a/src/basic/random-util.h b/src/basic/random-util.h index c0ed0488e74c8..d65c472031704 100644 --- a/src/basic/random-util.h +++ b/src/basic/random-util.h @@ -3,8 +3,12 @@ #include "basic-forward.h" -void random_bytes(void *p, size_t n) _nonnull_if_nonzero_(1, 2); /* Returns random bytes suitable for most uses, but may be insecure sometimes. */ -int crypto_random_bytes(void *p, size_t n) _nonnull_if_nonzero_(1, 2); /* Returns secure random bytes after waiting for the RNG to initialize. */ +/* Returns random bytes suitable for most uses, but may be insecure sometimes. */ +void random_bytes(void *p, size_t n) _nonnull_if_nonzero_(1, 2); +int random_bytes_allocate_iovec(size_t n, struct iovec *ret); + +/* Returns secure random bytes after waiting for the RNG to initialize. */ +int crypto_random_bytes(void *p, size_t n) _nonnull_if_nonzero_(1, 2); int crypto_random_bytes_allocate_iovec(size_t n, struct iovec *ret); static inline uint64_t random_u64(void) { diff --git a/src/basic/recurse-dir.c b/src/basic/recurse-dir.c index 0efa731868e6f..8f691d922945f 100644 --- a/src/basic/recurse-dir.c +++ b/src/basic/recurse-dir.c @@ -16,6 +16,9 @@ #define DEFAULT_RECURSION_MAX 100 static int sort_func(struct dirent * const *a, struct dirent * const *b) { + assert(a); + assert(b); + return strcmp((*a)->d_name, (*b)->d_name); } @@ -41,6 +44,8 @@ int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret) { * Start with space for up to 8 directory entries. We expect at least 2 ("." + ".."), hence hopefully * 8 will cover most cases comprehensively. (Note that most likely a lot more entries will actually * fit in the buffer, given we calculate maximum file name length here.) */ + /* Silence static analyzers */ + assert_cc(offsetof(DirectoryEntries, buffer) <= SIZE_MAX - DIRENT_SIZE_MAX * 8); de = malloc(offsetof(DirectoryEntries, buffer) + DIRENT_SIZE_MAX * 8); if (!de) return -ENOMEM; @@ -50,6 +55,8 @@ int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret) { size_t bs; ssize_t n; + /* Silence static analyzers, MALLOC_SIZEOF_SAFE is at least as large as the allocation */ + assert(MALLOC_SIZEOF_SAFE(de) >= offsetof(DirectoryEntries, buffer)); bs = MIN(MALLOC_SIZEOF_SAFE(de) - offsetof(DirectoryEntries, buffer), (size_t) SSIZE_MAX); assert(bs > de->buffer_size); diff --git a/src/basic/rlimit-util.c b/src/basic/rlimit-util.c index 331f2d1a5b607..6dbc5008b18f2 100644 --- a/src/basic/rlimit-util.c +++ b/src/basic/rlimit-util.c @@ -170,6 +170,9 @@ static int rlimit_parse_nice(const char *val, rlim_t *ret) { uint64_t rl; int r; + assert(val); + assert(ret); + /* So, Linux is weird. The range for RLIMIT_NICE is 40..1, mapping to the nice levels -20..19. However, the * RLIMIT_NICE limit defaults to 0 by the kernel, i.e. a value that maps to nice level 20, which of course is * bogus and does not exist. In order to permit parsing the RLIMIT_NICE of 0 here we hence implement a slight @@ -289,26 +292,25 @@ int rlimit_parse(int resource, const char *val, struct rlimit *ret) { } int rlimit_format(const struct rlimit *rl, char **ret) { - _cleanup_free_ char *s = NULL; - int r; + char *s; assert(rl); assert(ret); if (rl->rlim_cur >= RLIM_INFINITY && rl->rlim_max >= RLIM_INFINITY) - r = free_and_strdup(&s, "infinity"); + s = strdup("infinity"); else if (rl->rlim_cur >= RLIM_INFINITY) - r = asprintf(&s, "infinity:" RLIM_FMT, rl->rlim_max); + s = asprintf_safe("infinity:" RLIM_FMT, rl->rlim_max); else if (rl->rlim_max >= RLIM_INFINITY) - r = asprintf(&s, RLIM_FMT ":infinity", rl->rlim_cur); + s = asprintf_safe(RLIM_FMT ":infinity", rl->rlim_cur); else if (rl->rlim_cur == rl->rlim_max) - r = asprintf(&s, RLIM_FMT, rl->rlim_cur); + s = asprintf_safe(RLIM_FMT, rl->rlim_cur); else - r = asprintf(&s, RLIM_FMT ":" RLIM_FMT, rl->rlim_cur, rl->rlim_max); - if (r < 0) + s = asprintf_safe(RLIM_FMT ":" RLIM_FMT, rl->rlim_cur, rl->rlim_max); + if (!s) return -ENOMEM; - *ret = TAKE_PTR(s); + *ret = s; return 0; } diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 1194eafc1b0cd..698aa69a4b0f1 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -360,6 +360,7 @@ int sockaddr_port(const struct sockaddr *_sa, unsigned *ret_port) { /* Note, this returns the port as 'unsigned' rather than 'uint16_t', as AF_VSOCK knows larger ports */ assert(sa); + assert(ret_port); switch (sa->sa.sa_family) { @@ -824,6 +825,8 @@ bool ifname_valid_full(const char *p, IfnameValidFlags flags) { bool address_label_valid(const char *p) { + POINTER_MAY_BE_NULL(p); + if (isempty(p)) return false; @@ -1150,10 +1153,8 @@ int flush_accept(int fd) { r = fd_wait_for_event(fd, POLLIN, 0); if (r == -EINTR) continue; - if (r < 0) + if (r <= 0) return r; - if (r == 0) - return 0; if (iteration >= MAX_FLUSH_ITERATIONS) return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), @@ -1558,6 +1559,8 @@ int socket_set_option(int fd, int af, int opt_ipv4, int opt_ipv6, int val) { int socket_get_mtu(int fd, int af, size_t *ret) { int mtu, r; + assert(ret); + if (af == AF_UNSPEC) { af = socket_get_family(fd); if (af < 0) @@ -1796,7 +1799,7 @@ int vsock_get_local_cid(unsigned *ret) { /* If ret == NULL, we're just want to check if AF_VSOCK is available, so accept * any address. Otherwise, filter out special addresses that are cannot be used * to identify _this_ machine from the outside. */ - if (ret && IN_SET(tmp, VMADDR_CID_LOCAL, VMADDR_CID_HOST)) + if (ret && IN_SET(tmp, VMADDR_CID_LOCAL, VMADDR_CID_HOST, VMADDR_CID_ANY)) return log_debug_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), "IOCTL_VM_SOCKETS_GET_LOCAL_CID returned special value (%u), ignoring.", tmp); diff --git a/src/basic/sort-util.c b/src/basic/sort-util.c index d848745677d9a..a76a6c85a8219 100644 --- a/src/basic/sort-util.c +++ b/src/basic/sort-util.c @@ -65,9 +65,19 @@ void qsort_r_safe(void *base, size_t nmemb, size_t size, comparison_userdata_fn_ } int cmp_int(const int *a, const int *b) { + /* This is called from qsort()s inner loops. Correctly implemented qsort will never pass NULL so we + just suppress the check via POINTER_MAY_BE_NULL instead of assert() to avoid the runtime cost. */ + POINTER_MAY_BE_NULL(a); + POINTER_MAY_BE_NULL(b); + return CMP(*a, *b); } int cmp_uint16(const uint16_t *a, const uint16_t *b) { + /* This is called from qsort()s inner loops. Correctly implemented qsort will never pass NULL so we + just suppress the check via POINTER_MAY_BE_NULL instead of assert() to avoid the runtime cost. */ + POINTER_MAY_BE_NULL(a); + POINTER_MAY_BE_NULL(b); + return CMP(*a, *b); } diff --git a/src/basic/special.h b/src/basic/special.h index a5cdfebae57be..bdf62327f73cb 100644 --- a/src/basic/special.h +++ b/src/basic/special.h @@ -97,6 +97,7 @@ #define SPECIAL_PCRFS_ROOT_SERVICE "systemd-pcrfs-root.service" #define SPECIAL_VALIDATEFS_SERVICE "systemd-validatefs@.service" #define SPECIAL_HIBERNATE_RESUME_SERVICE "systemd-hibernate-resume.service" +#define SPECIAL_CLONESETUP_TARGET "clonesetup.target" /* Services systemd relies on */ #define SPECIAL_DBUS_SERVICE "dbus.service" diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 43f82dd926809..bbfb8a9d1879b 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -49,22 +49,35 @@ static int verify_stat_at( return verify ? r : r >= 0; } +static int mode_verify_regular(mode_t mode) { + if (S_ISDIR(mode)) + return -EISDIR; + + if (S_ISLNK(mode)) + return -ELOOP; + + if (!S_ISREG(mode)) + return -EBADFD; + + return 0; +} + int stat_verify_regular(const struct stat *st) { assert(st); /* Checks whether the specified stat() structure refers to a regular file. If not returns an * appropriate error code. */ - if (S_ISDIR(st->st_mode)) - return -EISDIR; + return mode_verify_regular(st->st_mode); +} - if (S_ISLNK(st->st_mode)) - return -ELOOP; +int statx_verify_regular(const struct statx *stx) { + assert(stx); - if (!S_ISREG(st->st_mode)) - return -EBADFD; + if (!FLAGS_SET(stx->stx_mask, STATX_TYPE)) + return -ENODATA; - return 0; + return mode_verify_regular(stx->stx_mode); } int verify_regular_at(int fd, const char *path, bool follow) { @@ -78,31 +91,29 @@ int fd_verify_regular(int fd) { return verify_regular_at(fd, /* path= */ NULL, /* follow= */ false); } -int stat_verify_directory(const struct stat *st) { - assert(st); - - if (S_ISLNK(st->st_mode)) +static int mode_verify_directory(mode_t mode) { + if (S_ISLNK(mode)) return -ELOOP; - if (!S_ISDIR(st->st_mode)) + if (!S_ISDIR(mode)) return -ENOTDIR; return 0; } +int stat_verify_directory(const struct stat *st) { + assert(st); + + return mode_verify_directory(st->st_mode); +} + int statx_verify_directory(const struct statx *stx) { assert(stx); if (!FLAGS_SET(stx->stx_mask, STATX_TYPE)) return -ENODATA; - if (S_ISLNK(stx->stx_mode)) - return -ELOOP; - - if (!S_ISDIR(stx->stx_mode)) - return -ENOTDIR; - - return 0; + return mode_verify_directory(stx->stx_mode); } int fd_verify_directory(int fd) { @@ -134,6 +145,9 @@ int stat_verify_symlink(const struct stat *st) { } int fd_verify_symlink(int fd) { + if (IN_SET(fd, AT_FDCWD, XAT_FDROOT)) + return -EISDIR; + return verify_stat_at(fd, /* path= */ NULL, /* follow= */ false, stat_verify_symlink, /* verify= */ true); } @@ -142,21 +156,38 @@ int is_symlink(const char *path) { return verify_stat_at(AT_FDCWD, path, false, stat_verify_symlink, false); } -int stat_verify_socket(const struct stat *st) { - assert(st); +static int mode_verify_socket(mode_t mode) { + if (S_ISDIR(mode)) + return -EISDIR; - if (S_ISLNK(st->st_mode)) + if (S_ISLNK(mode)) return -ELOOP; - if (S_ISDIR(st->st_mode)) - return -EISDIR; - - if (!S_ISSOCK(st->st_mode)) + if (!S_ISSOCK(mode)) return -ENOTSOCK; return 0; } +int stat_verify_socket(const struct stat *st) { + assert(st); + + return mode_verify_socket(st->st_mode); +} + +int statx_verify_socket(const struct statx *stx) { + assert(stx); + + return mode_verify_socket(stx->stx_mode); +} + +int fd_verify_socket(int fd) { + if (IN_SET(fd, AT_FDCWD, XAT_FDROOT)) + return -EISDIR; + + return verify_stat_at(fd, /* path= */ NULL, /* follow= */ false, stat_verify_socket, /* verify= */ true); +} + int is_socket(const char *path) { assert(!isempty(path)); return verify_stat_at(AT_FDCWD, path, /* follow= */ true, stat_verify_socket, /* verify= */ false); @@ -179,15 +210,52 @@ int fd_verify_linked(int fd) { return verify_stat_at(fd, NULL, false, stat_verify_linked, true); } -int stat_verify_device_node(const struct stat *st) { +int stat_verify_block(const struct stat *st) { assert(st); + if (S_ISDIR(st->st_mode)) + return -EISDIR; + if (S_ISLNK(st->st_mode)) return -ELOOP; + if (!S_ISBLK(st->st_mode)) + return -ENOTBLK; + + return 0; +} + +int fd_verify_block(int fd) { + if (IN_SET(fd, AT_FDCWD, XAT_FDROOT)) + return -EISDIR; + + return verify_stat_at(fd, /* path= */ NULL, /* follow= */ false, stat_verify_block, /* verify= */ true); +} + +int stat_verify_char(const struct stat *st) { + assert(st); + if (S_ISDIR(st->st_mode)) return -EISDIR; + if (S_ISLNK(st->st_mode)) + return -ELOOP; + + if (!S_ISCHR(st->st_mode)) + return -EBADFD; + + return 0; +} + +int stat_verify_device_node(const struct stat *st) { + assert(st); + + if (S_ISDIR(st->st_mode)) + return -EISDIR; + + if (S_ISLNK(st->st_mode)) + return -ELOOP; + if (!S_ISBLK(st->st_mode) && !S_ISCHR(st->st_mode)) return -ENOTTY; @@ -199,6 +267,28 @@ int is_device_node(const char *path) { return verify_stat_at(AT_FDCWD, path, false, stat_verify_device_node, false); } +int stat_verify_regular_or_block(const struct stat *st) { + assert(st); + + if (S_ISDIR(st->st_mode)) + return -EISDIR; + + if (S_ISLNK(st->st_mode)) + return -ELOOP; + + if (!S_ISREG(st->st_mode) && !S_ISBLK(st->st_mode)) + return -EBADFD; + + return 0; +} + +int fd_verify_regular_or_block(int fd) { + if (IN_SET(fd, AT_FDCWD, XAT_FDROOT)) + return -EISDIR; + + return verify_stat_at(fd, /* path= */ NULL, /* follow= */ false, stat_verify_regular_or_block, /* verify= */ true); +} + int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup) { _cleanup_close_ int fd = -EBADF; struct dirent *buf; @@ -307,7 +397,8 @@ DEFINE_STATX_BITS_TO_STRING(statx_attributes, uint64_t, statx_attribute_to_name, int xstatx_full(int fd, const char *path, - int flags, + int statx_flags, + XStatXFlags xstatx_flags, unsigned mandatory_mask, unsigned optional_mask, uint64_t mandatory_attributes, @@ -323,10 +414,13 @@ int xstatx_full(int fd, * 3. Takes separate mandatory and optional mask params, plus mandatory attributes. * Returns -EUNATCH if statx() does not return all masks specified as mandatory, * > 0 if all optional masks are supported, 0 otherwise. + * 4. Supports a new flag XSTATX_MNT_ID_BEST which acquires STATX_MNT_ID_UNIQUE if available and + * STATX_MNT_ID if not. */ assert(fd >= 0 || IN_SET(fd, AT_FDCWD, XAT_FDROOT)); assert((mandatory_mask & optional_mask) == 0); + assert(!FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST) || !((mandatory_mask|optional_mask) & (STATX_MNT_ID|STATX_MNT_ID_UNIQUE))); assert(ret); _cleanup_free_ char *p = NULL; @@ -334,12 +428,21 @@ int xstatx_full(int fd, if (r < 0) return r; - if (statx(fd, strempty(path), - flags|(isempty(path) ? AT_EMPTY_PATH : 0), - mandatory_mask|optional_mask, + unsigned request_mask = mandatory_mask|optional_mask; + if (FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST)) + request_mask |= STATX_MNT_ID|STATX_MNT_ID_UNIQUE; + + if (statx(fd, + strempty(path), + statx_flags|(isempty(path) ? AT_EMPTY_PATH : 0), + request_mask, &sx) < 0) return negative_errno(); + if (FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST) && + !(sx.stx_mask & (STATX_MNT_ID|STATX_MNT_ID_UNIQUE))) + return log_debug_errno(SYNTHETIC_ERRNO(EUNATCH), "statx() did not return either STATX_MNT_ID or STATX_MNT_ID_UNIQUE."); + if (!FLAGS_SET(sx.stx_mask, mandatory_mask)) { if (DEBUG_LOGGING) { _cleanup_free_ char *mask_str = statx_mask_to_string(mandatory_mask & ~sx.stx_mask); @@ -482,15 +585,17 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl goto fallback; } - if (r == 0) + bool have_unique_mntid = r > 0; + + if (!have_unique_mntid) mntida = _mntida; r = name_to_handle_at_try_fid( fdb, fileb, &hb, - r > 0 ? NULL : &_mntidb, /* if we managed to get unique mnt id for a, insist on that for b */ - r > 0 ? &mntidb : NULL, + have_unique_mntid ? NULL : &_mntidb, /* if we managed to get unique mnt id for a, insist on that for b */ + have_unique_mntid ? &mntidb : NULL, ntha_flags); if (r < 0) { if (is_name_to_handle_at_fatal_error(r)) @@ -498,8 +603,10 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl goto fallback; } - if (r == 0) + if (r == 0) { + assert(!have_unique_mntid); /* _mntidb was initialized by name_to_handle_at_try_fid() */ mntidb = _mntidb; + } /* Now compare the two file handles */ if (!file_handle_equal(ha, hb)) @@ -654,14 +761,15 @@ bool statx_inode_same(const struct statx *a, const struct statx *b) { a->stx_ino == b->stx_ino; } -bool statx_mount_same(const struct statx *a, const struct statx *b) { +int statx_mount_same(const struct statx *a, const struct statx *b) { if (!statx_is_set(a) || !statx_is_set(b)) return false; - assert(FLAGS_SET(a->stx_mask, STATX_MNT_ID)); - assert(FLAGS_SET(b->stx_mask, STATX_MNT_ID)); + if ((FLAGS_SET(a->stx_mask, STATX_MNT_ID) && FLAGS_SET(b->stx_mask, STATX_MNT_ID)) || + (FLAGS_SET(a->stx_mask, STATX_MNT_ID_UNIQUE) && FLAGS_SET(b->stx_mask, STATX_MNT_ID_UNIQUE))) + return a->stx_mnt_id == b->stx_mnt_id; - return a->stx_mnt_id == b->stx_mnt_id; + return -ENODATA; } usec_t statx_timestamp_load(const struct statx_timestamp *ts) { @@ -674,6 +782,10 @@ nsec_t statx_timestamp_load_nsec(const struct statx_timestamp *ts) { void inode_hash_func(const struct stat *q, struct siphash *state) { siphash24_compress_typesafe(q->st_dev, state); siphash24_compress_typesafe(q->st_ino, state); + + /* Also include inode type, to mirror stat_inode_same() */ + mode_t type = q->st_mode & S_IFMT; + siphash24_compress_typesafe(type, state); } int inode_compare_func(const struct stat *a, const struct stat *b) { @@ -683,11 +795,68 @@ int inode_compare_func(const struct stat *a, const struct stat *b) { if (r != 0) return r; - return CMP(a->st_ino, b->st_ino); + r = CMP(a->st_ino, b->st_ino); + if (r != 0) + return r; + + return CMP(a->st_mode & S_IFMT, b->st_mode & S_IFMT); } DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free); +void inode_unmodified_hash_func(const struct stat *q, struct siphash *state) { + inode_hash_func(q, state); + + siphash24_compress_typesafe(q->st_mtim.tv_sec, state); + siphash24_compress_typesafe(q->st_mtim.tv_nsec, state); + + if (S_ISREG(q->st_mode)) + siphash24_compress_typesafe(q->st_size, state); + else { + uint64_t invalid = UINT64_MAX; + siphash24_compress_typesafe(invalid, state); + } + + if (S_ISCHR(q->st_mode) || S_ISBLK(q->st_mode)) + siphash24_compress_typesafe(q->st_rdev, state); + else { + dev_t invalid = (dev_t) -1; + siphash24_compress_typesafe(invalid, state); + } +} + +int inode_unmodified_compare_func(const struct stat *a, const struct stat *b) { + int r; + + r = inode_compare_func(a, b); + if (r != 0) + return r; + + r = CMP(a->st_mtim.tv_sec, b->st_mtim.tv_sec); + if (r != 0) + return r; + + r = CMP(a->st_mtim.tv_nsec, b->st_mtim.tv_nsec); + if (r != 0) + return r; + + if (S_ISREG(a->st_mode)) { + r = CMP(a->st_size, b->st_size); + if (r != 0) + return r; + } + + if (S_ISCHR(a->st_mode) || S_ISBLK(a->st_mode)) { + r = CMP(a->st_rdev, b->st_rdev); + if (r != 0) + return r; + } + + return 0; +} + +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_unmodified_hash_ops, struct stat, inode_unmodified_hash_func, inode_unmodified_compare_func, free); + const char* inode_type_to_string(mode_t m) { /* Returns a short string for the inode type. We use the same name as the underlying macros for each @@ -736,3 +905,20 @@ mode_t inode_type_from_string(const char *s) { return MODE_INVALID; } + +int vfs_free_bytes(int fd, uint64_t *ret) { + assert(fd >= 0); + assert(ret); + + /* Safely returns the current available disk space (for root, i.e. including any space reserved for + * root) of the disk referenced by the fd, converted to bytes. */ + + struct statvfs sv; + if (fstatvfs(fd, &sv) < 0) + return -errno; + + if (!MUL_SAFE(ret, (uint64_t) sv.f_frsize, (uint64_t) sv.f_bfree)) + return -ERANGE; + + return 0; +} diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 0e4bec513b1ad..267d6ed7b410c 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -7,6 +7,7 @@ #include "basic-forward.h" int stat_verify_regular(const struct stat *st); +int statx_verify_regular(const struct statx *stx); int verify_regular_at(int fd, const char *path, bool follow); int fd_verify_regular(int fd); @@ -21,14 +22,24 @@ int fd_verify_symlink(int fd); int is_symlink(const char *path); int stat_verify_socket(const struct stat *st); +int statx_verify_socket(const struct statx *stx); +int fd_verify_socket(int fd); int is_socket(const char *path); int stat_verify_linked(const struct stat *st); int fd_verify_linked(int fd); +int stat_verify_block(const struct stat *st); +int fd_verify_block(int fd); + +int stat_verify_char(const struct stat *st); + int stat_verify_device_node(const struct stat *st); int is_device_node(const char *path); +int stat_verify_regular_or_block(const struct stat *st); +int fd_verify_regular_or_block(int fd); + int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup); static inline int dir_is_empty(const char *path, bool ignore_hidden_or_backup) { return dir_is_empty_at(AT_FDCWD, path, ignore_hidden_or_backup); @@ -45,9 +56,14 @@ static inline int null_or_empty_path(const char *fn) { return null_or_empty_path_with_root(fn, NULL); } +typedef enum XStatXFlags { + XSTATX_MNT_ID_BEST = 1 << 0, /* Like STATX_MNT_ID_UNIQUE if available, STATX_MNT_ID otherwise */ +} XStatXFlags; + int xstatx_full(int fd, const char *path, - int flags, + int statx_flags, + XStatXFlags xstatx_flags, unsigned mandatory_mask, unsigned optional_mask, uint64_t mandatory_attributes, @@ -56,11 +72,11 @@ int xstatx_full(int fd, static inline int xstatx( int fd, const char *path, - int flags, + int statx_flags, unsigned mandatory_mask, struct statx *ret) { - return xstatx_full(fd, path, flags, mandatory_mask, 0, 0, ret); + return xstatx_full(fd, path, statx_flags, 0, mandatory_mask, 0, 0, ret); } int fd_is_read_only_fs(int fd); @@ -108,17 +124,24 @@ bool stat_inode_same(const struct stat *a, const struct stat *b); bool stat_inode_unmodified(const struct stat *a, const struct stat *b); bool statx_inode_same(const struct statx *a, const struct statx *b); -bool statx_mount_same(const struct statx *a, const struct statx *b); +int statx_mount_same(const struct statx *a, const struct statx *b); int xstatfsat(int dir_fd, const char *path, struct statfs *ret); usec_t statx_timestamp_load(const struct statx_timestamp *ts) _pure_; nsec_t statx_timestamp_load_nsec(const struct statx_timestamp *ts) _pure_; +/* This compares inode number, backing device and inode type, but not modification info */ void inode_hash_func(const struct stat *q, struct siphash *state); int inode_compare_func(const struct stat *a, const struct stat *b); extern const struct hash_ops inode_hash_ops; +/* This is a more thorough version of the above, and also checks the mtimes, the size, and the rdev. It does + * not check "external" attributes such as access mode or ownership. */ +void inode_unmodified_hash_func(const struct stat *q, struct siphash *state); +int inode_unmodified_compare_func(const struct stat *a, const struct stat *b); +extern const struct hash_ops inode_unmodified_hash_ops; + DECLARE_STRING_TABLE_LOOKUP(inode_type, mode_t); /* Macros that check whether the stat/statx structures have been initialized already. For "struct stat" we @@ -139,3 +162,5 @@ static inline bool inode_type_can_hardlink(mode_t m) { * type). */ return IN_SET(m & S_IFMT, S_IFSOCK, S_IFLNK, S_IFREG, S_IFBLK, S_IFCHR, S_IFIFO); } + +int vfs_free_bytes(int fd, uint64_t *ret); diff --git a/src/basic/static-destruct.h b/src/basic/static-destruct.h index 5772d24240f88..00087ad779e0a 100644 --- a/src/basic/static-destruct.h +++ b/src/basic/static-destruct.h @@ -12,8 +12,6 @@ typedef void (*free_func_t)(void *p); * variables declared in .so's, as the list is private to the same linking unit. But maybe that's a good thing. */ #define _common_static_destruct_attrs_ \ - /* Older compilers don't know "retain" attribute. */ \ - _Pragma("GCC diagnostic ignored \"-Wattributes\"") \ /* The actual destructor structure we place in a special section to find it. */ \ _section_("SYSTEMD_STATIC_DESTRUCT") \ /* Use pointer alignment, since that is apparently what gcc does for static variables. */ \ diff --git a/src/basic/stdio-util.c b/src/basic/stdio-util.c new file mode 100644 index 0000000000000..53267f08e2368 --- /dev/null +++ b/src/basic/stdio-util.c @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "stdio-util.h" + +char* asprintf_safe(const char *restrict fmt, ...) { + _cleanup_free_ char *buf = NULL; + va_list ap; + int r; + + va_start(ap, fmt); + r = vasprintf(&buf, fmt, ap); + va_end(ap); + + if (r < 0) + return NULL; + return TAKE_PTR(buf); +} diff --git a/src/basic/stdio-util.h b/src/basic/stdio-util.h index f8ab0f0012fb7..f8055b3853bcc 100644 --- a/src/basic/stdio-util.h +++ b/src/basic/stdio-util.h @@ -21,6 +21,8 @@ static inline char* snprintf_ok(char *buf, size_t len, const char *format, ...) #define xsprintf(buf, fmt, ...) \ assert_message_se(snprintf_ok(buf, ELEMENTSOF(buf), fmt, ##__VA_ARGS__), "xsprintf: buffer too small") +char* asprintf_safe(const char *restrict fmt, ...) _printf_(1, 2); + #define VA_FORMAT_ADVANCE(format, ap) \ do { \ int _argtypes[128]; \ diff --git a/src/basic/string-table.c b/src/basic/string-table.c index 069cb40ec1a0d..66cf4bae1146e 100644 --- a/src/basic/string-table.c +++ b/src/basic/string-table.c @@ -3,6 +3,7 @@ #include #include "parse-util.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" @@ -40,14 +41,16 @@ ssize_t string_table_lookup_from_string_with_boolean(const char * const *table, int string_table_lookup_to_string_fallback(const char * const *table, size_t len, ssize_t i, size_t max, char **ret) { char *s; + assert(ret); + if (i < 0 || i > (ssize_t) max) return -ERANGE; - if (i < (ssize_t) len && table[i]) { + if (i < (ssize_t) len && table[i]) s = strdup(table[i]); - if (!s) - return -ENOMEM; - } else if (asprintf(&s, "%zd", i) < 0) + else + s = asprintf_safe("%zd", i); + if (!s) return -ENOMEM; *ret = s; diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 055e571afb680..4d930839232d7 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -174,6 +174,9 @@ char* ascii_strlower_n(char *s, size_t n) { int ascii_strcasecmp_n(const char *a, const char *b, size_t n) { + assert(a); + assert(b); + for (; n > 0; a++, b++, n--) { int x, y; @@ -276,6 +279,10 @@ static bool string_has_ansi_sequence(const char *s, size_t len) { } static size_t previous_ansi_sequence(const char *s, size_t length, const char **ret_where) { + + assert(s); + assert(ret_where); + /* Locate the previous ANSI sequence and save its start in *ret_where and return length. */ for (size_t i = length - 2; i > 0; i--) { /* -2 because at least two bytes are needed */ @@ -660,6 +667,7 @@ char* strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { assert(ibuf); assert(*ibuf); + POINTER_MAY_BE_NULL(_isz); /* This does three things: * @@ -969,22 +977,27 @@ int strextendf_with_separator(char **x, const char *separator, const char *forma return -ENOMEM; } -char* strrep(const char *s, unsigned n) { - char *r, *p; +char* strrep(const char *s, size_t n) { + char *ret, *p; size_t l; assert(s); l = strlen(s); - p = r = malloc(l * n + 1); - if (!r) + if (!MUL_ASSIGN_SAFE(&l, n)) + return NULL; + if (!INC_SAFE(&l, 1)) + return NULL; + + p = ret = malloc(l); + if (!ret) return NULL; - for (unsigned i = 0; i < n; i++) + for (size_t i = 0; i < n; i++) p = stpcpy(p, s); *p = 0; - return r; + return ret; } int split_pair(const char *s, const char *sep, char **ret_first, char **ret_second) { @@ -1086,25 +1099,40 @@ int strdup_to_full(char **ret, const char *src) { } }; -bool string_is_safe(const char *p) { - if (!p) +bool string_is_safe(const char *p, StringSafeFlags flags) { + + /* Baseline checks are: + * • No control characters (i.e. 0…31 + 127) + * • UTF-8 valid (well, technically we skip this test if STRING_ASCII is set, since that is a tighter test) + */ + + if (FLAGS_SET(flags, STRING_ALLOW_EMPTY) ? !p : isempty(p)) return false; - /* Checks if the specified string contains no quotes or control characters */ + if (!FLAGS_SET(flags, STRING_ASCII) && !utf8_is_valid(p)) + return false; for (const char *t = p; *t; t++) { - if (*t > 0 && *t < ' ') /* no control characters */ + if ((*t > 0 && *t < ' ') || *t == 0x7f) /* never allow control characters */ return false; - if (strchr(QUOTES "\\\x7f", *t)) + if (!FLAGS_SET(flags, STRING_ALLOW_BACKSLASHES) && *t == '\\') + return false; + + if (!FLAGS_SET(flags, STRING_ALLOW_QUOTES) && strchr(QUOTES, *t)) + return false; + + if (!FLAGS_SET(flags, STRING_ALLOW_GLOBS) && strchr(GLOB_CHARS, *t)) + return false; + + if (FLAGS_SET(flags, STRING_ASCII) && (uint8_t) *t >= 0x80) return false; } - return true; -} + if (FLAGS_SET(flags, STRING_FILENAME) && !filename_is_valid(p)) + return false; -bool string_is_safe_ascii(const char *p) { - return ascii_is_valid(p) && string_is_safe(p); + return true; } char* str_realloc(char *p) { @@ -1133,6 +1161,7 @@ int string_truncate_lines(const char *s, size_t n_lines, char **ret) { size_t n = 0; assert(s); + assert(ret); /* Truncate after the specified number of lines. Returns > 0 if a truncation was applied or == 0 if * there were fewer lines in the string anyway. Trailing newlines on input are ignored, and not @@ -1187,6 +1216,8 @@ int string_extract_line(const char *s, size_t i, char **ret) { const char *p = s; size_t c = 0; + assert(ret); + /* Extract the i'nth line from the specified string. Returns > 0 if there are more lines after that, * and == 0 if we are looking at the last line or already beyond the last line. As special * optimization, if the first line is requested and the string only consists of one line we return @@ -1276,7 +1307,7 @@ char* string_replace_char(char *str, char old_char, char new_char) { return str; } -int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret) { +int make_cstring(const void *s, size_t n, MakeCStringMode mode, char **ret) { char *b; assert(s || n == 0); @@ -1295,11 +1326,11 @@ int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret) { b = new0(char, 1); } else { - const char *nul; + const uint8_t *nul; nul = memchr(s, 0, n); if (nul) { - if (nul < s + n - 1 || /* embedded NUL? */ + if (nul < (const uint8_t*) s + n - 1 || /* embedded NUL? */ mode == MAKE_CSTRING_REFUSE_TRAILING_NUL) return -EINVAL; @@ -1503,17 +1534,32 @@ char* strrstr_internal(const char *haystack, const char *needle) { /* Special case: for the empty string we return the very last possible occurrence, i.e. *after* the * last char, not before. */ - if (*needle == 0) + if (needle[0] == 0) return (char*) strchr(haystack, 0); + /* Special case: for single character strings, just use optimized strrchr() */ + if (needle[1] == 0) + return (char*) strrchr(haystack, needle[0]); + for (const char *p = strstr(haystack, needle), *q; p; p = q) { q = strstr(p + 1, needle); if (!q) - return (char *) p; + return (char*) p; } return NULL; } +char* strrstr_no_case_internal(const char *haystack, const char *needle) { + if (!haystack || !needle) + return NULL; + + for (const char *p = strchr(haystack, 0); p > haystack; p--) + if (startswith_no_case(p, needle)) + return (char*) p; + + return startswith_no_case(haystack, needle) ? (char*) haystack : NULL; +} + size_t str_common_prefix(const char *a, const char *b) { assert(a); assert(b); diff --git a/src/basic/string-util.h b/src/basic/string-util.h index 0143c37a656e2..17eaf5d9a6a82 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -193,7 +193,7 @@ int strextendf_with_separator(char **x, const char *separator, const char *forma }) #define strprepend(x, ...) strprepend_with_separator(x, NULL, __VA_ARGS__) -char* strrep(const char *s, unsigned n); +char* strrep(const char *s, size_t n); #define strrepa(s, n) \ ({ \ @@ -220,8 +220,16 @@ static inline int strdup_to(char **ret, const char *src) { return r < 0 ? r : 0; /* Suppress return value of 1. */ } -bool string_is_safe(const char *p) _pure_; -bool string_is_safe_ascii(const char *p) _pure_; +typedef enum StringSafeFlags { + STRING_ASCII = 1 << 0, /* Verify string is 7-Bit ASCII (rather than just UTF-8) */ + STRING_ALLOW_EMPTY = 1 << 1, /* Allow empty strings */ + STRING_ALLOW_BACKSLASHES = 1 << 2, /* Allow backslashes (\) */ + STRING_ALLOW_QUOTES = 1 << 3, /* Allow quotes (" or ') */ + STRING_ALLOW_GLOBS = 1 << 4, /* Allow globs (?, * or [) */ + STRING_FILENAME = 1 << 5, /* Verify the string is valid as regular filename */ +} StringSafeFlags; + +bool string_is_safe(const char *p, StringSafeFlags flags) _pure_; DISABLE_WARNING_STRINGOP_TRUNCATION; static inline void strncpy_exact(char *buf, const char *src, size_t buf_len) { @@ -271,7 +279,7 @@ typedef enum MakeCStringMode { _MAKE_CSTRING_MODE_INVALID = -1, } MakeCStringMode; -int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret); +int make_cstring(const void *s, size_t n, MakeCStringMode mode, char **ret); size_t strspn_from_end(const char *str, const char *accept) _pure_; @@ -312,4 +320,8 @@ char* strrstr_internal(const char *haystack, const char *needle) _pure_; #define strrstr(haystack, needle) \ const_generic(haystack, strrstr_internal(haystack, needle)) +char* strrstr_no_case_internal(const char *haystack, const char *needle) _pure_; +#define strrstr_no_case(haystack, needle) \ + const_generic(haystack, strrstr_no_case_internal(haystack, needle)) + size_t str_common_prefix(const char *a, const char *b) _pure_; diff --git a/src/basic/strv.c b/src/basic/strv.c index 6cbc9633ae175..4044a129c60e7 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -587,6 +587,9 @@ int strv_push_with_size(char ***l, size_t *n, char *value) { * If n is not NULL, the size after the push will be returned. * If value is empty, no action is taken and *n is not set. */ + assert(l); + POINTER_MAY_BE_NULL(n); + if (!value) return 0; @@ -615,6 +618,8 @@ int strv_push_pair(char ***l, char *a, char *b) { char **c; size_t n; + assert(l); + if (!a && !b) return 0; @@ -828,6 +833,9 @@ char** strv_remove(char **l, const char *s) { } bool strv_overlap(char * const *a, char * const *b) { + POINTER_MAY_BE_NULL(a); + POINTER_MAY_BE_NULL(b); + STRV_FOREACH(i, a) if (strv_contains(b, *i)) return true; @@ -836,6 +844,11 @@ bool strv_overlap(char * const *a, char * const *b) { } static int str_compare(char * const *a, char * const *b) { + /* This is called from qsort()s inner loops. Correctly implemented qsort will never pass NULL so we + just suppress the check via POINTER_MAY_BE_NULL instead of assert() to avoid the runtime cost. */ + POINTER_MAY_BE_NULL(a); + POINTER_MAY_BE_NULL(b); + return strcmp(*a, *b); } @@ -862,6 +875,9 @@ char** strv_sort_uniq(char **l) { int strv_compare(char * const *a, char * const *b) { int r; + POINTER_MAY_BE_NULL(a); + POINTER_MAY_BE_NULL(b); + if (strv_isempty(a)) { if (strv_isempty(b)) return 0; diff --git a/src/basic/strv.h b/src/basic/strv.h index 7249d8a311767..d7c4b2bcf08ed 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -104,10 +104,6 @@ static inline const char* STRV_IFNOTNULL(const char *x) { return x ?: STRV_IGNORE; } -static inline bool strv_isempty(char * const *l) { - return !l || !*l; -} - int strv_split_full(char ***t, const char *s, const char *separators, ExtractFlags flags); char** strv_split(const char *s, const char *separators); diff --git a/src/basic/strxcpyx.c b/src/basic/strxcpyx.c index dc40d620e7e6b..f8410f7d0c11d 100644 --- a/src/basic/strxcpyx.c +++ b/src/basic/strxcpyx.c @@ -64,6 +64,8 @@ size_t strpcpyf_full(char **dest, size_t size, bool *ret_truncated, const char * i = vsnprintf(*dest, size, src, va); va_end(va); + assert(i >= 0); + if (i < (int) size) { *dest += i; size -= i; diff --git a/src/basic/syslog-util.c b/src/basic/syslog-util.c index fd910ca76d450..bd1bb65282332 100644 --- a/src/basic/syslog-util.c +++ b/src/basic/syslog-util.c @@ -4,9 +4,7 @@ #include "sd-id128.h" -#include "glob-util.h" #include "hexdecoct.h" -#include "path-util.h" #include "string-table.h" #include "string-util.h" #include "syslog-util.h" @@ -111,7 +109,8 @@ bool log_namespace_name_valid(const char *s) { * (so that /var/log/journal/. can be created based on it). Also make sure it * is suitable as unit instance name, and does not contain fishy characters. */ - if (!filename_is_valid(s)) + /* Let's avoid globbing for now */ + if (!string_is_safe(s, STRING_FILENAME)) return false; if (strlen(s) > LOG_NAMESPACE_MAX) @@ -120,12 +119,5 @@ bool log_namespace_name_valid(const char *s) { if (!unit_instance_is_valid(s)) return false; - if (!string_is_safe(s)) - return false; - - /* Let's avoid globbing for now */ - if (string_is_glob(s)) - return false; - return true; } diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 732ab0824d4d5..e5e66a1864777 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -44,8 +44,8 @@ #include "time-util.h" #include "utf8.h" -/* How much to wait for a reply to a terminal sequence */ -#define CONSOLE_REPLY_WAIT_USEC (333 * USEC_PER_MSEC) +/* How much to wait when reading/writing ANSI sequences from/to the console */ +#define CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC (333 * USEC_PER_MSEC) static volatile unsigned cached_columns = 0; static volatile unsigned cached_lines = 0; @@ -309,13 +309,17 @@ int ask_string_full( * swapping out stdin/stdout. */ int fd_input = fileno(stdin); int fd_output = fileno(stdout); + struct termios old_termios = TERMIOS_NULL; + CLEANUP_TERMIOS_RESET(fd_input, old_termios); + if (fd_input < 0 || fd_output < 0 || same_fd(fd_input, fd_output) <= 0) goto fallback; /* Try to disable echo, which also tells us if this even is a terminal */ - struct termios old_termios; - if (tcgetattr(fd_input, &old_termios) < 0) + if (tcgetattr(fd_input, &old_termios) < 0) { + old_termios = TERMIOS_NULL; goto fallback; + } struct termios new_termios = old_termios; termios_disable_echo(&new_termios); @@ -342,15 +346,13 @@ int ask_string_full( if (get_completions) { r = get_completions(string, &completions, userdata); if (r < 0) - goto fail; + return r; } _cleanup_free_ char *new_string = NULL; CompletionResult cr = pick_completion(string, completions, &new_string); - if (cr < 0) { - r = cr; - goto fail; - } + if (cr < 0) + return cr; if (IN_SET(cr, COMPLETION_PARTIAL, COMPLETION_FULL)) { /* Output the new suffix we learned */ fputs(ASSERT_PTR(startswith(new_string, strempty(string))), stdout); @@ -369,10 +371,8 @@ int ask_string_full( fputc('\n', stdout); _cleanup_strv_free_ char **filtered = strv_filter_prefix(completions, string); - if (!filtered) { - r = -ENOMEM; - goto fail; - } + if (!filtered) + return -ENOMEM; r = show_menu(filtered, /* n_columns= */ SIZE_MAX, @@ -381,7 +381,7 @@ int ask_string_full( /* grey_prefix= */ string, /* with_numbers= */ false); if (r < 0) - goto fail; + return r; /* Show the prompt again */ fputs(ansi_highlight(), stdout); @@ -419,8 +419,7 @@ int ask_string_full( } else if (c == 4) { /* Ctrl-d → cancel this field input */ - r = -ECANCELED; - goto fail; + return -ECANCELED; } else if (char_is_cc(c) || n >= LINE_MAX) /* refuse control characters and too long strings */ @@ -428,10 +427,8 @@ int ask_string_full( else { /* Regular char */ - if (!GREEDY_REALLOC(string, n+2)) { - r = -ENOMEM; - goto fail; - } + if (!GREEDY_REALLOC(string, n+2)) + return -ENOMEM; string[n++] = (char) c; string[n] = 0; @@ -442,9 +439,6 @@ int ask_string_full( fflush(stdout); } - if (tcsetattr(fd_input, TCSANOW, &old_termios) < 0) - return -errno; - if (!string) { string = strdup(""); if (!string) @@ -454,10 +448,6 @@ int ask_string_full( *ret = TAKE_PTR(string); return 0; -fail: - (void) tcsetattr(fd_input, TCSANOW, &old_termios); - return r; - fallback: /* A simple fallback without TTY magic */ r = read_line(stdin, LONG_LINE_MAX, &string); @@ -858,7 +848,7 @@ int vt_disallocate(const char *tty_path) { "\033[3J" /* clear screen including scrollback, requires Linux 2.6.40 */ "\033c", /* reset to initial state */ SIZE_MAX, - 100 * USEC_PER_MSEC); + CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); } static int vt_default_utf8(void) { @@ -957,7 +947,8 @@ static int terminal_reset_ioctl(int fd, bool switch_to_text) { } int terminal_reset_ansi_seq(int fd) { - int r, k; + _cleanup_(nonblock_resetp) int nonblock_reset = -EBADF; + int r; assert(fd >= 0); @@ -967,8 +958,10 @@ int terminal_reset_ansi_seq(int fd) { r = fd_nonblock(fd, true); if (r < 0) return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m"); + if (r > 0) + nonblock_reset = fd; - k = loop_write_full(fd, + r = loop_write_full(fd, "\033[!p" /* soft terminal reset */ ANSI_OSC "104" ANSI_ST /* reset color palette via OSC 104 */ ANSI_NORMAL /* reset colors */ @@ -976,17 +969,11 @@ int terminal_reset_ansi_seq(int fd) { "\033[1G" /* place cursor at beginning of current line */ "\033[0J", /* erase till end of screen */ SIZE_MAX, - 100 * USEC_PER_MSEC); - if (k < 0) - log_debug_errno(k, "Failed to reset terminal through ANSI sequences: %m"); - - if (r > 0) { - r = fd_nonblock(fd, false); - if (r < 0) - log_debug_errno(r, "Failed to set terminal back to blocking mode: %m"); - } + CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); + if (r < 0) + log_debug_errno(r, "Failed to reset terminal through ANSI sequences: %m"); - return k < 0 ? k : r; + return r; } void reset_dev_console_fd(int fd, bool switch_to_text) { @@ -1476,6 +1463,8 @@ int getttyname_harder(int fd, char **ret) { _cleanup_free_ char *s = NULL; int r; + assert(ret); + r = getttyname_malloc(fd, &s); if (r < 0) return r; @@ -1726,6 +1715,16 @@ static bool on_dev_null(void) { return cached_on_dev_null; } +bool term_env_valid(const char *term) { + /* Checks if the specified $TERM value is suitable for propagation, i.e. is not empty, not set to + * "unknown" (as is common in CI), and only contains characters valid in terminal type names. + * Valid $TERM values are things like "xterm-256color", "linux", "screen.xterm-256color", i.e. + * alphanumeric characters, hyphens, underscores, dots, and plus signs. */ + return !isempty(term) && + !streq(term, "unknown") && + in_charset(term, ALPHANUMERICAL "-_+."); +} + bool getenv_terminal_is_dumb(void) { const char *e; @@ -1864,6 +1863,8 @@ int terminal_set_cursor_position(int fd, unsigned row, unsigned column) { } static int terminal_verify_same(int input_fd, int output_fd) { + int r; + assert(input_fd >= 0); assert(output_fd >= 0); @@ -1874,15 +1875,17 @@ static int terminal_verify_same(int input_fd, int output_fd) { if (fstat(input_fd, &sti) < 0) return -errno; - if (!S_ISCHR(sti.st_mode)) /* TTYs are character devices */ - return -ENOTTY; + r = stat_verify_char(&sti); /* TTYs are character devices */ + if (r < 0) + return r; struct stat sto; if (fstat(output_fd, &sto) < 0) return -errno; - if (!S_ISCHR(sto.st_mode)) - return -ENOTTY; + r = stat_verify_char(&sto); + if (r < 0) + return r; if (sti.st_rdev != sto.st_rdev) return -ENOLINK; @@ -1986,14 +1989,18 @@ int terminal_get_cursor_position( assert(input_fd >= 0); assert(output_fd >= 0); - if (terminal_is_dumb()) + if (getenv_terminal_is_dumb()) return -EOPNOTSUPP; r = terminal_verify_same(input_fd, output_fd); if (r < 0) return log_debug_errno(r, "Called with distinct input/output fds: %m"); - struct termios old_termios; + /* Failure to reset the terminal is ignored here and in similar cases below. + * We already have our result; if cleanup fails it doesn't change the validity of the result. */ + struct termios old_termios = TERMIOS_NULL; + CLEANUP_TERMIOS_RESET(input_fd, old_termios); + if (tcgetattr(input_fd, &old_termios) < 0) return log_debug_errno(errno, "Failed to get terminal settings: %m"); @@ -2006,16 +2013,16 @@ int terminal_get_cursor_position( /* Request cursor position (DSR/CPR) */ r = loop_write(output_fd, "\x1B[6n", SIZE_MAX); if (r < 0) - goto finish; + return r; /* Open a 2nd input fd, in non-blocking mode, so that we won't ever hang in read() should someone * else process the POLLIN. */ nonblock_input_fd = r = fd_reopen(input_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); if (r < 0) - goto finish; + return r; - usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC); + usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */ size_t buf_full = 0; CursorPositionContext context = {}; @@ -2023,18 +2030,14 @@ int terminal_get_cursor_position( for (bool first = true;; first = false) { if (buf_full == 0) { usec_t n = now(CLOCK_MONOTONIC); - if (n >= end) { - r = -EOPNOTSUPP; - goto finish; - } + if (n >= end) + return -EOPNOTSUPP; r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n)); if (r < 0) - goto finish; - if (r == 0) { - r = -EOPNOTSUPP; - goto finish; - } + return r; + if (r == 0) + return -EOPNOTSUPP; /* On the first try, read multiple characters, i.e. the shortest valid * reply. Afterwards read byte-wise, since we don't want to read too much, and @@ -2044,8 +2047,7 @@ int terminal_get_cursor_position( if (errno == EAGAIN) continue; - r = -errno; - goto finish; + return -errno; } assert((size_t) l <= sizeof(buf)); @@ -2055,7 +2057,7 @@ int terminal_get_cursor_position( size_t processed; r = scan_cursor_position_response(&context, buf, buf_full, &processed); if (r < 0) - goto finish; + return r; assert(processed <= buf_full); buf_full -= processed; @@ -2063,26 +2065,17 @@ int terminal_get_cursor_position( if (r > 0) { /* Superficial validity check */ - if (context.row >= 32766 || context.column >= 32766) { - r = -ENODATA; - goto finish; - } + if (context.row >= 32766 || context.column >= 32766) + return -ENODATA; if (ret_row) *ret_row = context.row; if (ret_column) *ret_column = context.column; - r = 0; - goto finish; + return 0; } } - -finish: - /* We ignore failure here and in similar cases below. We already got a reply and if cleanup fails, - * this doesn't change the validity of the result. */ - (void) tcsetattr(input_fd, TCSANOW, &old_termios); - return r; } int terminal_reset_defensive(int fd, TerminalResetFlags flags) { @@ -2124,6 +2117,25 @@ void termios_disable_echo(struct termios *termios) { termios->c_cc[VTIME] = 0; } +static bool termios_is_null(const struct termios *t) { + if (!t) + return true; + + return t->c_iflag == UINT_MAX && + t->c_oflag == UINT_MAX && + t->c_cflag == UINT_MAX && + t->c_lflag == UINT_MAX; +} + +void termios_reset(const TermiosResetContext *c) { + assert(c); + + PROTECT_ERRNO; + + if (c->fd && *c->fd >= 0 && !termios_is_null(c->termios)) + (void) tcsetattr(*c->fd, TCSANOW, c->termios); +} + typedef enum BackgroundColorState { BACKGROUND_TEXT, BACKGROUND_ESCAPE, @@ -2295,7 +2307,9 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret if (r < 0) return r; - struct termios old_termios; + struct termios old_termios = TERMIOS_NULL; + CLEANUP_TERMIOS_RESET(nonblock_input_fd, old_termios); + if (tcgetattr(nonblock_input_fd, &old_termios) < 0) return -errno; @@ -2307,9 +2321,9 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret r = loop_write(STDOUT_FILENO, ANSI_OSC "11;?" ANSI_ST, SIZE_MAX); if (r < 0) - goto finish; + return r; - usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC); + usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); char buf[STRLEN(ANSI_OSC "11;rgb:0/0/0" ANSI_ST)]; /* shortest possible reply */ size_t buf_full = 0; BackgroundColorContext context = {}; @@ -2317,18 +2331,14 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret for (bool first = true;; first = false) { if (buf_full == 0) { usec_t n = now(CLOCK_MONOTONIC); - if (n >= end) { - r = -EOPNOTSUPP; - goto finish; - } + if (n >= end) + return -EOPNOTSUPP; r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n)); if (r < 0) - goto finish; - if (r == 0) { - r = -EOPNOTSUPP; - goto finish; - } + return r; + if (r == 0) + return -EOPNOTSUPP; /* On the first try, read multiple characters, i.e. the shortest valid * reply. Afterwards read byte-wise, since we don't want to read too much, and @@ -2337,8 +2347,7 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret if (l < 0) { if (errno == EAGAIN) continue; - r = -errno; - goto finish; + return -errno; } assert((size_t) l <= sizeof(buf)); @@ -2348,7 +2357,7 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret size_t processed; r = scan_background_color_response(&context, buf, buf_full, &processed); if (r < 0) - goto finish; + return r; assert(processed <= buf_full); buf_full -= processed; @@ -2361,71 +2370,41 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret *ret_green = (double) context.green / ((UINT64_C(1) << context.green_bits) - 1); assert(context.blue_bits > 0); *ret_blue = (double) context.blue / ((UINT64_C(1) << context.blue_bits) - 1); - r = 0; - goto finish; + return 0; } } - -finish: - (void) tcsetattr(nonblock_input_fd, TCSANOW, &old_termios); - return r; } -int terminal_get_size_by_dsr( - int input_fd, +/* Determine terminal dimensions by means of ANSI sequences: save the cursor via DECSC, position it far + * to the bottom right (clamped to actual terminal dimensions), read back via DSR where we ended up, and + * restore cursor via DECRC. Only needs a single DSR round-trip, and always restores the cursor regardless + * of whether the response is received. + * + * Caller must have already opened a non-blocking input fd and configured termios (echo/icanon off). */ +static int terminal_query_size_by_dsr( + int nonblock_input_fd, int output_fd, unsigned *ret_rows, unsigned *ret_columns) { int r; - assert(input_fd >= 0); + assert(nonblock_input_fd >= 0); assert(output_fd >= 0); - /* Tries to determine the terminal dimension by means of ANSI sequences. - * - * We position the cursor briefly at an absolute location very far down and very far to the right, - * and then read back where we actually ended up. Because cursor locations are capped at the terminal - * width/height we should then see the right values. In order to not risk integer overflows in - * terminal applications we'll use INT16_MAX-1 as location to jump to — hopefully a value that is - * large enough for any real-life terminals, but small enough to not overflow anything or be - * recognized as a "niche" value. (Note that the dimension fields in "struct winsize" are 16bit only, - * too). */ - - if (terminal_is_dumb()) - return -EOPNOTSUPP; - - r = terminal_verify_same(input_fd, output_fd); - if (r < 0) - return log_debug_errno(r, "Called with distinct input/output fds: %m"); - - /* Open a 2nd input fd, in non-blocking mode, so that we won't ever hang in read() - * should someone else process the POLLIN. Do all subsequent operations on the new fd. */ - _cleanup_close_ int nonblock_input_fd = r = fd_reopen(input_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + /* Use DECSC/DECRC to save/restore cursor instead of querying position via DSR. This way the cursor + * is always restored — even on timeout — and we only need one DSR response instead of two. */ + r = loop_write_full(output_fd, + "\x1B" "7" /* DECSC: save cursor position */ + "\x1B[32766;32766H" /* CUP: position cursor far to the right and to the bottom, staying within 16bit signed range */ + "\x1B[6n" /* DSR: request cursor position (CPR) */ + "\x1B" "8", /* DECRC: restore cursor position */ + SIZE_MAX, + CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); if (r < 0) return r; - struct termios old_termios; - if (tcgetattr(nonblock_input_fd, &old_termios) < 0) - return log_debug_errno(errno, "Failed to get terminal settings: %m"); - - struct termios new_termios = old_termios; - termios_disable_echo(&new_termios); - - if (tcsetattr(nonblock_input_fd, TCSANOW, &new_termios) < 0) - return log_debug_errno(errno, "Failed to set new terminal settings: %m"); - - unsigned saved_row = 0, saved_column = 0; - - r = loop_write(output_fd, - "\x1B[6n" /* Request cursor position (DSR/CPR) */ - "\x1B[32766;32766H" /* Position cursor really far to the right and to the bottom, but let's stay within the 16bit signed range */ - "\x1B[6n", /* Request cursor position again */ - SIZE_MAX); - if (r < 0) - goto finish; - - usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC); + usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */ size_t buf_full = 0; CursorPositionContext context = {}; @@ -2433,18 +2412,14 @@ int terminal_get_size_by_dsr( for (bool first = true;; first = false) { if (buf_full == 0) { usec_t n = now(CLOCK_MONOTONIC); - if (n >= end) { - r = -EOPNOTSUPP; - goto finish; - } + if (n >= end) + return -EOPNOTSUPP; r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n)); if (r < 0) - goto finish; - if (r == 0) { - r = -EOPNOTSUPP; - goto finish; - } + return r; + if (r == 0) + return -EOPNOTSUPP; /* On the first try, read multiple characters, i.e. the shortest valid * reply. Afterwards read byte-wise, since we don't want to read too much, and @@ -2454,8 +2429,7 @@ int terminal_get_size_by_dsr( if (errno == EAGAIN) continue; - r = -errno; - goto finish; + return -errno; } assert((size_t) l <= sizeof(buf)); @@ -2465,60 +2439,71 @@ int terminal_get_size_by_dsr( size_t processed; r = scan_cursor_position_response(&context, buf, buf_full, &processed); if (r < 0) - goto finish; + return r; assert(processed <= buf_full); buf_full -= processed; memmove(buf, buf + processed, buf_full); if (r > 0) { - if (saved_row == 0) { - assert(saved_column == 0); + /* Superficial validity checks (no particular reason to check for < 4, it's + * just a way to look for unreasonably small values) */ + if (context.row < 4 || context.column < 4 || context.row >= 32766 || context.column >= 32766) + return -ENODATA; - /* First sequence, this is the cursor position before we set it somewhere - * into the void at the bottom right. Let's save where we are so that we can - * return later. */ + if (ret_rows) + *ret_rows = context.row; + if (ret_columns) + *ret_columns = context.column; - /* Superficial validity checks */ - if (context.row <= 0 || context.column <= 0 || context.row >= 32766 || context.column >= 32766) { - r = -ENODATA; - goto finish; - } + return 0; + } + } +} - saved_row = context.row; - saved_column = context.column; +/* Common setup for ANSI terminal queries: validate the fds, open a non-blocking input fd, and configure + * termios with echo and canonical mode disabled. Caller must restore termios and close the fd when done. */ +static int terminal_prepare_query( + int input_fd, + int output_fd, + int *ret_nonblock_fd, + struct termios *ret_saved_termios) { - /* Reset state */ - context = (CursorPositionContext) {}; - } else { - /* Second sequence, this is the cursor position after we set it somewhere - * into the void at the bottom right. */ - - /* Superficial validity checks (no particular reason to check for < 4, it's - * just a way to look for unreasonably small values) */ - if (context.row < 4 || context.column < 4 || context.row >= 32766 || context.column >= 32766) { - r = -ENODATA; - goto finish; - } + int r; + + assert(input_fd >= 0); + assert(output_fd >= 0); + assert(ret_nonblock_fd); + assert(ret_saved_termios); - if (ret_rows) - *ret_rows = context.row; - if (ret_columns) - *ret_columns = context.column; + /* Use getenv_terminal_is_dumb() instead of terminal_is_dumb() here since we operate on an + * explicitly passed fd, not on stdio. terminal_is_dumb() additionally checks on_tty() which + * tests whether *stderr* is a tty — that's irrelevant when we're querying a directly opened + * terminal such as /dev/console. */ + if (getenv_terminal_is_dumb()) + return -EOPNOTSUPP; - r = 0; - goto finish; - } - } - } + r = terminal_verify_same(input_fd, output_fd); + if (r < 0) + return log_debug_errno(r, "Called with distinct input/output fds: %m"); -finish: - /* Restore cursor position */ - if (saved_row > 0 && saved_column > 0) - (void) terminal_set_cursor_position(output_fd, saved_row, saved_column); - (void) tcsetattr(nonblock_input_fd, TCSANOW, &old_termios); + /* Open a 2nd input fd, in non-blocking mode, so that we won't ever hang in read() + * should someone else process the POLLIN. Do all subsequent operations on the new fd. */ + _cleanup_close_ int nonblock_input_fd = r = fd_reopen(input_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (r < 0) + return r; - return r; + if (tcgetattr(nonblock_input_fd, ret_saved_termios) < 0) + return log_debug_errno(errno, "Failed to get terminal settings: %m"); + + struct termios new_termios = *ret_saved_termios; + termios_disable_echo(&new_termios); + + if (tcsetattr(nonblock_input_fd, TCSANOW, &new_termios) < 0) + return log_debug_errno(errno, "Failed to set new terminal settings: %m"); + + *ret_nonblock_fd = TAKE_FD(nonblock_input_fd); + return 0; } /* @@ -2557,63 +2542,38 @@ static int scan_text_area_size_response( return 0; } -int terminal_get_size_by_csi18( - int input_fd, +/* Determine terminal dimensions by means of an ANSI CSI 18 sequence. + * + * Caller must have already opened a non-blocking input fd and configured termios (echo/icanon off). */ +static int terminal_query_size_by_csi18( + int nonblock_input_fd, int output_fd, unsigned *ret_rows, unsigned *ret_columns) { + int r; - assert(input_fd >= 0); + assert(nonblock_input_fd >= 0); assert(output_fd >= 0); - /* Tries to determine the terminal dimension by means of an ANSI sequence CSI 18. */ - - if (terminal_is_dumb()) - return -EOPNOTSUPP; - - r = terminal_verify_same(input_fd, output_fd); - if (r < 0) - return log_debug_errno(r, "Called with distinct input/output fds: %m"); - - /* Open a 2nd input fd, in non-blocking mode, so that we won't ever hang in read() - * should someone else process the POLLIN. Do all subsequent operations on the new fd. */ - _cleanup_close_ int nonblock_input_fd = r = fd_reopen(input_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + r = loop_write_full(output_fd, CSI18_Q, SIZE_MAX, CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); if (r < 0) return r; - struct termios old_termios; - if (tcgetattr(nonblock_input_fd, &old_termios) < 0) - return log_debug_errno(errno, "Failed to get terminal settings: %m"); - - struct termios new_termios = old_termios; - termios_disable_echo(&new_termios); - - if (tcsetattr(nonblock_input_fd, TCSANOW, &new_termios) < 0) - return log_debug_errno(errno, "Failed to set new terminal settings: %m"); - - r = loop_write(output_fd, CSI18_Q, SIZE_MAX); - if (r < 0) - goto finish; - - usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC); + usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); char buf[STRLEN(CSI18_R1)]; size_t bytes = 0; for (;;) { usec_t n = now(CLOCK_MONOTONIC); - if (n >= end) { - r = -EOPNOTSUPP; - break; - } + if (n >= end) + return -EOPNOTSUPP; r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n)); if (r < 0) - break; - if (r == 0) { - r = -EOPNOTSUPP; - break; - } + return r; + if (r == 0) + return -EOPNOTSUPP; /* On the first read, read multiple characters, i.e. the shortest valid reply. Afterwards * read byte by byte, since we don't want to read too much and drop characters from the input @@ -2622,8 +2582,7 @@ int terminal_get_size_by_csi18( if (l < 0) { if (errno == EAGAIN) continue; - r = -errno; - break; + return -errno; } assert((size_t) l <= sizeof(buf) - bytes); @@ -2631,19 +2590,68 @@ int terminal_get_size_by_csi18( r = scan_text_area_size_response(buf, bytes, ret_rows, ret_columns); if (r != -EAGAIN) - break; + return r; - if (bytes == sizeof(buf)) { - r = -EOPNOTSUPP; /* The response has the right prefix, but we didn't find a valid - * answer with a terminator in the allotted space. Something is - * wrong, possibly some unrelated bytes got injected into the - * answer. */ - break; - } + if (bytes == sizeof(buf)) + return -EOPNOTSUPP; /* The response has the right prefix, but we didn't find a valid + * answer with a terminator in the allotted space. Something is + * wrong, possibly some unrelated bytes got injected into the + * answer. */ + } +} + +int terminal_get_size( + int input_fd, + int output_fd, + unsigned *ret_rows, + unsigned *ret_columns, + bool try_dsr, + bool try_csi18) { + + _cleanup_close_ int nonblock_input_fd = -EBADF; + struct termios old_termios = TERMIOS_NULL; + CLEANUP_TERMIOS_RESET(nonblock_input_fd, old_termios); + _cleanup_(nonblock_resetp) int nonblock_reset = -EBADF; + int r; + + assert(try_dsr || try_csi18); + + r = terminal_prepare_query(input_fd, output_fd, &nonblock_input_fd, &old_termios); + if (r < 0) + return r; + + /* Put the output fd in non-blocking mode with a write timeout, to avoid blocking indefinitely on + * write if the terminal is not consuming data (e.g. serial console with flow control). */ + r = fd_nonblock(output_fd, true); + if (r < 0) + return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m"); + if (r > 0) + nonblock_reset = output_fd; + + /* Flush any stale input that might confuse the response parsers. */ + (void) tcflush(nonblock_input_fd, TCIFLUSH); + + if (try_csi18) { + r = terminal_query_size_by_csi18(nonblock_input_fd, output_fd, ret_rows, ret_columns); + if (r >= 0) + return r; + + /* Query failed. Flush any outstanding input. */ + (void) tcflush(nonblock_input_fd, TCIFLUSH); + + if (!IN_SET(r, -EOPNOTSUPP, -EINVAL)) + return r; + } + + if (try_dsr) { + r = terminal_query_size_by_dsr(nonblock_input_fd, output_fd, ret_rows, ret_columns); + if (r >= 0) + return r; + + /* Query failed. Flush any outstanding input. */ + (void) tcflush(nonblock_input_fd, TCIFLUSH); } -finish: - (void) tcsetattr(nonblock_input_fd, TCSANOW, &old_termios); return r; } @@ -2658,20 +2666,12 @@ int terminal_fix_size(int input_fd, int output_fd) { * sequences are interpreted by the final terminal instead of an intermediary tty driver they should * be more accurate. */ - r = terminal_verify_same(input_fd, output_fd); - if (r < 0) - return r; struct winsize ws = {}; if (ioctl(output_fd, TIOCGWINSZ, &ws) < 0) return log_debug_errno(errno, "Failed to query terminal dimensions, ignoring: %m"); - r = terminal_get_size_by_csi18(input_fd, output_fd, &rows, &columns); - if (IN_SET(r, -EOPNOTSUPP, -EINVAL)) - /* We get -EOPNOTSUPP if the query fails and -EINVAL when the received answer is invalid. - * Try the fallback method. It is more involved and moves the cursor, but seems to have wider - * support. */ - r = terminal_get_size_by_dsr(input_fd, output_fd, &rows, &columns); + r = terminal_get_size(input_fd, output_fd, &rows, &columns, /* try_dsr= */ true, /* try_csi18= */ true); if (r < 0) return log_debug_errno(r, "Failed to acquire terminal dimensions via ANSI sequences, not adjusting terminal dimensions: %m"); @@ -2745,7 +2745,9 @@ int terminal_get_terminfo_by_dcs(int fd, char **ret_name) { /* Note: fd must be in non-blocking read-write mode! */ - struct termios old_termios; + struct termios old_termios = TERMIOS_NULL; + CLEANUP_TERMIOS_RESET(fd, old_termios); + if (tcgetattr(fd, &old_termios) < 0) return -errno; @@ -2757,26 +2759,22 @@ int terminal_get_terminfo_by_dcs(int fd, char **ret_name) { r = loop_write(fd, DCS_TERMINFO_Q, SIZE_MAX); if (r < 0) - goto finish; + return r; - usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC); + usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); char buf[STRLEN(DCS_TERMINFO_R1) + MAX_TERMINFO_LENGTH + STRLEN(ANSI_ST)]; size_t bytes = 0; for (;;) { usec_t n = now(CLOCK_MONOTONIC); - if (n >= end) { - r = -EOPNOTSUPP; - break; - } + if (n >= end) + return -EOPNOTSUPP; r = fd_wait_for_event(fd, POLLIN, usec_sub_unsigned(end, n)); if (r < 0) - break; - if (r == 0) { - r = -EOPNOTSUPP; - break; - } + return r; + if (r == 0) + return -EOPNOTSUPP; /* On the first read, read multiple characters, i.e. the shortest valid reply. Afterwards * read byte by byte, since we don't want to read too much and drop characters from the input @@ -2785,8 +2783,7 @@ int terminal_get_terminfo_by_dcs(int fd, char **ret_name) { if (l < 0) { if (errno == EAGAIN) continue; - r = -errno; - break; + return -errno; } assert((size_t) l <= sizeof(buf) - bytes); @@ -2794,20 +2791,14 @@ int terminal_get_terminfo_by_dcs(int fd, char **ret_name) { r = scan_terminfo_response(buf, bytes, ret_name); if (r != -EAGAIN) - break; + return r; - if (bytes == sizeof(buf)) { - r = -EOPNOTSUPP; /* The response has the right prefix, but we didn't find a valid - * answer with a terminator in the allotted space. Something is - * wrong, possibly some unrelated bytes got injected into the - * answer. */ - break; - } + if (bytes == sizeof(buf)) + return -EOPNOTSUPP; /* The response has the right prefix, but we didn't find a valid + * answer with a terminator in the allotted space. Something is + * wrong, possibly some unrelated bytes got injected into the + * answer. */ } - -finish: - (void) tcsetattr(fd, TCSANOW, &old_termios); - return r; } int have_terminfo_file(const char *name) { diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 1cd80b0186e30..7ac5661104159 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -118,6 +118,7 @@ void columns_lines_cache_reset(int _unused_ signum); void reset_terminal_feature_caches(void); bool on_tty(void); +bool term_env_valid(const char *term); bool getenv_terminal_is_dumb(void); bool terminal_is_dumb(void); @@ -145,12 +146,34 @@ assert_cc((TTY_MODE & 0711) == 0600); void termios_disable_echo(struct termios *termios); +/* A termios sentinel with all flag fields set to all-ones-bits. No real tcgetattr() result will ever + * match this because no real terminal configuration uses all-ones in every flag field simultaneously. */ +#define TERMIOS_NULL (struct termios) { \ + .c_iflag = UINT_MAX, \ + .c_oflag = UINT_MAX, \ + .c_cflag = UINT_MAX, \ + .c_lflag = UINT_MAX, \ +} + +typedef struct TermiosResetContext { + int *fd; + struct termios *termios; +} TermiosResetContext; + +void termios_reset(const TermiosResetContext *c); + +#define CLEANUP_TERMIOS_RESET(_fd, _termios) \ + _cleanup_(termios_reset) _unused_ const TermiosResetContext \ + CONCATENATE(_cleanup_termios_, UNIQ) = { \ + .fd = &(_fd), \ + .termios = &(_termios), \ + } + /* The $TERM value we use for terminals other than the Linux console */ #define FALLBACK_TERM "vt220" int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue); -int terminal_get_size_by_dsr(int input_fd, int output_fd, unsigned *ret_rows, unsigned *ret_columns); -int terminal_get_size_by_csi18(int input_fd, int output_fd, unsigned *ret_rows, unsigned *ret_columns); +int terminal_get_size(int input_fd, int output_fd, unsigned *ret_rows, unsigned *ret_columns, bool try_dsr, bool try_csi18); int terminal_fix_size(int input_fd, int output_fd); int terminal_get_terminfo_by_dcs(int fd, char **ret_name); diff --git a/src/basic/time-util.c b/src/basic/time-util.c index 5dd00af952d29..78c33c7553ce6 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -49,7 +49,15 @@ usec_t now(clockid_t clock_id) { assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0); - return timespec_load(&ts); + usec_t n = timespec_load(&ts); + + /* We use both 0 and USEC_INFINITY as niche values. If the current time collides with either, things are + * really weird and really broken. Let's not allow this to go through, it would break too many of our + * assumptions in code. */ + assert(n > 0); + assert(n < USEC_INFINITY); + + return n; } nsec_t now_nsec(clockid_t clock_id) { @@ -57,7 +65,12 @@ nsec_t now_nsec(clockid_t clock_id) { assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0); - return timespec_load_nsec(&ts); + nsec_t n = timespec_load_nsec(&ts); + + assert(n > 0); + assert(n < NSEC_INFINITY); + + return n; } dual_timestamp* dual_timestamp_now(dual_timestamp *ts) { @@ -1892,3 +1905,51 @@ TimestampStyle timestamp_style_from_string(const char *s) { return TIMESTAMP_US_UTC; return t; } + +int parse_calendar_date_full(const char *s, bool allow_pre_epoch, usec_t *ret_usec, struct tm *ret_tm) { + struct tm parsed_tm = {}, copy_tm; + const char *k; + int r; + + assert(s); + + k = strptime(s, "%Y-%m-%d", &parsed_tm); + if (!k || *k) + return -EINVAL; + + copy_tm = parsed_tm; + + usec_t usec = USEC_INFINITY; + + if (allow_pre_epoch) { + /* For birth dates we use timegm() directly since we need to accept pre-epoch dates. + * timegm() returns (time_t) -1 both on error and for one second before the epoch. + * Initialize wday to -1 beforehand: if it remains -1 after the call, it's a genuine + * error; if timegm() changed it, the date was successfully normalized. */ + copy_tm.tm_wday = -1; + if (timegm(©_tm) == (time_t) -1 && copy_tm.tm_wday == -1) + return -EINVAL; + } else { + r = mktime_or_timegm_usec(©_tm, /* utc= */ true, &usec); + if (r < 0) + return r; + } + + /* Refuse non-normalized dates, e.g. Feb 30 */ + if (copy_tm.tm_mday != parsed_tm.tm_mday || + copy_tm.tm_mon != parsed_tm.tm_mon || + copy_tm.tm_year != parsed_tm.tm_year) + return -EINVAL; + + if (ret_usec) + *ret_usec = usec; + if (ret_tm) { + /* Reset to unset, then fill in only the date fields we parsed and validated */ + *ret_tm = BIRTH_DATE_UNSET; + ret_tm->tm_mday = parsed_tm.tm_mday; + ret_tm->tm_mon = parsed_tm.tm_mon; + ret_tm->tm_year = parsed_tm.tm_year; + } + + return 0; +} diff --git a/src/basic/time-util.h b/src/basic/time-util.h index bde0b02d037c4..9a66a90859d67 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include #include #include "basic-forward.h" @@ -113,6 +114,7 @@ struct timespec* timespec_store(struct timespec *ts, usec_t u); struct timespec* timespec_store_nsec(struct timespec *ts, nsec_t n); #define TIMESPEC_STORE(u) timespec_store(&(struct timespec) {}, (u)) +#define TIMESPEC_STORE_NSEC(n) timespec_store_nsec(&(struct timespec) {}, (n)) usec_t timeval_load(const struct timeval *tv) _pure_; struct timeval* timeval_store(struct timeval *tv, usec_t u); @@ -181,6 +183,23 @@ const char* etc_localtime(void); int mktime_or_timegm_usec(struct tm *tm, bool utc, usec_t *ret); int localtime_or_gmtime_usec(usec_t t, bool utc, struct tm *ret); +int parse_calendar_date_full(const char *s, bool allow_pre_epoch, usec_t *ret_usec, struct tm *ret_tm); + +static inline int parse_calendar_date(const char *s, usec_t *ret) { + return parse_calendar_date_full(s, /* allow_pre_epoch= */ false, ret, NULL); +} + +#define BIRTH_DATE_UNSET \ + (const struct tm) { \ + .tm_year = INT_MIN, \ + } + +#define BIRTH_DATE_IS_SET(tm) ((tm).tm_year != INT_MIN) + +static inline int parse_birth_date(const char *s, struct tm *ret) { + return parse_calendar_date_full(s, /* allow_pre_epoch= */ true, NULL, ret); +} + uint32_t usec_to_jiffies(usec_t usec); usec_t jiffies_to_usec(uint32_t jiffies); diff --git a/src/basic/tmpfile-util.c b/src/basic/tmpfile-util.c index be7a930c44c18..9c2dc33f065d7 100644 --- a/src/basic/tmpfile-util.c +++ b/src/basic/tmpfile-util.c @@ -294,7 +294,8 @@ int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **r return fd; } - log_debug_errno(fd, "Failed to use O_TMPFILE for %s: %m", target); + if (!ERRNO_IS_NEG_NOT_SUPPORTED(fd)) + log_debug_errno(fd, "Failed to use O_TMPFILE for %s: %m", target); _cleanup_free_ char *tmp = NULL; r = tempfn_random(target, NULL, &tmp); diff --git a/src/basic/uid-classification.c b/src/basic/uid-classification.c index 7a45fc57504e8..a3bc7ef3d4cb6 100644 --- a/src/basic/uid-classification.c +++ b/src/basic/uid-classification.c @@ -25,6 +25,8 @@ static int parse_alloc_uid(const char *path, const char *name, const char *t, ui uid_t uid; int r; + assert(ret_uid); + r = parse_uid(t, &uid); if (r < 0) return log_debug_errno(r, "%s: failed to parse %s %s, ignoring: %m", path, name, t); @@ -37,6 +39,8 @@ static int parse_alloc_uid(const char *path, const char *name, const char *t, ui #endif int read_login_defs(UGIDAllocationRange *ret_defs, const char *path, const char *root) { + assert(ret_defs); + #if ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES _cleanup_fclose_ FILE *f = NULL; UGIDAllocationRange defs; diff --git a/src/basic/uid-range.c b/src/basic/uid-range.c index 31305952ba43c..62c7d7d928eb7 100644 --- a/src/basic/uid-range.c +++ b/src/basic/uid-range.c @@ -63,6 +63,10 @@ static void uid_range_coalesce(UIDRange *range) { break; begin = MIN(x->start, y->start); + + /* Silence static analyzers, overflow is prevented by uid_range_add_internal() */ + assert(x->start <= UINT32_MAX - x->nr); + assert(y->start <= UINT32_MAX - y->nr); end = MAX(x->start + x->nr, y->start + y->nr); x->start = begin; @@ -71,7 +75,12 @@ static void uid_range_coalesce(UIDRange *range) { if (range->n_entries > j + 1) memmove(y, y + 1, sizeof(UIDRangeEntry) * (range->n_entries - j - 1)); + /* Silence static analyzers, n_entries > 0 since j < n_entries holds in the loop condition */ + assert(range->n_entries > 0); range->n_entries--; + + /* Silence static analyzers, j cannot be 0 here since it starts at i + 1, i.e. >= 1 */ + assert(j > 0); j--; } } diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c index ea4eebbf5d37f..57a67af163e31 100644 --- a/src/basic/unit-def.c +++ b/src/basic/unit-def.c @@ -24,6 +24,8 @@ int unit_name_from_dbus_path(const char *path, char **name) { const char *e; char *n; + assert(name); + e = startswith(path, "/org/freedesktop/systemd1/unit/"); if (!e) return -EINVAL; @@ -70,6 +72,14 @@ const char* unit_dbus_interface_from_name(const char *name) { return unit_dbus_interface_from_type(t); } +const char* unit_type_to_capitalized_string(UnitType t) { + const char *di = unit_dbus_interface_from_type(t); + if (!di) + return NULL; + + return ASSERT_PTR(startswith(di, "org.freedesktop.systemd1.")); +} + static const char* const unit_type_table[_UNIT_TYPE_MAX] = { [UNIT_SERVICE] = "service", [UNIT_SOCKET] = "socket", diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h index 5fecd3ecec14e..8d05b5b5ed8be 100644 --- a/src/basic/unit-def.h +++ b/src/basic/unit-def.h @@ -321,6 +321,7 @@ void unit_types_list(void); DECLARE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState); DECLARE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState); +const char* unit_type_to_capitalized_string(UnitType t); DECLARE_STRING_TABLE_LOOKUP(freezer_state, FreezerState); FreezerState freezer_state_finish(FreezerState state) _const_; diff --git a/src/basic/unit-name.c b/src/basic/unit-name.c index 43b3f6831222a..70ea429e27969 100644 --- a/src/basic/unit-name.c +++ b/src/basic/unit-name.c @@ -17,8 +17,7 @@ /* Characters valid in a unit name. */ #define VALID_CHARS \ - DIGITS \ - LETTERS \ + ALPHANUMERICAL \ ":-_.\\" /* The same, but also permits the single @ character that may appear */ @@ -346,6 +345,7 @@ int unit_name_unescape(const char *f, char **ret) { char *t; assert(f); + assert(ret); r = strdup(f); if (!r) @@ -548,6 +548,8 @@ int unit_name_hash_long(const char *name, char **ret) { le64_t h; size_t len; + assert(ret); + if (strlen(name) < UNIT_NAME_MAX) return -EMSGSIZE; diff --git a/src/basic/user-util.c b/src/basic/user-util.c index e434fbec8f985..b735d27474272 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -185,7 +185,7 @@ const char* default_root_shell_at(int rfd) { assert(rfd >= 0 || rfd == AT_FDCWD); - int r = chaseat(rfd, DEFAULT_USER_SHELL, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL); + int r = chaseat(rfd, rfd, DEFAULT_USER_SHELL, /* flags= */ 0, NULL, NULL); if (r < 0 && r != -ENOENT) log_debug_errno(r, "Failed to look up shell '%s': %m", DEFAULT_USER_SHELL); if (r > 0) @@ -762,18 +762,17 @@ bool valid_user_group_name(const char *u, ValidUserFlags flags) { * don't allow slashes. */ return false; - if (in_charset(u, "0123456789")) /* Don't allow fully numeric strings, they might be confused - * with UIDs (note that this test is more broad than - * the parse_uid() test above, as it will cover more than - * the 32-bit range, and it will detect 65535 (which is in - * invalid UID, even though in the unsigned 32 bit range) */ + if (in_charset(u, DIGITS)) /* Don't allow fully numeric strings, they might be confused with + * UIDs (note that this test is more broad than the parse_uid() + * test above, as it will cover more than the 32-bit range, and it + * will detect 65535 (which is in invalid UID, even though in the + * unsigned 32 bit range) */ return false; - if (u[0] == '-' && in_charset(u + 1, "0123456789")) /* Don't allow negative fully numeric - * strings either. After all some people - * write 65535 as -1 (even though that's - * not even true on 32-bit uid_t - * anyway) */ + if (u[0] == '-' && in_charset(u + 1, DIGITS)) /* Don't allow negative fully numeric strings + * either. After all some people write 65535 as + * -1 (even though that's not even true on + * 32-bit uid_t anyway) */ return false; if (dot_or_dot_dot(u)) /* User names typically become home directory names, and these two are @@ -1114,6 +1113,8 @@ int getpwnam_malloc(const char *name, struct passwd **ret) { for (;;) { _cleanup_free_ void *buf = NULL; + /* Silence static analyzers */ + assert(bufsize <= SIZE_MAX - ALIGN(sizeof(struct passwd))); buf = malloc0(ALIGN(sizeof(struct passwd)) + bufsize); if (!buf) return -ENOMEM; @@ -1155,6 +1156,8 @@ int getpwuid_malloc(uid_t uid, struct passwd **ret) { for (;;) { _cleanup_free_ void *buf = NULL; + /* Silence static analyzers */ + assert(bufsize <= SIZE_MAX - ALIGN(sizeof(struct passwd))); buf = malloc0(ALIGN(sizeof(struct passwd)) + bufsize); if (!buf) return -ENOMEM; @@ -1199,6 +1202,8 @@ int getgrnam_malloc(const char *name, struct group **ret) { for (;;) { _cleanup_free_ void *buf = NULL; + /* Silence static analyzers */ + assert(bufsize <= SIZE_MAX - ALIGN(sizeof(struct group))); buf = malloc0(ALIGN(sizeof(struct group)) + bufsize); if (!buf) return -ENOMEM; @@ -1238,6 +1243,8 @@ int getgrgid_malloc(gid_t gid, struct group **ret) { for (;;) { _cleanup_free_ void *buf = NULL; + /* Silence static analyzers */ + assert(bufsize <= SIZE_MAX - ALIGN(sizeof(struct group))); buf = malloc0(ALIGN(sizeof(struct group)) + bufsize); if (!buf) return -ENOMEM; diff --git a/src/basic/utf8.c b/src/basic/utf8.c index 8a0cfc012ec6d..edb0ea1ca5513 100644 --- a/src/basic/utf8.c +++ b/src/basic/utf8.c @@ -63,6 +63,7 @@ int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar) { size_t len; assert(str); + assert(ret_unichar); len = utf8_encoded_expected_len(str[0]); @@ -283,6 +284,9 @@ int utf8_to_ascii(const char *str, char replacement_char, char **ret) { /* Convert to a string that has only ASCII chars, replacing anything that is not ASCII * by replacement_char. */ + assert(str); + assert(ret); + _cleanup_free_ char *ans = new(char, strlen(str) + 1); if (!ans) return -ENOMEM; @@ -500,6 +504,8 @@ size_t char16_strlen(const char16_t *s) { } size_t char16_strsize(const char16_t *s) { + POINTER_MAY_BE_NULL(s); + return s ? (char16_strlen(s) + 1) * sizeof(*s) : 0; } @@ -566,6 +572,8 @@ int utf8_encoded_valid_unichar(const char *str, size_t length /* bytes */) { size_t utf8_n_codepoints(const char *str) { size_t n = 0; + assert(str); + /* Returns the number of UTF-8 codepoints in this string, or SIZE_MAX if the string is not valid UTF-8. */ while (*str != 0) { @@ -583,6 +591,7 @@ size_t utf8_n_codepoints(const char *str) { } size_t utf8_console_width(const char *str) { + POINTER_MAY_BE_NULL(str); if (isempty(str)) return 0; diff --git a/src/basic/xattr-util.c b/src/basic/xattr-util.c index 68ee83d899ede..fb886f6ae82c5 100644 --- a/src/basic/xattr-util.c +++ b/src/basic/xattr-util.c @@ -301,6 +301,18 @@ int xsetxattr_full( if (size == SIZE_MAX) size = strlen(value); + /* Skip the write if the xattr already has the correct value, to avoid + * unnecessary timestamp changes on the file. Only do this for plain + * replace mode (xattr_flags == 0) — XATTR_CREATE callers expect + * -EEXIST when the xattr already exists. */ + _cleanup_free_ char *old_value = NULL; + size_t old_size; + + if (xattr_flags == 0 && + getxattr_at_malloc(fd, path, name, at_flags, &old_value, &old_size) >= 0 && + memcmp_nn(old_value, old_size, value, size) == 0) + return 0; + if (have_xattrat && !isempty(path)) { struct xattr_args args = { .value = PTR_TO_UINT64(value), @@ -429,11 +441,13 @@ int getcrtime_at( * concept is useful for determining how "old" a file really is, and hence using the older of the two makes * most sense. */ - r = xstatx_full(fd, path, + r = xstatx_full(fd, + path, at_flags_normalize_nofollow(at_flags)|AT_STATX_DONT_SYNC, - /* mandatory_mask = */ 0, + /* xstatx_flags= */ 0, + /* mandatory_mask= */ 0, STATX_BTIME, - /* mandatory_attributes = */ 0, + /* mandatory_attributes= */ 0, &sx); if (r > 0 && sx.stx_btime.tv_sec != 0) /* > 0: all optional masks are supported */ a = statx_timestamp_load(&sx.stx_btime); diff --git a/src/battery-check/battery-check.c b/src/battery-check/battery-check.c index 43ff0e53e0386..706a7d869c53a 100644 --- a/src/battery-check/battery-check.c +++ b/src/battery-check/battery-check.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include /* IWYU pragma: keep */ #include "sd-messages.h" @@ -9,9 +8,11 @@ #include "battery-util.h" #include "build.h" #include "fd-util.h" +#include "format-table.h" #include "glyph-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "plymouth-util.h" #include "pretty-print.h" #include "proc-cmdline.h" @@ -27,22 +28,28 @@ static bool arg_doit = true; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-battery-check", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s\n\n" - "%sCheck battery level to see whether there's enough charge.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\nSee the %s for details.\n", + "%sCheck battery level to see whether there's enough charge.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } @@ -70,41 +77,23 @@ static int plymouth_send_message(const char *mode, const char *message) { return 0; } -static int parse_argv(int argc, char * argv[]) { - - enum { - ARG_VERSION = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + FOREACH_OPTION(&state, c, /* ret_a= */ NULL, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state) != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s takes no argument.", program_invocation_short_name); diff --git a/src/binfmt/binfmt.c b/src/binfmt/binfmt.c index ee7d2a4d0711e..f8c2b55595e07 100644 --- a/src/binfmt/binfmt.c +++ b/src/binfmt/binfmt.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -12,8 +11,10 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "path-util.h" #include "pretty-print.h" @@ -108,88 +109,71 @@ static int cat_config(char **files) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-binfmt.service", "8", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" - "Registers binary formats with the kernel.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --cat-config Show configuration files\n" - " --tldr Show non-comment parts of configuration\n" - " --no-pager Do not pipe output into a pager\n" - " --unregister Unregister all existing entries\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n" + "\n%sRegisters binary formats with the kernel.%s\n" + "\nOptions:\n", program_invocation_short_name, - link); + ansi_highlight(), + ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_CAT_CONFIG, - ARG_TLDR, - ARG_NO_PAGER, - ARG_UNREGISTER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "cat-config", no_argument, NULL, ARG_CAT_CONFIG }, - { "tldr", no_argument, NULL, ARG_TLDR }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "unregister", no_argument, NULL, ARG_UNREGISTER }, - {} - }; - - int c; +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_CAT_CONFIG: + OPTION_COMMON_CAT_CONFIG: arg_cat_flags = CAT_CONFIG_ON; break; - case ARG_TLDR: + OPTION_COMMON_TLDR: arg_cat_flags = CAT_TLDR; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_UNREGISTER: + OPTION_LONG("unregister", NULL, "Unregister all existing entries"): arg_unregister = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if ((arg_unregister || arg_cat_flags != CAT_CONFIG_OFF) && argc > optind) + char **args = option_parser_get_args(&state); + + if ((arg_unregister || arg_cat_flags != CAT_CONFIG_OFF) && !strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Positional arguments are not allowed with --cat-config/--tldr or --unregister."); + *ret_args = args; return 1; } @@ -208,7 +192,8 @@ static int binfmt_mounted_and_writable_warn(void) { static int run(int argc, char *argv[]) { int r; - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -221,13 +206,13 @@ static int run(int argc, char *argv[]) { if (arg_unregister) return disable_binfmt(); - if (argc > optind) { + if (!strv_isempty(args)) { r = binfmt_mounted_and_writable_warn(); if (r <= 0) return r; - for (int i = optind; i < argc; i++) - RET_GATHER(r, apply_file(argv[i], false)); + STRV_FOREACH(f, args) + RET_GATHER(r, apply_file(*f, false)); } else { _cleanup_strv_free_ char **files = NULL; diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index 9c717ace9d87d..33fbdbb760832 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" @@ -9,12 +8,15 @@ #include "efivars.h" #include "fd-util.h" #include "find-esp.h" +#include "format-table.h" #include "fs-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-util.h" #include "path-util.h" #include "pretty-print.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "sync-util.h" @@ -25,75 +27,77 @@ static char **arg_path = NULL; STATIC_DESTRUCTOR_REGISTER(arg_path, strv_freep); -static int help(int argc, char *argv[], void *userdata) { +typedef enum Status { + STATUS_GOOD, + STATUS_BAD, + STATUS_INDETERMINATE, +} Status; + +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-bless-boot.service", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options, verbs); + printf("%s [OPTIONS...] COMMAND\n" "\n%sMark the boot process as good or bad.%s\n" - "\nCommands:\n" - " status Show status of current boot loader entry\n" - " good Mark this boot as good\n" - " bad Mark this boot as bad\n" - " indeterminate Undo any marking as good or bad\n" - "\nOptions:\n" - " -h --help Show this help\n" - " --version Print version\n" - " --path=PATH Path to the $BOOT partition (may be used multiple times)\n" - "\nSee the %s for details.\n", + "\nCommands:\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + printf("\nOptions:\n"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_PATH = 0x100, - ARG_VERSION, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "path", required_argument, NULL, ARG_PATH }, - {} - }; +VERB_COMMON_HELP_HIDDEN(help); - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - switch (c) { + OptionParser state = { argc, argv }; + const char *arg; - case 'h': - help(0, NULL, NULL); - return 0; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + switch (c) { + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_PATH: - r = strv_extend(&arg_path, optarg); + OPTION_LONG("path", "PATH", "Path to the $BOOT partition (may be used multiple times)"): + r = strv_extend(&arg_path, arg); if (r < 0) return log_oom(); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } @@ -106,11 +110,28 @@ static int acquire_path(void) { if (!strv_isempty(arg_path)) return 0; - r = find_esp_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &esp_path, NULL, NULL, NULL, NULL, &esp_devid); + r = find_esp_and_warn_full( + /* root= */ NULL, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &esp_path, + /* ret_fd= */ NULL, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid= */ NULL, + &esp_devid); if (r < 0 && r != -ENOKEY) /* ENOKEY means not found, and is the only error the function won't log about on its own */ return r; - r = find_xbootldr_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &xbootldr_path, NULL, &xbootldr_devid); + r = find_xbootldr_and_warn_full( + /* root= */ NULL, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &xbootldr_path, + /* ret_fd= */ NULL, + /* ret_uuid= */ NULL, + &xbootldr_devid); if (r < 0 && r != -ENOKEY) return r; @@ -299,7 +320,7 @@ static int make_good(const char *prefix, const char *suffix, char **ret) { } static int make_bad(const char *prefix, uint64_t done, const char *suffix, char **ret) { - _cleanup_free_ char *bad = NULL; + char *bad; assert(prefix); assert(suffix); @@ -308,26 +329,26 @@ static int make_bad(const char *prefix, uint64_t done, const char *suffix, char /* Generate the path we'd use on bad boots. Let's simply set the 'left' counter to zero, and keep the 'done' * counter. The information might be interesting to boot loaders, after all. */ - if (done == 0) { + if (done == 0) bad = strjoin(prefix, "+0", suffix); - if (!bad) - return -ENOMEM; - } else { - if (asprintf(&bad, "%s+0-%" PRIu64 "%s", prefix, done, suffix) < 0) - return -ENOMEM; - } + else + bad = asprintf_safe("%s+0-%" PRIu64 "%s", prefix, done, suffix); + if (!bad) + return -ENOMEM; - *ret = TAKE_PTR(bad); + *ret = bad; return 0; } -static int verb_status(int argc, char *argv[], void *userdata) { +VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, "Show status of current boot loader entry"); +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL; uint64_t left, done; int r; r = acquire_boot_count_path(&path, &prefix, &left, &done, &suffix); - if (r == -EUNATCH) { /* No boot count in place, then let's consider this a "clean" boot, as "good", "bad" or "indeterminate" don't apply. */ + if (r == -EUNATCH) { /* No boot count in place, then let's consider this a "clean" boot, + * since "good", "bad", or "indeterminate" don't apply. */ puts("clean"); return 0; } @@ -427,12 +448,21 @@ static int rename_in_dir_idempotent(int fd, const char *from, const char *to) { return 1; } -static int verb_set(int argc, char *argv[], void *userdata) { +VERB_FULL(verb_set, "good", NULL, VERB_ANY, 1, 0, STATUS_GOOD, + "Mark this boot as good"); +VERB_FULL(verb_set, "bad", NULL, VERB_ANY, 1, 0, STATUS_BAD, + "Mark this boot as bad"); +VERB_FULL(verb_set, "indeterminate", NULL, VERB_ANY, 1, 0, STATUS_INDETERMINATE, + "Undo any marking as good or bad"); +static int verb_set(int argc, char *argv[], uintptr_t data, void *userdata) { _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL; const char *target, *source1, *source2; uint64_t left, done; + Status status = data; int r; + assert(IN_SET(status, STATUS_GOOD, STATUS_BAD, STATUS_INDETERMINATE)); + r = acquire_boot_count_path(&path, &prefix, &left, &done, &suffix); if (r == -EUNATCH) /* acquire_boot_count_path() won't log on its own for this specific error */ return log_error_errno(r, "Not booted with boot counting in effect."); @@ -452,23 +482,25 @@ static int verb_set(int argc, char *argv[], void *userdata) { return log_oom(); /* Figure out what rename to what */ - if (streq(argv[0], "good")) { + switch (status) { + case STATUS_GOOD: target = good; source1 = path; source2 = bad; /* Maybe this boot was previously marked as 'bad'? */ - } else if (streq(argv[0], "bad")) { + break; + case STATUS_BAD: target = bad; source1 = path; source2 = good; /* Maybe this boot was previously marked as 'good'? */ - } else { - assert(streq(argv[0], "indeterminate")); - + break; + case STATUS_INDETERMINATE: if (left == 0) return log_error_errno(r, "Current boot entry was already marked bad in a previous boot, cannot reset to indeterminate."); target = path; source1 = good; source2 = bad; + break; } STRV_FOREACH(p, arg_path) { @@ -532,20 +564,12 @@ static int verb_set(int argc, char *argv[], void *userdata) { } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "good", VERB_ANY, 1, 0, verb_set }, - { "bad", VERB_ANY, 1, 0, verb_set }, - { "indeterminate", VERB_ANY, 1, 0, verb_set }, - {} - }; - + char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -557,7 +581,7 @@ static int run(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Marking a boot is only supported on EFI systems."); - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/bless-boot/boot-check-no-failures.c b/src/bless-boot/boot-check-no-failures.c index b3018924748f1..bea5e5791665e 100644 --- a/src/bless-boot/boot-check-no-failures.c +++ b/src/bless-boot/boot-check-no-failures.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" @@ -8,63 +7,53 @@ #include "alloc-util.h" #include "build.h" #include "bus-error.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pretty-print.h" static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-boot-check-no-failures.service", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...]\n" - "\n%sVerify system operational state.%s\n\n" - " -h --help Show this help\n" - " --version Print version\n" - "\nSee the %s for details.\n", + "\n%sVerify system operational state.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_PATH = 0x100, - ARG_VERSION, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + + FOREACH_OPTION(&state, c, /* ret_a= */ NULL, /* on_error= */ return c) switch (c) { - case 'h': - help(); - return 0; + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } return 1; diff --git a/src/boot/addon.c b/src/boot/addon.c index 95b29daf5514a..17a361436127d 100644 --- a/src/boot/addon.c +++ b/src/boot/addon.c @@ -8,6 +8,7 @@ DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-addon " GIT_VERSIO /* This is intended to carry data, not to be executed */ +// NOLINTNEXTLINE(misc-use-internal-linkage) EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table); EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table) { return EFI_UNSUPPORTED; diff --git a/src/boot/boot-secret.c b/src/boot/boot-secret.c new file mode 100644 index 0000000000000..54078b51c23bd --- /dev/null +++ b/src/boot/boot-secret.c @@ -0,0 +1,374 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "boot-secret.h" +#include "efi-efivars.h" +#include "efi-log.h" +#include "random-seed.h" +#include "sha256-fundamental.h" +#include "util.h" + +#define BOOT_SECRET_MIXIN_PATH u"\\loader\\boot-secret-mixin" + +/* This maintains a per-system secret that is stored in an EFI variable that is only accessible during EFI + * boot, and becomes inaccessible afterwards, once ExitBootServices() is called. The variable is + * automatically initialized if missing. A secret derived by hashing from this EFI variable secret is then + * passed to the OS, in an initrd file inaccessible to unprivileged userspace. To make things a bit more + * robust while hashing two more pieces of information are mixed in: a random "mixin" that is stored in the + * ESP and is supposed to ensure that the passed boot secrets are distinct for each disk used on the system; + * moreover an OS identifier derived from the UKI's .osrel field (ideally IMAGE_ID=, but if not defined ID= + * will do, with a final fallback to "linux"). Note that these two additions are not supposed to enhance the + * cryptographic quality of the secret, they are just supposed to make things more robust on systems with + * multiple disks and OSes. + * + * The boot secret passed to the OS can be used to protect resources during OS runtime, from earliest boot + * phases on, as a fallback for the usual TPM based protections. + * + * Note that this secret comes with much weaker protection than TPM backed secrets: there's no physical + * isolation, there are no cryptographic access policies, there's just the hope the firmware reasonably + * correctly implements boot-time-only EFI variable mechanism. (But then again, this is what mok/shim's + * security also relies on, and hence this all is not too bad?) */ + +static EFI_STATUS random_seed_find_table(struct linux_efi_random_seed **ret) { + assert(ret); + + /* We use the Linux random seed EFI table as our source of randomness, since there's reason to + * believe it is as good as it possibly would get. Note that we ourselves might be the ones + * initializing it, based on EFI RNG APIs, the monotonic boot counter, a random seed file on disk and + * the clock. */ + + struct linux_efi_random_seed *seed_table = + find_configuration_table(MAKE_GUID_PTR(LINUX_EFI_RANDOM_SEED_TABLE)); + if (!seed_table) + return log_debug_status(EFI_NOT_FOUND, "No random seed available, not creating a boot secret."); + if (seed_table->size < BOOT_SECRET_SIZE) + return log_debug_status(EFI_NOT_FOUND, "Random seed is available, but too short."); + + *ret = seed_table; + return EFI_SUCCESS; +} + +static void random_seed_evolve(struct linux_efi_random_seed *seed_table) { + static const char label[] = "systemd-stub random seed evolve label v1"; + + assert(seed_table); + + /* Whenever we derived something from the Linux random seed EFI table we evolve the secret in it, so + * that the seed is never reused. */ + + struct sha256_ctx hash; + CLEANUP_ERASE(hash); + sha256_init_ctx(&hash); + sha256_process_bytes(label, sizeof(label) - 1, &hash); + sha256_process_bytes(&seed_table->size, sizeof(seed_table->size), &hash); + sha256_process_bytes(seed_table->seed, seed_table->size, &hash); + assert(seed_table->size >= SHA256_DIGEST_SIZE); + sha256_finish_ctx(&hash, seed_table->seed); +} + +static void random_seed_make_secret( + struct linux_efi_random_seed *seed_table, + uint8_t ret_secret[static BOOT_SECRET_SIZE]) { + + static const char label[] = "systemd-stub random seed make secret label v1"; + + assert(seed_table); + assert(ret_secret); + + /* Derive a new secret from the Linux random seed EFI table data */ + + struct sha256_ctx hash; + CLEANUP_ERASE(hash); + sha256_init_ctx(&hash); + sha256_process_bytes(label, sizeof(label) - 1, &hash); + sha256_process_bytes(&seed_table->size, sizeof(seed_table->size), &hash); + sha256_process_bytes(seed_table->seed, seed_table->size, &hash); + sha256_finish_ctx(&hash, ret_secret); + + random_seed_evolve(seed_table); /* ← ensure the same seed is not reused */ +} + +static EFI_STATUS read_efivar_secret(uint8_t ret_secret[static BOOT_SECRET_SIZE]) { + EFI_STATUS err; + + assert(ret_secret); + + /* Reads the boot secret from the EFI variable, ensuring it's properly protected from the OS, as per + * the attribute flags */ + + _cleanup_free_ void* data = NULL; + uint32_t attributes; + size_t size = 0; + err = efivar_get_raw_full(MAKE_GUID_PTR(LOADER), u"LoaderBootSecret", &attributes, &data, &size); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to read LoaderBootSecret EFI variable: %m"); + + if (size != BOOT_SECRET_SIZE) { + err = log_debug_status(EFI_PROTOCOL_ERROR, "Unexpected size of BootSecret EFI variable, ignoring."); + goto finish; + } + + if ((attributes & (EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS)) != + (EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS)) { + err = log_debug_status(EFI_PROTOCOL_ERROR, "Unexpected attributes of BootSecret EFI variable, ignoring."); + goto finish; + } + + memcpy(ret_secret, data, size); + err = EFI_SUCCESS; +finish: + explicit_bzero_safe(data, size); + return err; +} + +static EFI_STATUS setup_efivar_secret( + struct linux_efi_random_seed *seed_table, + uint8_t ret_secret[static BOOT_SECRET_SIZE]) { + + EFI_STATUS err; + + assert(seed_table); + assert(ret_secret); + + /* Generates a new EFI variable secret, and stores it in an EFI variable. */ + + uint8_t secret[BOOT_SECRET_SIZE]; + CLEANUP_ERASE(secret); + random_seed_make_secret(seed_table, secret); + + /* Set the variable with the EFI_VARIABLE_RUNTIME_ACCESS flag off (!), so that it's invisible after + * ExitBootServices()! */ + err = RT->SetVariable( + (char16_t*) u"LoaderBootSecret", + MAKE_GUID_PTR(LOADER), + EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS, /* ← No EFI_VARIABLE_RUNTIME_ACCESS here */ + sizeof(secret), + secret); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to set boot secret EFI variable: %m"); + + memcpy(ret_secret, secret, sizeof(secret)); + return EFI_SUCCESS; +} + +static EFI_STATUS acquire_efivar_secret( + struct linux_efi_random_seed *seed_table, + uint8_t ret_secret[static BOOT_SECRET_SIZE]) { + + EFI_STATUS err; + + assert(seed_table); + assert(ret_secret); + + /* Try to read the boot secret EFI variable, but if it doesn't exist create a new one */ + + err = read_efivar_secret(ret_secret); + if (err != EFI_NOT_FOUND) + return err; + + return setup_efivar_secret(seed_table, ret_secret); +} + +static EFI_STATUS setup_secret_mixin( + EFI_FILE *handle, + struct linux_efi_random_seed *seed_table, + uint8_t ret_mixin[static BOOT_SECRET_SIZE]) { + + EFI_STATUS err; + + assert(handle); + assert(seed_table); + assert(ret_mixin); + + /* This writes a new 'mixin' to the ESP, in case the ESP so far had none */ + + uint8_t mixin[BOOT_SECRET_SIZE]; + random_seed_make_secret(seed_table, mixin); + + size_t wsize = sizeof(mixin); + err = handle->Write(handle, &wsize, mixin); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to write secret mixin file: %m"); + if (wsize != sizeof(mixin)) + return log_debug_status(EFI_LOAD_ERROR, "Short write while writing secret mixin file: %m"); + + err = handle->Flush(handle); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to flush secret mixin file: %m"); + + memcpy(ret_mixin, mixin, sizeof(mixin)); + return EFI_SUCCESS; +} + +static EFI_STATUS acquire_secret_mixin( + EFI_FILE *root_dir, + struct linux_efi_random_seed *seed_table, + uint8_t ret_mixin[static BOOT_SECRET_SIZE]) { + + EFI_STATUS err; + + assert(seed_table); + assert(ret_mixin); + + if (!root_dir) + return EFI_NOT_FOUND; + + /* Acquires the mixin for the boot secret stored in the ESP. If it already exists we'll read it. If + * it doesn't we'll initialize it */ + + bool writable; + _cleanup_file_close_ EFI_FILE *handle = NULL; + err = root_dir->Open( + root_dir, + &handle, + (char16_t *) BOOT_SECRET_MIXIN_PATH, + EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, + /* Attributes= */ 0); + if (err == EFI_WRITE_PROTECTED) { + err = root_dir->Open( + root_dir, + &handle, + (char16_t *) BOOT_SECRET_MIXIN_PATH, + EFI_FILE_MODE_READ, + /* Attributes= */ 0); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to read the boot secret mixin file '%ls': %m", BOOT_SECRET_MIXIN_PATH); + + writable = false; + } else if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to access the boot secret mixin file '%ls': %m", BOOT_SECRET_MIXIN_PATH); + else + writable = true; + + _cleanup_free_ EFI_FILE_INFO *info = NULL; + err = get_file_info(handle, &info, /* ret_size= */ NULL); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to get boot secret mixin file '%ls' info: %m", BOOT_SECRET_MIXIN_PATH); + if (info->FileSize == 0 && writable) /* New file? Fill it. */ + return setup_secret_mixin(handle, seed_table, ret_mixin); + + /* If the mixin file is too small we won't overwrite it (in order to not destroy some potentially + * load bearing key), but we won't use it either. */ + if (info->FileSize < BOOT_SECRET_SIZE) + return log_debug_status(EFI_PROTOCOL_ERROR, "Boot secret mixin file '%ls' is too short %" PRIu64 " < %u", BOOT_SECRET_MIXIN_PATH, info->FileSize, BOOT_SECRET_SIZE); + + uint8_t mixin[BOOT_SECRET_SIZE]; + size_t rsize = sizeof(mixin); + err = handle->Read(handle, &rsize, mixin); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to read boot secret mixin file '%ls': %m", BOOT_SECRET_MIXIN_PATH); + if (rsize != BOOT_SECRET_SIZE) + return log_debug_status(EFI_PROTOCOL_ERROR, "Unexpected size from Read(): %zu != %zu", rsize, sizeof(mixin)); + + memcpy(ret_mixin, mixin, BOOT_SECRET_SIZE); + return EFI_SUCCESS; +} + +static char* pick_id(const char *_osrel, size_t osrel_size) { + assert(_osrel || osrel_size == 0); + + /* Make a NUL terminated copy we can chop into pieces */ + _cleanup_free_ char *osrel = NULL; + osrel = xmalloc(osrel_size + 1); + if (osrel_size > 0) + memcpy(osrel, _osrel, osrel_size); + osrel[osrel_size] = 0; + + /* Find an OS ID. Preferably the IMAGE_ID. */ + _cleanup_free_ char *os_id = NULL; + char *line, *key, *value; + size_t pos = 0; + while ((line = line_get_key_value(osrel, "=", &pos, &key, &value))) { + if (streq8(key, "IMAGE_ID")) + return xstrdup8(value); + + if (streq8(key, "ID")) { + free(os_id); + os_id = xstrdup8(value); + } + } + + /* If the IMAGE_ID= wasn't set, use the OS ID=. If that one isn't set either fall back to "linux". */ + return TAKE_PTR(os_id) ?: xstrdup8("linux"); +} + +static void derive_secret( + uint8_t efivar_secret[static BOOT_SECRET_SIZE], + uint8_t secret_mixin[static BOOT_SECRET_SIZE], + const char *id, + uint8_t ret[static BOOT_SECRET_SIZE]) { + + static const char hash_label[] = "systemd-stub derive secret label v1"; + + assert(efivar_secret); + assert(secret_mixin); + assert(id); + assert(ret); + + /* Now combine the EFI variable secret, the mixin from the ESP and the OS id to generate the secret + * to pass to the OS */ + + struct sha256_ctx hash; + CLEANUP_ERASE(hash); + sha256_init_ctx(&hash); + sha256_process_bytes(hash_label, sizeof(hash_label) - 1, &hash); + sha256_process_bytes(efivar_secret, BOOT_SECRET_SIZE, &hash); + sha256_process_bytes(secret_mixin, BOOT_SECRET_SIZE, &hash); + + /* Include an OS id in the hash, so that every OS gets a different derived secret */ + size_t size = strlen8(id); + sha256_process_bytes(&size, sizeof(size), &hash); + sha256_process_bytes(id, size, &hash); + + assert_cc(SHA256_DIGEST_SIZE == BOOT_SECRET_SIZE); + sha256_finish_ctx(&hash, ret); +} + +EFI_STATUS prepare_boot_secret( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector *osrel_section, + uint8_t ret[static BOOT_SECRET_SIZE]) { + + EFI_STATUS err; + + assert(loaded_image); + assert(ret); + + /* Prepares the boot secret to pass to the OS */ + + if (!loaded_image->DeviceHandle) + return EFI_SUCCESS; + + _cleanup_file_close_ EFI_FILE *root = NULL; + err = open_volume(loaded_image->DeviceHandle, &root); + if (err != EFI_SUCCESS) + return err; + + /* We need the Linux random seed EFI table, so that we can initialize the EFI variable secret and + * generate the secret mixin. */ + struct linux_efi_random_seed *seed_table = NULL; + err = random_seed_find_table(&seed_table); + if (err != EFI_SUCCESS) + return err; + + uint8_t efivar_secret[BOOT_SECRET_SIZE]; + CLEANUP_ERASE(efivar_secret); + err = acquire_efivar_secret(seed_table, efivar_secret); + if (err != EFI_SUCCESS) + return err; + + uint8_t secret_mixin[BOOT_SECRET_SIZE]; + err = acquire_secret_mixin(root, seed_table, secret_mixin); + if (err != EFI_SUCCESS) + return err; + + const char *osrel = NULL; + size_t osrel_size = 0; + if (PE_SECTION_VECTOR_IS_SET(osrel_section)) { + osrel = (const char*) loaded_image->ImageBase + osrel_section->memory_offset; + osrel_size = osrel_section->memory_size; + } + _cleanup_free_ char *id = pick_id(osrel, osrel_size); + + derive_secret(efivar_secret, secret_mixin, id, ret); + return EFI_SUCCESS; +} diff --git a/src/boot/boot-secret.h b/src/boot/boot-secret.h new file mode 100644 index 0000000000000..d3e9f53d9ceec --- /dev/null +++ b/src/boot/boot-secret.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" +#include "pe.h" +#include "proto/loaded-image.h" + +#define BOOT_SECRET_SIZE 32U + +EFI_STATUS prepare_boot_secret( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector *osrel_section, + uint8_t ret[static BOOT_SECRET_SIZE]); diff --git a/src/boot/boot.c b/src/boot/boot.c index cdf36b9520305..df5ce31fa3a3f 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -20,6 +20,7 @@ #include "part-discovery.h" #include "pe.h" #include "proto/block-io.h" +#include "proto/disk-io.h" #include "proto/load-file.h" #include "proto/simple-text-io.h" #include "random-seed.h" @@ -61,6 +62,9 @@ typedef enum LoaderType { LOADER_SECURE_BOOT_KEYS, LOADER_BAD, /* Marker: this boot loader spec type #1 entry is invalid */ LOADER_IGNORE, /* Marker: this boot loader spec type #1 entry does not match local host */ + LOADER_REBOOT, + LOADER_POWEROFF, + LOADER_FWSETUP, _LOADER_TYPE_MAX, } LoaderType; @@ -82,6 +86,9 @@ typedef enum LoaderType { /* Whether to persistently save the selected entry in an EFI variable, if that's requested. */ #define LOADER_TYPE_SAVE_ENTRY(t) IN_SET(t, LOADER_AUTO, LOADER_EFI, LOADER_LINUX, LOADER_UKI, LOADER_UKI_URL, LOADER_TYPE2_UKI) +/* Whether this item is implemented fully inside of systemd-boot */ +#define LOADER_TYPE_IS_INTERNAL(t) IN_SET(t, LOADER_SECURE_BOOT_KEYS, LOADER_REBOOT, LOADER_POWEROFF, LOADER_FWSETUP) + typedef enum { REBOOT_NO, REBOOT_YES, @@ -319,9 +326,13 @@ static void print_status(Config *config, char16_t *loaded_image_path) { secure_boot_mode_to_string(secure)); printf(" shim: %ls\n", yes_no(shim_loaded())); printf(" TPM: %ls\n", yes_no(tpm_present())); - printf(" console mode: %i/%" PRIi64 " (%zux%zu @%ux%u)\n", - ST->ConOut->Mode->Mode, ST->ConOut->Mode->MaxMode - INT64_C(1), - x_max, y_max, screen_width, screen_height); + printf(" console mode: %i/%" PRIi64 " (%zux%zu", + ST->ConOut->Mode->Mode, ST->ConOut->Mode->MaxMode - INT64_C(1), + x_max, y_max); + if (screen_width > 0 && screen_height > 0) + printf(" @ %ux%u", + screen_width, screen_height); + printf(")\n"); if (!ps_continue()) return; @@ -419,7 +430,7 @@ static void print_status(Config *config, char16_t *loaded_image_path) { printf(" options: %ls\n", entry->options); if (entry->profile > 0) printf(" profile: %u\n", entry->profile); - printf(" internal call: %ls\n", yes_no(!!entry->call)); + printf(" internal call: %ls\n", yes_no(LOADER_TYPE_IS_INTERNAL(entry->type))); printf("counting boots: %ls\n", yes_no(entry->tries_left >= 0)); if (entry->tries_left >= 0) { @@ -1046,12 +1057,14 @@ static BootEntry* boot_entry_free(BootEntry *entry) { DEFINE_TRIVIAL_CLEANUP_FUNC(BootEntry *, boot_entry_free); static EFI_STATUS config_timeout_sec_from_string(const char *value, uint64_t *dst) { + assert(dst); + if (streq8(value, "menu-disabled")) *dst = TIMEOUT_MENU_DISABLED; else if (streq8(value, "menu-force")) - *dst = TIMEOUT_MENU_DISABLED; + *dst = TIMEOUT_MENU_FORCE; else if (streq8(value, "menu-hidden")) - *dst = TIMEOUT_MENU_DISABLED; + *dst = TIMEOUT_MENU_HIDDEN; else { uint64_t u; if (!parse_number8(value, &u, NULL) || u > TIMEOUT_TYPE_MAX) @@ -1555,6 +1568,7 @@ static void config_load_defaults(Config *config, EFI_FILE *root_dir) { EFI_STATUS err; assert(root_dir); + assert(config); *config = (Config) { .editor = true, @@ -1732,6 +1746,12 @@ static void config_load_smbios_entries( } } +static unsigned boot_entry_profile(const BootEntry *a) { + assert(a); + + return a->profile == UINT_MAX ? 0 : a->profile; +} + static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { int r; @@ -1765,6 +1785,10 @@ static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { r = -strverscmp_improved(a->version, b->version); if (r != 0) return r; + + r = CMP(boot_entry_profile(a), boot_entry_profile(b)); + if (r != 0) + return r; } /* Now order by ID. The version is likely part of the ID, thus note that this will generatelly put @@ -1779,7 +1803,7 @@ static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { /* Note: the strverscmp_improved() call above checked for us that we are looking at the very * same id, hence at this point we only need to compare profile numbers, since we know they * belong to the same UKI. */ - r = CMP(a->profile, b->profile); + r = CMP(boot_entry_profile(a), boot_entry_profile(b)); if (r != 0) return r; } @@ -1894,11 +1918,15 @@ static void config_select_default_entry(Config *config) { } /* select the first suitable entry */ - for (i = 0; i < config->n_entries; i++) + for (i = 0; i < config->n_entries; i++) { + if (config->entries[i]->profile > 0) + continue; /* For now, never select any non-default profile */ + if (LOADER_TYPE_MAY_AUTO_SELECT(config->entries[i]->type)) { config->idx_default = i; return; } + } /* If no configured entry to select from was found, enable the menu. */ config->idx_default = 0; @@ -2142,22 +2170,19 @@ static EFI_STATUS call_boot_windows_bitlocker(const BootEntry *entry, EFI_FILE * if (err != EFI_SUCCESS || block_io->Media->BlockSize < 512 || block_io->Media->BlockSize > 4096) continue; - #define BLOCK_IO_BUFFER_SIZE 4096 - _cleanup_pages_ Pages buf_pages = xmalloc_aligned_pages( - AllocateMaxAddress, - EfiLoaderData, - EFI_SIZE_TO_PAGES(BLOCK_IO_BUFFER_SIZE), - block_io->Media->IoAlign, - /* On 32-bit allocate below 4G boundary as we can't easily access anything above that. - * 64-bit platforms don't suffer this limitation, so we can allocate from anywhere. - * addr= */ UINTPTR_MAX); - char *buf = PHYSICAL_ADDRESS_TO_POINTER(buf_pages.addr); - - err = block_io->ReadBlocks(block_io, block_io->Media->MediaId, /* LBA= */ 0, BLOCK_IO_BUFFER_SIZE, buf); + EFI_DISK_IO_PROTOCOL *disk_io; + err = BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_DISK_IO_PROTOCOL), (void **) &disk_io); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to get disk I/O protocol: %m"); + continue; + } + + char buf[STRLEN("-FVE-FS-")]; + err = disk_io->ReadDisk(disk_io, block_io->Media->MediaId, /* Offset= */ 3, sizeof(buf), buf); if (err != EFI_SUCCESS) continue; - if (memcmp(buf + 3, "-FVE-FS-", STRLEN("-FVE-FS-")) == 0) { + if (memcmp(buf, "-FVE-FS-", STRLEN("-FVE-FS-")) == 0) { found = true; break; } @@ -2544,7 +2569,7 @@ static EFI_STATUS initrd_prepare( assert(ret_initrd_pages); assert(ret_initrd_size); - if (entry->type != LOADER_LINUX || !entry->initrd) { + if (entry->type != LOADER_LINUX || strv_isempty(entry->initrd)) { *ret_options = NULL; *ret_initrd_pages = (Pages) {}; *ret_initrd_size = 0; @@ -2694,7 +2719,12 @@ static EFI_STATUS expand_path( if (IN_SET(err, EFI_NOT_FOUND, EFI_INVALID_PARAMETER)) continue; /* Skip over LoadFile() handles that after all don't consider themselves * appropriate for this kind of path */ - if (err != EFI_BUFFER_TOO_SMALL) { + if (!IN_SET(err, EFI_SUCCESS, EFI_BUFFER_TOO_SMALL)) { + /* NB: firmwares are supposed to return EFI_BUFFER_TOO_SMALL whenever we pass a NULL + * buffer. But for compatibility with quirky firmwares let's be lenient for the + * special case of a zero sized file: the firmware might return EFI_SUCCESS here and + * initialize the size to zero, as a buffer is not actually necessary for that + * case. */ log_warning_status(err, "Failed to get file via LoadFile() protocol, ignoring: %m"); continue; } @@ -3044,6 +3074,7 @@ static void config_add_system_entries(Config *config) { if (config->auto_firmware && FLAGS_SET(get_os_indications_supported(), EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) { BootEntry *entry = xnew(BootEntry, 1); *entry = (BootEntry) { + .type = LOADER_FWSETUP, .id = xstrdup16(u"auto-reboot-to-firmware-setup"), .title = xstrdup16(u"Reboot Into Firmware Interface"), .call = call_reboot_into_firmware, @@ -3056,6 +3087,7 @@ static void config_add_system_entries(Config *config) { if (config->auto_poweroff) { BootEntry *entry = xnew(BootEntry, 1); *entry = (BootEntry) { + .type = LOADER_POWEROFF, .id = xstrdup16(u"auto-poweroff"), .title = xstrdup16(u"Power Off The System"), .call = call_poweroff_system, @@ -3068,6 +3100,7 @@ static void config_add_system_entries(Config *config) { if (config->auto_reboot) { BootEntry *entry = xnew(BootEntry, 1); *entry = (BootEntry) { + .type = LOADER_REBOOT, .id = xstrdup16(u"auto-reboot"), .title = xstrdup16(u"Reboot The System"), .call = call_reboot_system, @@ -3255,4 +3288,5 @@ static EFI_STATUS run(EFI_HANDLE image) { } } +// NOLINTNEXTLINE(misc-use-internal-linkage) DEFINE_EFI_MAIN_FUNCTION(run, "systemd-boot", /* wait_for_debugger= */ false); diff --git a/src/boot/chid.c b/src/boot/chid.c index 28f2b7b898435..8abd9de47ceea 100644 --- a/src/boot/chid.c +++ b/src/boot/chid.c @@ -99,6 +99,8 @@ static EFI_STATUS populate_board_chids(EFI_GUID ret_chids[static CHID_TYPES_MAX] EFI_STATUS chid_match(const void *hwid_buffer, size_t hwid_length, uint32_t match_type, const Device **ret_device) { EFI_STATUS status; + assert(ret_device); + if ((uintptr_t) hwid_buffer % alignof(Device) != 0) return EFI_INVALID_PARAMETER; diff --git a/src/boot/console.c b/src/boot/console.c index 21b36e5a6e5f9..8dcd986aa4f15 100644 --- a/src/boot/console.c +++ b/src/boot/console.c @@ -1,8 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "console.h" +#include "device-path-util.h" #include "efi-log.h" +#include "efi-string.h" #include "proto/graphics-output.h" +#include "proto/pci-io.h" +#include "string-util-fundamental.h" +#include "util.h" #define SYSTEM_FONT_WIDTH 8 #define SYSTEM_FONT_HEIGHT 19 @@ -11,6 +16,8 @@ #define VIEWPORT_RATIO 10 static void event_closep(EFI_EVENT *event) { + assert(event); + if (!*event) return; @@ -191,6 +198,9 @@ EFI_STATUS query_screen_resolution(uint32_t *ret_w, uint32_t *ret_h) { EFI_STATUS err; EFI_GRAPHICS_OUTPUT_PROTOCOL *go; + assert(ret_w); + assert(ret_h); + err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_GRAPHICS_OUTPUT_PROTOCOL), NULL, (void **) &go); if (err != EFI_SUCCESS) return err; @@ -342,3 +352,251 @@ EFI_STATUS console_query_mode(size_t *x_max, size_t *y_max) { return err; } + +static bool has_virtio_console_pci_device(void) { + _cleanup_free_ EFI_HANDLE *handles = NULL; + size_t n_handles = 0; + + EFI_STATUS err = BS->LocateHandleBuffer( + ByProtocol, + MAKE_GUID_PTR(EFI_PCI_IO_PROTOCOL), + NULL, + &n_handles, + &handles); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to locate PCI I/O protocol handles, assuming no VirtIO console: %m"); + return false; + } + + if (n_handles == 0) { + log_debug("No PCI devices found, not scanning for VirtIO console."); + return false; + } + + log_debug("Found %zu PCI devices, scanning for VirtIO console...", n_handles); + + size_t n_virtio_console = 0; + + for (size_t i = 0; i < n_handles; i++) { + EFI_PCI_IO_PROTOCOL *pci_io = NULL; + + if (BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_PCI_IO_PROTOCOL), (void **) &pci_io) != EFI_SUCCESS) + continue; + + /* Read PCI vendor ID and device ID (at offsets 0x00 and 0x02 in PCI config space) */ + uint16_t pci_id[2] = {}; + if (pci_io->Pci.Read(pci_io, EfiPciIoWidthUint16, /* offset= */ 0x00, /* count= */ 2, pci_id) != EFI_SUCCESS) + continue; + + log_debug("PCI device %zu: vendor=%04x device=%04x", i, pci_id[0], pci_id[1]); + + if (pci_id[0] == PCI_VENDOR_ID_REDHAT && pci_id[1] == PCI_DEVICE_ID_VIRTIO_CONSOLE) + n_virtio_console++; + + if (n_virtio_console > 1) { + log_debug("There is more than one VirtIO console PCI device, cannot determine which one is the console."); + return false; + } + } + + if (n_virtio_console == 0) { + log_debug("No VirtIO console PCI device found."); + return false; + } + + log_debug("Found exactly one VirtIO console PCI device."); + return true; +} + +static bool has_graphics_output(void) { + EFI_GRAPHICS_OUTPUT_PROTOCOL *gop = NULL; + EFI_STATUS err; + + err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_GRAPHICS_OUTPUT_PROTOCOL), NULL, (void **) &gop); + if (err != EFI_SUCCESS) { + log_debug_status(err, "No EFI Graphics Output Protocol found: %m"); + return false; + } + + log_debug("EFI Graphics Output Protocol found."); + return true; +} + +#if defined(__i386__) || defined(__x86_64__) + +/* Walk the device path looking for a UART console and determine the COM port index from the + * ACPI device path node. On x86, the Linux kernel assigns fixed ttyS indices based on I/O port + * addresses (see arch/x86/include/asm/serial.h): + * + * ttyS0=0x3F8, ttyS1=0x2F8, ttyS2=0x3E8, ttyS3=0x2E8 + * + * On standard PC firmware, the ACPI UID for PNP0501 (16550 UART) maps directly to the COM port + * index: UID 0 = COM1 (0x3F8) = ttyS0, UID 1 = COM2 (0x2F8) = ttyS1, etc. + * + * Returns EFI_SUCCESS and sets *ret_index on success, or EFI_NOT_FOUND if no PNP0501 UART + * was found. */ +static EFI_STATUS device_path_get_uart_index(const EFI_DEVICE_PATH *dp, uint32_t *ret_index) { + assert(ret_index); + + for (const EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node)) + if (node->Type == ACPI_DEVICE_PATH && + node->SubType == ACPI_DP && + node->Length >= sizeof(ACPI_HID_DEVICE_PATH)) { + const ACPI_HID_DEVICE_PATH *acpi = (const ACPI_HID_DEVICE_PATH *) node; + if (acpi->HID == EISA_PNP_ID(0x0501)) { + *ret_index = acpi->UID; + return EFI_SUCCESS; + } + } + + return EFI_NOT_FOUND; +} + +/* Check if the console output is a serial UART. If so, determine the COM port index from the + * ACPI device path so we can pass the correct console= device to the kernel. */ +static EFI_STATUS find_serial_console_index(uint32_t *ret_index) { + assert(ret_index); + + /* First try the ConOut handle directly. */ + EFI_DEVICE_PATH *dp = NULL; + if (BS->HandleProtocol(ST->ConsoleOutHandle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp) == EFI_SUCCESS) { + _cleanup_free_ char16_t *dp_str = NULL; + (void) device_path_to_str(dp, &dp_str); + log_debug("ConOut device path: %ls", strempty(dp_str)); + + if (device_path_get_uart_index(dp, ret_index) == EFI_SUCCESS) { + log_debug("ConOut is a serial console (port index %u).", *ret_index); + return EFI_SUCCESS; + } + + log_debug("ConOut device path does not contain a PNP0501 UART node."); + return EFI_NOT_FOUND; + } + + /* ConOut handle has no device path (e.g. ConSplitter virtual handle). Enumerate all + * text output handles and check if any of them is a serial console. */ + log_debug("ConOut handle has no device path, enumerating text output handles..."); + + _cleanup_free_ EFI_HANDLE *handles = NULL; + size_t n_handles = 0; + if (BS->LocateHandleBuffer( + ByProtocol, + MAKE_GUID_PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL), + NULL, + &n_handles, + &handles) != EFI_SUCCESS) { + log_debug("Failed to enumerate text output handles."); + return EFI_NOT_FOUND; + } + + bool found = false; + + for (size_t i = 0; i < n_handles; i++) { + dp = NULL; + if (BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp) != EFI_SUCCESS) + continue; + + _cleanup_free_ char16_t *dp_str = NULL; + (void) device_path_to_str(dp, &dp_str); + log_debug("Text output handle %zu device path: %ls", i, strempty(dp_str)); + + uint32_t index; + if (device_path_get_uart_index(dp, &index) != EFI_SUCCESS) + continue; + + log_debug("Text output handle %zu is a serial console (port index %u).", i, index); + + if (found && *ret_index != index) { + log_debug("Multiple serial consoles with different port indices found, cannot determine which one to use."); + return EFI_NOT_FOUND; + } + + *ret_index = index; + found = true; + } + + if (!found) { + log_debug("No serial console found among text output handles."); + return EFI_NOT_FOUND; + } + + return EFI_SUCCESS; +} + +static const char16_t *serial_console_arg(uint32_t index) { + /* Use the uart I/O port address format (see Documentation/admin-guide/kernel-parameters.txt) + * instead of ttyS names. This addresses the 8250/16550 UART at the specified I/O port + * directly and switches to the matching ttyS device later. The I/O port addresses for + * the standard COM ports are fixed (see arch/x86/include/asm/serial.h), and the ACPI UID + * for PNP0501 maps directly to the COM port index. */ + static const char16_t *const table[] = { + u"console=uart,io,0x3f8", /* COM1 */ + u"console=uart,io,0x2f8", /* COM2 */ + u"console=uart,io,0x3e8", /* COM3 */ + u"console=uart,io,0x2e8", /* COM4 */ + }; + + if (index >= ELEMENTSOF(table)) + return NULL; + + return table[index]; +} + +#endif /* __i386__ || __x86_64__ */ + +/* If there's no console= in the command line yet, try to detect the appropriate console device. + * + * Detection order: + * 1. If exactly one VirtIO console PCI device exists -> console=hvc0 + * 2. If there's graphical output (GOP) -> don't add console=, the kernel defaults are fine + * 3. On x86, if exactly one serial console exists -> console=uart,io, + * 4. Otherwise -> don't add console=, let the user handle it + * + * VirtIO console takes priority since it's explicitly configured by the VMM. Graphics is + * checked before serial to avoid accidentally redirecting output away from a graphical + * console by adding a serial console= argument. + * + * Serial console auto-detection is restricted to x86 where ACPI PNP0501 UIDs map to fixed + * I/O port addresses for 8250/16550 UARTs. On non-x86 (e.g. ARM), serial device indices are + * assigned dynamically, and the kernel has its own console auto-detection mechanisms + * (DT stdout-path, etc.). + * + * Not TPM-measured because the value is deterministically derived from firmware-reported + * hardware state (PCI device enumeration, GOP presence, serial device paths). */ +void cmdline_append_console(char16_t **cmdline) { + assert(cmdline); + + if (*cmdline && (efi_fnmatch(u"console=*", *cmdline) || efi_fnmatch(u"* console=*", *cmdline))) { + log_debug("Kernel command line already contains console=, not adding one."); + return; + } + + const char16_t *console_arg = NULL; + + if (has_virtio_console_pci_device()) + console_arg = u"console=hvc0"; + else if (has_graphics_output()) { + log_debug("Graphical output available, not adding console= to kernel command line."); + return; + } +#if defined(__i386__) || defined(__x86_64__) + else { + uint32_t serial_index; + if (find_serial_console_index(&serial_index) == EFI_SUCCESS) + console_arg = serial_console_arg(serial_index); + } +#endif + + if (!console_arg) { + log_debug("Cannot determine console type, not adding console= to kernel command line."); + return; + } + + log_debug("Appending %ls to kernel command line.", console_arg); + + _cleanup_free_ char16_t *old = TAKE_PTR(*cmdline); + if (isempty(old)) + *cmdline = xstrdup16(console_arg); + else + *cmdline = xasprintf("%ls %ls", old, console_arg); +} diff --git a/src/boot/console.h b/src/boot/console.h index 4d0d1364d8fa0..3a2bc6391cdce 100644 --- a/src/boot/console.h +++ b/src/boot/console.h @@ -36,3 +36,4 @@ EFI_STATUS console_key_read(uint64_t *ret_key, uint64_t timeout_usec); EFI_STATUS console_set_mode(int64_t mode); EFI_STATUS console_query_mode(size_t *x_max, size_t *y_max); EFI_STATUS query_screen_resolution(uint32_t *ret_width, uint32_t *ret_height); +void cmdline_append_console(char16_t **cmdline); diff --git a/src/boot/cpio.c b/src/boot/cpio.c index 8a15253dedad1..81792b00a89f4 100644 --- a/src/boot/cpio.c +++ b/src/boot/cpio.c @@ -8,7 +8,7 @@ #include "util.h" static char *write_cpio_word(char *p, uint32_t v) { - static const char hex[] = "0123456789abcdef"; + const char *hex = LOWERCASE_HEXDIGITS; assert(p); @@ -52,12 +52,11 @@ static char *pad4(char *p, const char *start) { return p; } -static EFI_STATUS pack_cpio_one( +EFI_STATUS pack_cpio_one( const char16_t *fname, const void *contents, size_t contents_size, - const char *target_dir_prefix, - uint32_t access_mode, + const CpioTarget *target, uint32_t *inode_counter, void **cpio_buffer, size_t *cpio_buffer_size) { @@ -67,7 +66,7 @@ static EFI_STATUS pack_cpio_one( assert(fname); assert(contents || contents_size == 0); - assert(target_dir_prefix); + assert(target); assert(inode_counter); assert(cpio_buffer); assert(cpio_buffer_size); @@ -84,7 +83,7 @@ static EFI_STATUS pack_cpio_one( l = 6 + 13*8 + 1 + 1; /* Fixed CPIO header size, slash separator, and NUL byte after the file name */ - target_dir_prefix_size = strlen8(target_dir_prefix); + target_dir_prefix_size = strlen8(target->directory); if (l > SIZE_MAX - target_dir_prefix_size) return EFI_OUT_OF_RESOURCES; l += target_dir_prefix_size; @@ -121,11 +120,11 @@ static EFI_STATUS pack_cpio_one( a = mempcpy(a, "070701", 6); /* magic ID */ - a = write_cpio_word(a, (*inode_counter)++); /* inode */ - a = write_cpio_word(a, access_mode | 0100000 /* = S_IFREG */); /* mode */ - a = write_cpio_word(a, 0); /* uid */ - a = write_cpio_word(a, 0); /* gid */ - a = write_cpio_word(a, 1); /* nlink */ + a = write_cpio_word(a, (*inode_counter)++); /* inode */ + a = write_cpio_word(a, target->access_mode | 0100000 /* = S_IFREG */); /* mode */ + a = write_cpio_word(a, 0); /* uid */ + a = write_cpio_word(a, 0); /* gid */ + a = write_cpio_word(a, 1); /* nlink */ /* Note: we don't make any attempt to propagate the mtime here, for two reasons: it's a mess given * that FAT usually is assumed to operate with timezoned timestamps, while UNIX does not. More @@ -141,7 +140,7 @@ static EFI_STATUS pack_cpio_one( a = write_cpio_word(a, target_dir_prefix_size + fname_size + 2); /* fname size */ a = write_cpio_word(a, 0); /* "crc" */ - a = mempcpy(a, target_dir_prefix, target_dir_prefix_size); + a = mempcpy(a, target->directory, target_dir_prefix_size); *(a++) = '/'; a = mangle_filename(a, fname); @@ -225,16 +224,15 @@ static EFI_STATUS pack_cpio_dir( return EFI_SUCCESS; } -static EFI_STATUS pack_cpio_prefix( - const char *path, - uint32_t dir_mode, +EFI_STATUS pack_cpio_prefix( + const CpioTarget *target, uint32_t *inode_counter, void **cpio_buffer, size_t *cpio_buffer_size) { EFI_STATUS err; - assert(path); + assert(target); assert(inode_counter); assert(cpio_buffer); assert(cpio_buffer_size); @@ -243,7 +241,7 @@ static EFI_STATUS pack_cpio_prefix( * (similar to mkdir -p behaviour) all leading paths are created with 0555 access mode, only the * final dir is created with the specified directory access mode. */ - for (const char *p = path;;) { + for (const char *p = target->directory;;) { const char *e; e = strchr8(p, '/'); @@ -253,7 +251,7 @@ static EFI_STATUS pack_cpio_prefix( if (e > p) { _cleanup_free_ char *t = NULL; - t = xstrndup8(path, e - path); + t = xstrndup8(target->directory, e - target->directory); if (!t) return EFI_OUT_OF_RESOURCES; @@ -265,10 +263,10 @@ static EFI_STATUS pack_cpio_prefix( p = e + 1; } - return pack_cpio_dir(path, dir_mode, inode_counter, cpio_buffer, cpio_buffer_size); + return pack_cpio_dir(target->directory, target->dir_mode, inode_counter, cpio_buffer, cpio_buffer_size); } -static EFI_STATUS pack_cpio_trailer( +EFI_STATUS pack_cpio_trailer( void **cpio_buffer, size_t *cpio_buffer_size) { @@ -307,9 +305,7 @@ EFI_STATUS pack_cpio( const char16_t *dropin_dir, const char16_t *match_suffix, const char16_t *exclude_suffix, - const char *target_dir_prefix, - uint32_t dir_mode, - uint32_t access_mode, + const CpioTarget *target, uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, @@ -325,7 +321,7 @@ EFI_STATUS pack_cpio( EFI_STATUS err; assert(loaded_image); - assert(target_dir_prefix); + assert(target); assert(ret_buffer); if (!loaded_image->DeviceHandle) @@ -400,7 +396,7 @@ EFI_STATUS pack_cpio( /* Generate the leading directory inodes right before adding the first files, to the * archive. Otherwise the cpio archive cannot be unpacked, since the leading dirs won't exist. */ - err = pack_cpio_prefix(target_dir_prefix, dir_mode, &inode, &buffer, &buffer_size); + err = pack_cpio_prefix(target, &inode, &buffer, &buffer_size); if (err != EFI_SUCCESS) return log_error_status(err, "Failed to pack cpio prefix: %m"); @@ -417,8 +413,7 @@ EFI_STATUS pack_cpio( err = pack_cpio_one( items[i], content, contentsize, - target_dir_prefix, - access_mode, + target, &inode, &buffer, &buffer_size); if (err != EFI_SUCCESS) @@ -453,10 +448,8 @@ EFI_STATUS pack_cpio( EFI_STATUS pack_cpio_literal( const void *data, size_t data_size, - const char *target_dir_prefix, + const CpioTarget *target, const char16_t *target_filename, - uint32_t dir_mode, - uint32_t access_mode, uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, @@ -468,22 +461,21 @@ EFI_STATUS pack_cpio_literal( EFI_STATUS err; assert(data || data_size == 0); - assert(target_dir_prefix); + assert(target); assert(target_filename); assert(ret_buffer); /* Generate the leading directory inodes right before adding the first files, to the * archive. Otherwise the cpio archive cannot be unpacked, since the leading dirs won't exist. */ - err = pack_cpio_prefix(target_dir_prefix, dir_mode, &inode, &buffer, &buffer_size); + err = pack_cpio_prefix(target, &inode, &buffer, &buffer_size); if (err != EFI_SUCCESS) return log_error_status(err, "Failed to pack cpio prefix: %m"); err = pack_cpio_one( target_filename, data, data_size, - target_dir_prefix, - access_mode, + target, &inode, &buffer, &buffer_size); if (err != EFI_SUCCESS) @@ -505,3 +497,55 @@ EFI_STATUS pack_cpio_literal( *ret_buffer = IOVEC_MAKE(TAKE_PTR(buffer), buffer_size); return EFI_SUCCESS; } + +/* The following are canonical definitions of the various cpio target directories we place resources in. We + * define them here in a single canonical list of targets because we need to reuse them at various places + * (well, some of them at least), and we don't want the access modes to deviate slightly on each use. */ + +const CpioTarget cpio_target_credentials = { + .directory = ".extra/credentials", + .dir_mode = 0500, + .access_mode = 0400, +}; + +const CpioTarget cpio_target_global_credentials = { + .directory = ".extra/global_credentials", + .dir_mode = 0500, + .access_mode = 0400, +}; + +const CpioTarget cpio_target_sysext = { + .directory = ".extra/sysext", + .dir_mode = 0555, + .access_mode = 0444, +}; + +const CpioTarget cpio_target_global_sysext = { + .directory = ".extra/global_sysext", + .dir_mode = 0555, + .access_mode = 0444, +}; + +const CpioTarget cpio_target_confext = { + .directory = ".extra/confext", + .dir_mode = 0555, + .access_mode = 0444, +}; + +const CpioTarget cpio_target_global_confext = { + .directory = ".extra/global_confext", + .dir_mode = 0555, + .access_mode = 0444, +}; + +const CpioTarget cpio_target_meta = { + .directory = ".extra", + .dir_mode = 0555, + .access_mode = 0444, +}; + +const CpioTarget cpio_target_meta_secret = { + .directory = ".extra", + .dir_mode = 0555, + .access_mode = 0400, +}; diff --git a/src/boot/cpio.h b/src/boot/cpio.h index bb741278fdc24..3c311bc714d28 100644 --- a/src/boot/cpio.h +++ b/src/boot/cpio.h @@ -4,14 +4,37 @@ #include "efi.h" #include "proto/loaded-image.h" +typedef struct CpioTarget { + const char *directory; /* Path to directory where to place resources */ + uint32_t dir_mode; /* Access mode for the directory */ + uint32_t access_mode; /* Access mode for the files in the directory */ +} CpioTarget; + +EFI_STATUS pack_cpio_one( + const char16_t *fname, + const void *contents, + size_t contents_size, + const CpioTarget *target, + uint32_t *inode_counter, + void **cpio_buffer, + size_t *cpio_buffer_size); + +EFI_STATUS pack_cpio_prefix( + const CpioTarget *target, + uint32_t *inode_counter, + void **cpio_buffer, + size_t *cpio_buffer_size); + +EFI_STATUS pack_cpio_trailer( + void **cpio_buffer, + size_t *cpio_buffer_size); + EFI_STATUS pack_cpio( EFI_LOADED_IMAGE_PROTOCOL *loaded_image, const char16_t *dropin_dir, const char16_t *match_suffix, const char16_t *exclude_suffix, - const char *target_dir_prefix, - uint32_t dir_mode, - uint32_t access_mode, + const CpioTarget *target, uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, @@ -20,11 +43,18 @@ EFI_STATUS pack_cpio( EFI_STATUS pack_cpio_literal( const void *data, size_t data_size, - const char *target_dir_prefix, + const CpioTarget *target, const char16_t *target_filename, - uint32_t dir_mode, - uint32_t access_mode, uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, bool *ret_measured); + +extern const CpioTarget cpio_target_credentials; +extern const CpioTarget cpio_target_global_credentials; +extern const CpioTarget cpio_target_sysext; +extern const CpioTarget cpio_target_global_sysext; +extern const CpioTarget cpio_target_confext; +extern const CpioTarget cpio_target_global_confext; +extern const CpioTarget cpio_target_meta; +extern const CpioTarget cpio_target_meta_secret; diff --git a/src/boot/devicetree.c b/src/boot/devicetree.c index 85fc07c49f38b..a9dccd2a57c56 100644 --- a/src/boot/devicetree.c +++ b/src/boot/devicetree.c @@ -141,10 +141,10 @@ static const char* devicetree_get_compatible(const void *dtb) { size_t size_words = struct_size / sizeof(uint32_t); size_t len, name_off, len_words, s; - for (size_t i = 0; i < end; i++) { + for (size_t i = 0; i < size_words; i++) { switch (be32toh(cursor[i])) { case FDT_BEGIN_NODE: - if (i >= size_words || cursor[++i] != 0) + if (i + 1 >= size_words || cursor[++i] != 0) return NULL; break; case FDT_NOP: diff --git a/src/boot/efi-efivars.c b/src/boot/efi-efivars.c index 0358a071e07d7..5581d98ec3502 100644 --- a/src/boot/efi-efivars.c +++ b/src/boot/efi-efivars.c @@ -177,7 +177,13 @@ EFI_STATUS efivar_get_uint64_le(const EFI_GUID *vendor, const char16_t *name, ui return EFI_SUCCESS; } -EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const char16_t *name, void **ret_data, size_t *ret_size) { +EFI_STATUS efivar_get_raw_full( + const EFI_GUID *vendor, + const char16_t *name, + uint32_t *ret_attributes, + void **ret_data, + size_t *ret_size) { + EFI_STATUS err; assert(vendor); @@ -188,11 +194,14 @@ EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const char16_t *name, void **r if (err != EFI_BUFFER_TOO_SMALL) return err; + uint32_t attributes = 0; _cleanup_free_ void *buf = xmalloc(size); - err = RT->GetVariable((char16_t *) name, (EFI_GUID *) vendor, NULL, &size, buf); + err = RT->GetVariable((char16_t *) name, (EFI_GUID *) vendor, ret_attributes ? &attributes : NULL, &size, buf); if (err != EFI_SUCCESS) return err; + if (ret_attributes) + *ret_attributes = attributes; if (ret_data) *ret_data = TAKE_PTR(buf); if (ret_size) diff --git a/src/boot/efi-efivars.h b/src/boot/efi-efivars.h index 1e74d6483cf4c..6d88f56e71296 100644 --- a/src/boot/efi-efivars.h +++ b/src/boot/efi-efivars.h @@ -22,7 +22,10 @@ void efivar_set_time_usec(const EFI_GUID *vendor, const char16_t *name, uint64_t EFI_STATUS efivar_unset(const EFI_GUID *vendor, const char16_t *name, uint32_t flags); EFI_STATUS efivar_get_str16(const EFI_GUID *vendor, const char16_t *name, char16_t **ret); -EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const char16_t *name, void **ret_data, size_t *ret_size); +EFI_STATUS efivar_get_raw_full(const EFI_GUID *vendor, const char16_t *name, uint32_t *ret_attributes, void **ret_data, size_t *ret_size); +static inline EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const char16_t *name, void **ret_data, size_t *ret_size) { + return efivar_get_raw_full(vendor, name, NULL, ret_data, ret_size); +} EFI_STATUS efivar_get_uint64_str16(const EFI_GUID *vendor, const char16_t *name, uint64_t *ret); EFI_STATUS efivar_get_uint32_le(const EFI_GUID *vendor, const char16_t *name, uint32_t *ret); EFI_STATUS efivar_get_uint64_le(const EFI_GUID *vendor, const char16_t *name, uint64_t *ret); diff --git a/src/boot/efi-log.c b/src/boot/efi-log.c index 3cecc7be06ae6..ed0a2746933e0 100644 --- a/src/boot/efi-log.c +++ b/src/boot/efi-log.c @@ -132,6 +132,7 @@ void log_wait(void) { log_count = 0; } +// NOLINTNEXTLINE(misc-use-internal-linkage) _used_ intptr_t __stack_chk_guard = (intptr_t) 0x70f6967de78acae3; /* We can only set a random stack canary if this function attribute is available, @@ -147,8 +148,10 @@ void __stack_chk_guard_init(void) { } #endif +// NOLINTBEGIN(misc-use-internal-linkage) _used_ _noreturn_ void __stack_chk_fail(void); _used_ _noreturn_ void __stack_chk_fail_local(void); +// NOLINTEND(misc-use-internal-linkage) void __stack_chk_fail(void) { panic(u"systemd-boot: Stack check failed, halting."); } @@ -157,6 +160,7 @@ void __stack_chk_fail_local(void) { } /* Called by libgcc for some fatal errors like integer overflow with -ftrapv. */ +// NOLINTNEXTLINE(misc-use-internal-linkage) _used_ _noreturn_ void abort(void); void abort(void) { panic(u"systemd-boot: Unknown error, halting."); @@ -164,8 +168,10 @@ void abort(void) { #if defined(__ARM_EABI__) /* These override the (weak) div0 handlers from libgcc as they would otherwise call raise() instead. */ +// NOLINTBEGIN(misc-use-internal-linkage) _used_ _noreturn_ int __aeabi_idiv0(int return_value); _used_ _noreturn_ long long __aeabi_ldiv0(long long return_value); +// NOLINTEND(misc-use-internal-linkage) int __aeabi_idiv0(int return_value) { panic(u"systemd-boot: Division by zero, halting."); diff --git a/src/boot/efi-log.h b/src/boot/efi-log.h index 5458f90109a0b..5dbcb425164ed 100644 --- a/src/boot/efi-log.h +++ b/src/boot/efi-log.h @@ -5,15 +5,12 @@ #include "efi-string.h" #include "proto/simple-text-io.h" /* IWYU pragma: keep */ -#if defined __has_attribute -# if __has_attribute(no_stack_protector) -# define HAVE_NO_STACK_PROTECTOR_ATTRIBUTE -# endif -#endif - -#if defined(HAVE_NO_STACK_PROTECTOR_ATTRIBUTE) && \ - (defined(__SSP__) || defined(__SSP_ALL__) || \ - defined(__SSP_STRONG__) || defined(__SSP_EXPLICIT__)) +#if defined(__has_attribute) && \ + __has_attribute(no_stack_protector) && \ + (defined(__SSP__) || \ + defined(__SSP_ALL__) || \ + defined(__SSP_STRONG__) || \ + defined(__SSP_EXPLICIT__)) # define STACK_PROTECTOR_RANDOM 1 __attribute__((no_stack_protector, noinline)) void __stack_chk_guard_init(void); #else diff --git a/src/boot/efi-string.c b/src/boot/efi-string.c index ee430c1b36a40..d0be7a1e160d3 100644 --- a/src/boot/efi-string.c +++ b/src/boot/efi-string.c @@ -349,6 +349,8 @@ static bool efi_fnmatch_prefix(const char16_t *p, const char16_t *h, const char1 /* Patterns are fnmatch-compatible (with reduced feature support). */ bool efi_fnmatch(const char16_t *pattern, const char16_t *haystack) { + assert(haystack); + /* Patterns can be considered as simple patterns (without '*') concatenated by '*'. By doing so we * simply have to make sure the very first simple pattern matches the start of haystack. Then we just * look for the remaining simple patterns *somewhere* within the haystack (in order) as any extra @@ -510,7 +512,7 @@ char* line_get_key_value(char *s, const char *sep, size_t *pos, char **ret_key, } char16_t *hexdump(const void *data, size_t size) { - static const char hex[] = "0123456789abcdef"; + const char *hex = LOWERCASE_HEXDIGITS; const uint8_t *d = data; assert(data || size == 0); @@ -676,7 +678,7 @@ static bool push_str(FormatContext *ctx, SpecifierContext *sp) { } static bool push_num(FormatContext *ctx, SpecifierContext *sp, uint64_t u) { - const char *digits = sp->lowercase ? "0123456789abcdef" : "0123456789ABCDEF"; + const char *digits = sp->lowercase ? LOWERCASE_HEXDIGITS : UPPERCASE_HEXDIGITS; char16_t tmp[32]; size_t n = 0; @@ -1050,10 +1052,12 @@ char16_t *xvasprintf_status(EFI_STATUS status, const char *format, va_list ap) { # undef memcmp # undef memcpy # undef memset +// NOLINTBEGIN(misc-use-internal-linkage) _used_ void *memchr(const void *p, int c, size_t n); _used_ int memcmp(const void *p1, const void *p2, size_t n); _used_ void *memcpy(void * restrict dest, const void * restrict src, size_t n); _used_ void *memset(void *p, int c, size_t n); +// NOLINTEND(misc-use-internal-linkage) #else /* And for userspace unit testing we need to give them an efi_ prefix. */ # undef memchr diff --git a/src/boot/generate-hwids-section.py b/src/boot/generate-hwids-section.py index 621183c20fba0..cfe6aea739aa3 100755 --- a/src/boot/generate-hwids-section.py +++ b/src/boot/generate-hwids-section.py @@ -20,6 +20,7 @@ #include #include +// NOLINTNEXTLINE(misc-use-internal-linkage) const uint8_t hwids_section_data[] = { """, end='', @@ -34,6 +35,8 @@ print( """}; + +// NOLINTNEXTLINE(misc-use-internal-linkage) const size_t hwids_section_len =""", f'{len(hwids)};', ) diff --git a/src/boot/initrd.c b/src/boot/initrd.c index d8cbe7deed425..044e011f60e89 100644 --- a/src/boot/initrd.c +++ b/src/boot/initrd.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "efi-log.h" #include "initrd.h" #include "iovec-util-fundamental.h" #include "proto/device-path.h" @@ -70,9 +71,6 @@ EFI_STATUS initrd_register( EFI_HANDLE *ret_initrd_handle) { EFI_STATUS err; - EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path; - EFI_HANDLE handle; - struct initrd_loader *loader; assert(ret_initrd_handle); @@ -82,15 +80,45 @@ EFI_STATUS initrd_register( if (!iovec_is_set(initrd)) return EFI_SUCCESS; - /* check if a LINUX_INITRD_MEDIA_GUID DevicePath is already registered. - LocateDevicePath checks for the "closest DevicePath" and returns its handle, - where as InstallMultipleProtocolInterfaces only matches identical DevicePaths. - */ - err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle); - if (err != EFI_NOT_FOUND) /* InitrdMedia is already registered */ - return EFI_ALREADY_STARTED; + /* We want to override the LINUX_INITRD_MEDIA device, let's hence first unregister any existing + * one. We don't really expect multiple of these to be registered, but who knows? Let's kill all we + * can find. */ + for (unsigned attempt = 0;; attempt++) { + + if (attempt >= 16) + return log_debug_status(EFI_DEVICE_ERROR, "Unable to free LINUX_INITRD_MEDIA device path after %u attempts, giving up.", attempt); + + EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path; + EFI_HANDLE handle = NULL; + err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle); + if (err == EFI_NOT_FOUND) /* Yay! All gone */ + break; + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to locate LINUX_INITRD_MEDIA device: %m"); + + /* Get the *actually* installed pointer for the device path */ + err = BS->HandleProtocol(handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void**) &dp); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to acquire DevicePath protocol on LINUX_INITRD_MEDIA device: %m"); + + /* Take away the device path protocol */ + err = BS->UninstallMultipleProtocolInterfaces( + handle, + MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), dp, + /* sentinel= */ NULL); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Unable to release DevicePath protocol from old handle: %m"); - loader = xnew(struct initrd_loader, 1); + /* NB: we leave the handle around (and thus leave the LoadFile2 protocol installed), because + * the owner might be unhappy if we destroy it for them. It will no longer have the device + * path we want to take possession of on it though. The assumption here is that whoever + * registered the device path is OK with the device path being taken away, even if it might + * not be OK with the handle being invalidated as a whole. */ + + log_debug("Successfully unregistered previous LINUX_INITRD_MEDIA device."); + } + + _cleanup_free_ struct initrd_loader *loader = xnew(struct initrd_loader, 1); *loader = (struct initrd_loader) { .load_file.LoadFile = initrd_load_file, .data = *initrd, @@ -98,38 +126,137 @@ EFI_STATUS initrd_register( /* create a new handle and register the LoadFile2 protocol with the InitrdMediaPath on it */ err = BS->InstallMultipleProtocolInterfaces( - ret_initrd_handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), - &efi_initrd_device_path, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), - loader, - NULL); + ret_initrd_handle, + MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), &efi_initrd_device_path, + MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), loader, + /* sentinel= */ NULL); if (err != EFI_SUCCESS) - free(loader); + return log_debug_status(err, "Failed to install new initrd device: %m"); + + log_debug("Installed new initrd of size %zu.", loader->data.iov_len); - return err; + TAKE_PTR(loader); + return EFI_SUCCESS; } EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle) { - EFI_STATUS err; struct initrd_loader *loader; + EFI_STATUS err; if (!initrd_handle) return EFI_SUCCESS; - /* get the LoadFile2 protocol that we allocated earlier */ + /* Get the LoadFile2 protocol that we allocated earlier */ err = BS->HandleProtocol(initrd_handle, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), (void **) &loader); if (err != EFI_SUCCESS) - return err; + return log_debug_status(err, "Failed to acquire LoadFile2 protocol on our own initrd handle: %m"); - /* uninstall all protocols thus destroying the handle */ + /* We uninstall the DevicePath and the LoadFile2 protocol in separate steps. That's because we want + * to gracefully handle the former (because it's OK if something else takes over the device path), + * but be strict on the latter, because that's genuinely ours */ + + (void) BS->UninstallMultipleProtocolInterfaces( + initrd_handle, + MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), &efi_initrd_device_path, + /* sentinel= */ NULL); + + /* This second call will also invalidate the handle, because it should be the last protocol on the handle */ err = BS->UninstallMultipleProtocolInterfaces( - initrd_handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), - &efi_initrd_device_path, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), - loader, - NULL); + initrd_handle, + MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), loader, + /* sentinel= */ NULL); if (err != EFI_SUCCESS) - return err; + return log_debug_status(err, "Failed to uninstall LoadFile2 protocol from our own initrd handle: %m"); - initrd_handle = NULL; free(loader); return EFI_SUCCESS; } + +EFI_STATUS initrd_read_previous(struct iovec *ret_initrd) { + EFI_STATUS err; + + /* If there's already an initrd registered, read it out, so that we can incorporate it in ours */ + + assert(ret_initrd); + + /* Get from the device path to the handle */ + EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path; + EFI_HANDLE handle; + err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle); + if (err != EFI_SUCCESS) + return err; + + /* Get from the handle to the protocol */ + EFI_LOAD_FILE2_PROTOCOL *protocol = NULL; + err = BS->HandleProtocol(handle, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), (void**) &protocol); + if (err != EFI_SUCCESS) + return err; + + size_t size = 0; + err = protocol->LoadFile(protocol, dp, /* bootPolicy= */ false, &size, /* Buffer= */ NULL); + if (err == EFI_SUCCESS) /* Success? Kinda unexpected given we set Buffer to NULL, but it probably + * means, that the file is zero-sized, let's treat it as such. */ + size = 0; + else if (err != EFI_BUFFER_TOO_SMALL) + return err; + + if (size == 0) + return EFI_NOT_FOUND; /* Treat empty initrds like missing ones */ + + _cleanup_free_ void *data = xmalloc(size); + err = protocol->LoadFile(protocol, dp, /* bootPolicy= */ false, &size, data); + if (err != EFI_SUCCESS) + return err; + + *ret_initrd = (struct iovec) { + .iov_base = TAKE_PTR(data), + .iov_len = size, + }; + + return EFI_SUCCESS; +} + +EFI_STATUS combine_initrds( + const struct iovec initrds[], size_t n_initrds, + Pages *ret_initrd_pages, size_t *ret_initrd_size) { + + size_t n = 0; + + /* Combine initrds by concatenation in memory */ + + assert(initrds || n_initrds == 0); + assert(ret_initrd_pages); + assert(ret_initrd_size); + + FOREACH_ARRAY(i, initrds, n_initrds) { + /* some initrds (the ones from UKI sections) need padding, pad all to be safe */ + size_t initrd_size = ALIGN4(i->iov_len); + if (n > SIZE_MAX - initrd_size) + return EFI_OUT_OF_RESOURCES; + + n += initrd_size; + } + + _cleanup_pages_ Pages pages = xmalloc_initrd_pages(n); + uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr); + + FOREACH_ARRAY(i, initrds, n_initrds) { + size_t pad; + + p = mempcpy(p, i->iov_base, i->iov_len); + + pad = ALIGN4(i->iov_len) - i->iov_len; + if (pad == 0) + continue; + + memzero(p, pad); + p += pad; + } + + assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p); + + *ret_initrd_pages = TAKE_STRUCT(pages); + *ret_initrd_size = n; + + return EFI_SUCCESS; +} diff --git a/src/boot/initrd.h b/src/boot/initrd.h index 50987c497d667..cfcb8d1c6f59b 100644 --- a/src/boot/initrd.h +++ b/src/boot/initrd.h @@ -2,6 +2,8 @@ #pragma once #include "efi.h" +#include "iovec-util-fundamental.h" +#include "util.h" EFI_STATUS initrd_register( const struct iovec *initrd, @@ -13,3 +15,7 @@ static inline void cleanup_initrd(EFI_HANDLE *initrd_handle) { (void) initrd_unregister(*initrd_handle); *initrd_handle = NULL; } + +EFI_STATUS initrd_read_previous(struct iovec *ret_initrd); + +EFI_STATUS combine_initrds(const struct iovec initrds[], size_t n_initrds, Pages *ret_initrd_pages, size_t *ret_initrd_size); diff --git a/src/boot/linux_x86.c b/src/boot/linux_x86.c index cf9707a6cfd7a..349e3fb26c01b 100644 --- a/src/boot/linux_x86.c +++ b/src/boot/linux_x86.c @@ -195,9 +195,14 @@ EFI_STATUS linux_exec_efi_handover( /* Setup size is determined by offset 0x0202 + byte value at offset 0x0201, which is the same as * offset of the header field and the target from the jump field (which we split for this reason). */ + size_t setup_hdr_len; + if (!ADD_SAFE(&setup_hdr_len, offsetof(SetupHeader, header), image_params->hdr.setup_size)) + setup_hdr_len = sizeof(SetupHeader); + else + setup_hdr_len = MIN(setup_hdr_len, sizeof(SetupHeader)); memcpy(&boot_params->hdr, &image_params->hdr, - offsetof(SetupHeader, header) + image_params->hdr.setup_size); + setup_hdr_len); boot_params->hdr.type_of_loader = 0xff; diff --git a/src/boot/measure.c b/src/boot/measure.c index 22129cb87d61d..085ebde472567 100644 --- a/src/boot/measure.c +++ b/src/boot/measure.c @@ -232,6 +232,33 @@ static EFI_STATUS tcg2_log_ipl_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buf return EFI_SUCCESS; } +static EFI_STATUS tcg2_log_tagged_event( + uint32_t pcrindex, + EFI_PHYSICAL_ADDRESS buffer, + size_t buffer_size, + uint32_t event_id, + const char16_t *description, + bool *ret_measured) { + + EFI_TCG2_PROTOCOL *tpm2; + EFI_STATUS err = EFI_SUCCESS; + + assert(ret_measured); + + tpm2 = tcg2_interface_check(/* ret_version= */ NULL); + if (!tpm2) { + *ret_measured = false; + return EFI_SUCCESS; + } + + err = tpm2_measure_to_pcr_and_tagged_event_log(tpm2, pcrindex, buffer, buffer_size, event_id, description); + if (err != EFI_SUCCESS) + return err; + + *ret_measured = true; + return EFI_SUCCESS; +} + static EFI_STATUS cc_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { EFI_CC_MEASUREMENT_PROTOCOL *cc; EFI_STATUS err = EFI_SUCCESS; @@ -291,8 +318,8 @@ EFI_STATUS tpm_log_tagged_event( const char16_t *description, bool *ret_measured) { - EFI_TCG2_PROTOCOL *tpm2; EFI_STATUS err; + bool tpm_ret_measured, cc_ret_measured; assert(description || pcrindex == UINT32_MAX); assert(event_id > 0); @@ -300,19 +327,26 @@ EFI_STATUS tpm_log_tagged_event( /* If EFI_SUCCESS is returned, will initialize ret_measured to true if we actually measured * something, or false if measurement was turned off. */ - tpm2 = tcg2_interface_check(/* ret_version= */ NULL); - if (!tpm2 || pcrindex == UINT32_MAX) { /* PCR disabled? */ + if (pcrindex == UINT32_MAX) { /* PCR disabled? */ if (ret_measured) *ret_measured = false; return EFI_SUCCESS; } - err = tpm2_measure_to_pcr_and_tagged_event_log(tpm2, pcrindex, buffer, buffer_size, event_id, description); - if (!err) + /* Measure into both CC and TPM if both are available to avoid a problem like CVE-2021-42299. + * The CC protocol has no tagged-event concept, hence use the IPL event log there. */ + err = cc_log_event(pcrindex, buffer, buffer_size, description, &cc_ret_measured); + if (err != EFI_SUCCESS) return err; - *ret_measured = true; + err = tcg2_log_tagged_event(pcrindex, buffer, buffer_size, event_id, description, &tpm_ret_measured); + if (err != EFI_SUCCESS) + return err; + + if (ret_measured) + *ret_measured = tpm_ret_measured || cc_ret_measured; + return EFI_SUCCESS; } diff --git a/src/boot/meson.build b/src/boot/meson.build index 06c8146a9ebcb..dfac98f034a6d 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -80,8 +80,14 @@ endif efi_conf = configuration_data() # import several configs from userspace -foreach name : ['HAVE_WARNING_ZERO_LENGTH_BOUNDS', 'HAVE_WARNING_ZERO_AS_NULL_POINTER_CONSTANT'] - efi_conf.set10(name, conf.get(name) == 1) +foreach name : ['HAVE_WARNING_ZERO_LENGTH_BOUNDS', + 'HAVE_WARNING_ZERO_AS_NULL_POINTER_CONSTANT'] + efi_conf.set(name, conf.get(name)) +endforeach + +foreach attr : possible_c_attributes + name = 'HAVE_ATTRIBUTE_' + attr.to_upper() + efi_conf.set(name, conf.get(name)) endforeach efi_conf.set10('ENABLE_TPM', get_option('tpm')) @@ -334,6 +340,7 @@ systemd_boot_sources = files( ) stub_sources = files( + 'boot-secret.c', 'cpio.c', 'linux.c', 'splash.c', diff --git a/src/boot/part-discovery.c b/src/boot/part-discovery.c index dc1aed0514b1d..8f5fefad47c1d 100644 --- a/src/boot/part-discovery.c +++ b/src/boot/part-discovery.c @@ -1,9 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "device-path-util.h" +#include "efi-log.h" #include "part-discovery.h" #include "proto/block-io.h" #include "proto/device-path.h" +#include "proto/disk-io.h" +#include "string-util-fundamental.h" #include "util.h" typedef struct { @@ -70,80 +73,98 @@ static bool verify_gpt(/* const */ GptHeader *h, EFI_LBA lba_expected) { return true; } -static EFI_STATUS try_gpt( - const EFI_GUID *type, - EFI_BLOCK_IO_PROTOCOL *block_io, +static EFI_STATUS read_gpt_entries( + EFI_DISK_IO_PROTOCOL *disk_io, + uint32_t media_id, + uint32_t block_size, EFI_LBA lba, - EFI_LBA *ret_backup_lba, /* May be changed even on error! */ - HARDDRIVE_DEVICE_PATH *ret_hd) { + EFI_LBA *reterr_backup_lba, /* May be changed even on error! */ + GptHeader *ret_gpt, + void **ret_entries) { - EFI_PARTITION_ENTRY *entries; - _cleanup_pages_ Pages gpt_pages = {}; - _cleanup_pages_ Pages entries_pages = {}; - GptHeader *gpt; + GptHeader gpt; EFI_STATUS err; uint32_t crc32; size_t size; - assert(block_io); - assert(block_io->Media); - assert(ret_hd); + assert(disk_io); + assert(ret_gpt); + assert(ret_entries); + + uint64_t offset; + if (!MUL_SAFE(&offset, lba, block_size)) + return log_debug_status( + EFI_INVALID_PARAMETER, + "LBA %" PRIu64 " * block size %" PRIu32 " overflow: %m", + lba, + block_size); - gpt_pages = xmalloc_aligned_pages( - AllocateMaxAddress, - EfiLoaderData, - EFI_SIZE_TO_PAGES(sizeof(GptHeader)), - block_io->Media->IoAlign, - /* On 32-bit allocate below 4G boundary as we can't easily access anything above that. - * 64-bit platforms don't suffer this limitation, so we can allocate from anywhere. - * addr= */ UINTPTR_MAX); - gpt = PHYSICAL_ADDRESS_TO_POINTER(gpt_pages.addr); - - /* Read the GPT header */ - err = block_io->ReadBlocks( - block_io, - block_io->Media->MediaId, - lba, - sizeof(*gpt), gpt); + err = disk_io->ReadDisk(disk_io, media_id, offset, sizeof(gpt), &gpt); if (err != EFI_SUCCESS) - return err; + return log_debug_status(err, "Failed to read GPT header at LBA %" PRIu64 ": %m", lba); - /* Indicate the location of backup LBA even if the rest of the header is corrupt. */ - if (ret_backup_lba) - *ret_backup_lba = gpt->AlternateLBA; + /* Expose backup LBA even if the rest of the header is corrupt, so the caller can + * try the backup GPT. */ + if (reterr_backup_lba) + *reterr_backup_lba = gpt.AlternateLBA; - if (!verify_gpt(gpt, lba)) - return EFI_NOT_FOUND; + if (!verify_gpt(&gpt, lba)) + return log_debug_status(EFI_NOT_FOUND, "GPT header at LBA %" PRIu64 " is not valid: %m", lba); + + size = (size_t) gpt.SizeOfPartitionEntry * (size_t) gpt.NumberOfPartitionEntries; + if (size == SIZE_MAX) /* overflow check */ + return log_debug_status(EFI_OUT_OF_RESOURCES, "GPT partition entries size overflow: %m"); + + _cleanup_free_ void *entries = xmalloc(size); - /* Now load the GPT entry table */ - size = ALIGN_TO((size_t) gpt->SizeOfPartitionEntry * (size_t) gpt->NumberOfPartitionEntries, 512); - entries_pages = xmalloc_aligned_pages( - AllocateMaxAddress, - EfiLoaderData, - EFI_SIZE_TO_PAGES(size), - block_io->Media->IoAlign, - /* On 32-bit allocate below 4G boundary as we can't easily access anything above that. - * 64-bit platforms don't suffer this limitation, so we can allocate from anywhere. - * addr= */ UINTPTR_MAX); - entries = PHYSICAL_ADDRESS_TO_POINTER(entries_pages.addr); - - err = block_io->ReadBlocks( - block_io, - block_io->Media->MediaId, - gpt->PartitionEntryLBA, - size, entries); + if (!MUL_SAFE(&offset, gpt.PartitionEntryLBA, block_size)) + return log_debug_status( + EFI_INVALID_PARAMETER, + "Partition entry LBA %" PRIu64 " * block size %" PRIu32 " overflow: %m", + gpt.PartitionEntryLBA, + block_size); + + err = disk_io->ReadDisk(disk_io, media_id, offset, size, entries); if (err != EFI_SUCCESS) - return err; + return log_debug_status(err, "Failed to read GPT partition entries at LBA %" PRIu64 ": %m", gpt.PartitionEntryLBA); - /* Calculate CRC of entries array, too */ err = BS->CalculateCrc32(entries, size, &crc32); - if (err != EFI_SUCCESS || crc32 != gpt->PartitionEntryArrayCRC32) - return EFI_CRC_ERROR; + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to calculate CRC32 of GPT partition entries: %m"); + if (crc32 != gpt.PartitionEntryArrayCRC32) + return log_debug_status( + EFI_CRC_ERROR, + "GPT partition entries CRC32 mismatch (got 0x%08" PRIx32 ", expected 0x%08" PRIx32 "): %m", + crc32, + gpt.PartitionEntryArrayCRC32); + + *ret_gpt = gpt; + *ret_entries = TAKE_PTR(entries); + return EFI_SUCCESS; +} + +static EFI_STATUS try_gpt( + const EFI_GUID *type, + EFI_DISK_IO_PROTOCOL *disk_io, + uint32_t media_id, + uint32_t block_size, + EFI_LBA lba, + EFI_LBA *reterr_backup_lba, /* May be changed even on error! */ + HARDDRIVE_DEVICE_PATH *ret_hd) { + + GptHeader gpt; + _cleanup_free_ void *entries = NULL; + EFI_STATUS err; - /* Now we can finally look for xbootloader partitions. */ - for (size_t i = 0; i < gpt->NumberOfPartitionEntries; i++) { + assert(ret_hd); + + err = read_gpt_entries(disk_io, media_id, block_size, lba, reterr_backup_lba, &gpt, &entries); + if (err != EFI_SUCCESS) + return err; + + for (size_t i = 0; i < gpt.NumberOfPartitionEntries; i++) { EFI_PARTITION_ENTRY *entry = - (EFI_PARTITION_ENTRY *) ((uint8_t *) entries + gpt->SizeOfPartitionEntry * i); + (EFI_PARTITION_ENTRY *) ((uint8_t *) entries + gpt.SizeOfPartitionEntry * i); if (!efi_guid_equal(&entry->PartitionTypeGUID, type)) continue; @@ -182,7 +203,7 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI EFI_DEVICE_PATH *partition_path; err = BS->HandleProtocol(device, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &partition_path); if (err != EFI_SUCCESS) - return err; + return log_debug_status(err, "Failed to get device path: %m"); /* Find the (last) partition node itself. */ EFI_DEVICE_PATH *part_node = NULL; @@ -194,8 +215,10 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI part_node = node; } - if (!part_node) + if (!part_node) { + log_debug("No hard drive device path node found."); return EFI_NOT_FOUND; + } /* Chop off the partition part, leaving us with the full path to the disk itself. */ _cleanup_free_ EFI_DEVICE_PATH *disk_path = NULL; @@ -205,7 +228,7 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI EFI_BLOCK_IO_PROTOCOL *block_io; err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), &p, &disk_handle); if (err != EFI_SUCCESS) - return err; + return log_debug_status(err, "Failed to locate disk device: %m"); /* The drivers for other partitions on this drive may not be initialized on fastboot firmware, so we * have to ask the firmware to do just that. */ @@ -213,16 +236,22 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), (void **) &block_io); if (err != EFI_SUCCESS) - return err; + return log_debug_status(err, "Failed to get block I/O protocol: %m"); /* Filter out some block devices early. (We only care about block devices that aren't * partitions themselves — we look for GPT partition tables to parse after all —, and only * those which contain a medium and have at least 2 blocks.) */ if (block_io->Media->LogicalPartition || !block_io->Media->MediaPresent || - block_io->Media->LastBlock <= 1) + block_io->Media->LastBlock <= 1 || + block_io->Media->BlockSize < 512 || block_io->Media->BlockSize > 4096) return EFI_NOT_FOUND; + EFI_DISK_IO_PROTOCOL *disk_io; + err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_DISK_IO_PROTOCOL), (void **) &disk_io); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to get disk I/O protocol: %m"); + /* Try several copies of the GPT header, in case one is corrupted */ EFI_LBA backup_lba = 0; for (size_t nr = 0; nr < 3; nr++) { @@ -241,7 +270,7 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI continue; HARDDRIVE_DEVICE_PATH hd; - err = try_gpt(type, block_io, lba, + err = try_gpt(type, disk_io, block_io->Media->MediaId, block_io->Media->BlockSize, lba, nr == 0 ? &backup_lba : NULL, /* Only get backup LBA location from first GPT header. */ &hd); if (err != EFI_SUCCESS) { @@ -291,6 +320,134 @@ EFI_STATUS partition_open(const EFI_GUID *type, EFI_HANDLE *device, EFI_HANDLE * return EFI_SUCCESS; } +static char16_t* disk_get_part_uuid_eltorito(const EFI_DEVICE_PATH *dp) { + EFI_STATUS err; + + assert(dp); + + /* When booting via El Torito, the device path contains a CDROM node instead of a HARDDRIVE + * node (UEFI specification §10.3.5.2). The CDROM node doesn't carry a partition UUID, so we + * need to read the GPT from the underlying disk to find it. Per §13.3.2, El Torito partition + * discovery applies to any block device, not just optical media (e.g. an ISO image dd'd to a + * USB stick). */ + + const CDROM_DEVICE_PATH *cdrom = NULL; + for (const EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node)) + if (node->Type == MEDIA_DEVICE_PATH && node->SubType == MEDIA_CDROM_DP) + cdrom = (const CDROM_DEVICE_PATH *) node; + if (!cdrom) { + log_debug("No El Torito device path node found."); + return NULL; + } + + /* Chop off the CDROM node to get the whole-disk device path */ + _cleanup_free_ EFI_DEVICE_PATH *disk_path = device_path_replace_node(dp, &cdrom->Header, /* new_node= */ NULL); + + EFI_DEVICE_PATH *remaining = disk_path; + EFI_HANDLE disk_handle; + err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), &remaining, &disk_handle); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to locate disk device for El Torito boot: %m"); + return NULL; + } + + (void) BS->ConnectController(disk_handle, /* DriverImageHandle= */ NULL, /* RemainingDevicePath= */ NULL, /* Recursive= */ true); + + EFI_BLOCK_IO_PROTOCOL *block_io; + err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), (void **) &block_io); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to get block I/O protocol for El Torito disk: %m"); + return NULL; + } + + if (block_io->Media->LogicalPartition || !block_io->Media->MediaPresent || + block_io->Media->LastBlock <= 1) { + log_debug("El Torito disk has unsuitable media (partition=%ls, present=%ls, lastblock=%" PRIu64 ").", + yes_no(block_io->Media->LogicalPartition), + yes_no(block_io->Media->MediaPresent), + (uint64_t) block_io->Media->LastBlock); + return NULL; + } + + uint32_t iso9660_block_size = block_io->Media->BlockSize; + if (iso9660_block_size < 512 || iso9660_block_size > 4096 || !ISPOWEROF2(iso9660_block_size)) { + log_debug("Unexpected El Torito block size %" PRIu32 ", skipping.", iso9660_block_size); + return NULL; + } + + EFI_DISK_IO_PROTOCOL *disk_io; + err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_DISK_IO_PROTOCOL), (void **) &disk_io); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to get disk I/O protocol for El Torito disk: %m"); + return NULL; + } + + uint32_t media_id = block_io->Media->MediaId; + + /* Probe for the GPT header at multiple possible sector sizes (512, 1024, 2048, 4096). + * The GPT header is at LBA 1, i.e. byte offset == sector_size. On El Torito media, the GPT + * may use a different sector size than the media's block size (e.g. 512-byte GPT sectors + * on 2048-byte blocks), so we try all possibilities. If the primary GPT header is corrupt + * but contains a valid backup LBA, fall back to the backup header. */ + uint32_t gpt_sector_size = 0; + GptHeader gpt; + _cleanup_free_ void *entries = NULL; + for (uint32_t ss = 512; ss <= 4096; ss <<= 1) { + EFI_LBA backup_lba = 0; + + err = read_gpt_entries(disk_io, media_id, ss, /* lba= */ 1, &backup_lba, &gpt, &entries); + if (err == EFI_SUCCESS) { + gpt_sector_size = ss; + break; + } + if (err != EFI_NOT_FOUND) + log_debug_status(err, "Failed to read primary GPT header at sector size %"PRIu32", ignoring: %m", ss); + + if (backup_lba != 0) { + err = read_gpt_entries(disk_io, media_id, ss, backup_lba, /* reterr_backup_lba= */ NULL, &gpt, &entries); + if (err == EFI_SUCCESS) { + gpt_sector_size = ss; + break; + } + if (err != EFI_NOT_FOUND) + log_debug_status(err, "Failed to read backup GPT header at sector size %"PRIu32", ignoring: %m", ss); + } + } + + if (gpt_sector_size == 0) { + log_debug("No valid GPT found on El Torito disk at any sector size."); + return NULL; + } + + log_debug("Found GPT on El Torito disk with sector size %" PRIu32 ", %" PRIu32 " partition entries.", + gpt_sector_size, gpt.NumberOfPartitionEntries); + + /* Find the partition whose byte offset matches the El Torito PartitionStart. + * El Torito PartitionStart is in media iso9660_block_size units, GPT StartingLBA is in gpt_sector_size units. */ + uint64_t cdrom_start; + if (!MUL_SAFE(&cdrom_start, cdrom->PartitionStart, iso9660_block_size)) { + log_debug("El Torito start offset overflow."); + return NULL; + } + + for (size_t i = 0; i < gpt.NumberOfPartitionEntries; i++) { + const EFI_PARTITION_ENTRY *entry = + (const EFI_PARTITION_ENTRY *) ((const uint8_t *) entries + gpt.SizeOfPartitionEntry * i); + + if (!efi_guid_equal(&entry->PartitionTypeGUID, &(const EFI_GUID) ESP_GUID)) + continue; + + uint64_t entry_start; + if (MUL_SAFE(&entry_start, entry->StartingLBA, gpt_sector_size) && + entry_start == cdrom_start) + return xasprintf(GUID_FORMAT_STR, GUID_FORMAT_VAL(entry->UniquePartitionGUID)); + } + + log_debug("No ESP partition matches El Torito start offset %" PRIu64 " (block size %" PRIu32 ").", + cdrom->PartitionStart, iso9660_block_size); + return NULL; +} + char16_t *disk_get_part_uuid(EFI_HANDLE *handle) { EFI_STATUS err; EFI_DEVICE_PATH *dp; @@ -304,16 +461,17 @@ char16_t *disk_get_part_uuid(EFI_HANDLE *handle) { if (err != EFI_SUCCESS) return NULL; - for (; !device_path_is_end(dp); dp = device_path_next_node(dp)) { - if (dp->Type != MEDIA_DEVICE_PATH || dp->SubType != MEDIA_HARDDRIVE_DP) + for (EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node)) { + if (node->Type != MEDIA_DEVICE_PATH || node->SubType != MEDIA_HARDDRIVE_DP) continue; - HARDDRIVE_DEVICE_PATH *hd = (HARDDRIVE_DEVICE_PATH *) dp; + HARDDRIVE_DEVICE_PATH *hd = (HARDDRIVE_DEVICE_PATH *) node; if (hd->SignatureType != SIGNATURE_TYPE_GUID) continue; return xasprintf(GUID_FORMAT_STR, GUID_FORMAT_VAL(hd->SignatureGuid)); } - return NULL; + /* No GPT partition node found — try El Torito device path as fallback */ + return disk_get_part_uuid_eltorito(dp); } diff --git a/src/boot/pe.c b/src/boot/pe.c index 397a7a69404ba..5fbf5a42e5386 100644 --- a/src/boot/pe.c +++ b/src/boot/pe.c @@ -396,7 +396,8 @@ static void pe_locate_sections( EFI_STATUS err = chid_match(hwids, hwids_section[0].memory_size, DEVICE_TYPE_DEVICETREE, &device); if (err != EFI_SUCCESS) { - log_error_status(err, "HWID matching failed, no DT blob will be selected: %m"); + log_full(err, err == EFI_NOT_FOUND ? LOG_DEBUG : LOG_ERR, + "HWID matching failed, no DT blob will be selected: %m"); hwids = NULL; } } @@ -569,8 +570,14 @@ EFI_STATUS pe_section_table_from_base( if (!verify_pe(dos, pe, /* allow_compatibility= */ false)) return EFI_LOAD_ERROR; + assert_cc(sizeof(pe->FileHeader.NumberOfSections) == sizeof(uint16_t)); /* multiplication below cannot overflow */ + + size_t n_section_table = pe->FileHeader.NumberOfSections; + if (n_section_table * sizeof(PeSectionHeader) > SECTION_TABLE_BYTES_MAX) + return EFI_OUT_OF_RESOURCES; + *ret_section_table = (const PeSectionHeader*) ((const uint8_t*) base + section_table_offset(dos, pe)); - *ret_n_section_table = pe->FileHeader.NumberOfSections; + *ret_n_section_table = n_section_table; return EFI_SUCCESS; } diff --git a/src/boot/proto/device-path.h b/src/boot/proto/device-path.h index b56c217082dd8..658c482df504a 100644 --- a/src/boot/proto/device-path.h +++ b/src/boot/proto/device-path.h @@ -27,7 +27,10 @@ enum { HW_MEMMAP_DP = 0x03, + ACPI_DP = 0x01, + MEDIA_HARDDRIVE_DP = 0x01, + MEDIA_CDROM_DP = 0x02, MEDIA_VENDOR_DP = 0x03, MEDIA_FILEPATH_DP = 0x04, MEDIA_PIWG_FW_FILE_DP = 0x06, @@ -47,6 +50,15 @@ typedef struct { EFI_GUID Guid; } _packed_ VENDOR_DEVICE_PATH; +/* EISA PNP ID encoding: compressed 3-letter vendor + 16-bit product ID. */ +#define EISA_PNP_ID(Id) ((uint32_t) (((Id) << 16) | 0x41D0)) + +typedef struct { + EFI_DEVICE_PATH Header; + uint32_t HID; + uint32_t UID; +} _packed_ ACPI_HID_DEVICE_PATH; + typedef struct { EFI_DEVICE_PATH Header; uint32_t MemoryType; @@ -73,6 +85,14 @@ typedef struct { uint8_t SignatureType; } _packed_ HARDDRIVE_DEVICE_PATH; +typedef struct { + EFI_DEVICE_PATH Header; + uint32_t BootEntry; + uint64_t PartitionStart; /* In media block size units */ + uint64_t PartitionSize; /* In media block size units */ +} _packed_ CDROM_DEVICE_PATH; +assert_cc(sizeof(CDROM_DEVICE_PATH) == 24); + typedef struct { EFI_DEVICE_PATH Header; char16_t PathName[]; diff --git a/src/boot/proto/disk-io.h b/src/boot/proto/disk-io.h new file mode 100644 index 0000000000000..d758a8eb2897e --- /dev/null +++ b/src/boot/proto/disk-io.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" + +#define EFI_DISK_IO_PROTOCOL_GUID \ + GUID_DEF(0xCE345171, 0xBA0B, 0x11d2, 0x8e, 0x4F, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b) + +typedef struct EFI_DISK_IO_PROTOCOL EFI_DISK_IO_PROTOCOL; +struct EFI_DISK_IO_PROTOCOL { + uint64_t Revision; + EFI_STATUS (EFIAPI *ReadDisk)( + EFI_DISK_IO_PROTOCOL *This, + uint32_t MediaId, + uint64_t Offset, + size_t BufferSize, + void *Buffer); + EFI_STATUS (EFIAPI *WriteDisk)( + EFI_DISK_IO_PROTOCOL *This, + uint32_t MediaId, + uint64_t Offset, + size_t BufferSize, + const void *Buffer); +}; diff --git a/src/boot/proto/pci-io.h b/src/boot/proto/pci-io.h new file mode 100644 index 0000000000000..2e385d4650a47 --- /dev/null +++ b/src/boot/proto/pci-io.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" + +#define EFI_PCI_IO_PROTOCOL_GUID \ + GUID_DEF(0x4cf5b200, 0x68b8, 0x4ca5, 0x9e, 0xec, 0xb2, 0x3e, 0x3f, 0x50, 0x02, 0x9a) + +typedef enum { + EfiPciIoWidthUint8, + EfiPciIoWidthUint16, + EfiPciIoWidthUint32, + EfiPciIoWidthUint64, +} EFI_PCI_IO_PROTOCOL_WIDTH; + +typedef struct EFI_PCI_IO_PROTOCOL EFI_PCI_IO_PROTOCOL; + +typedef EFI_STATUS (EFIAPI *EFI_PCI_IO_PROTOCOL_CONFIG)( + EFI_PCI_IO_PROTOCOL *This, + EFI_PCI_IO_PROTOCOL_WIDTH Width, + uint32_t Offset, + size_t Count, + void *Buffer); + +typedef struct { + EFI_PCI_IO_PROTOCOL_CONFIG Read; + EFI_PCI_IO_PROTOCOL_CONFIG Write; +} EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS; + +/* Minimal definition — only Pci.Read is used. Fields before Pci must be correctly sized + * (one function pointer each for PollMem/PollIo, two for Mem.Read/Write, two for Io.Read/Write) + * to ensure Pci is at the right offset. */ +struct EFI_PCI_IO_PROTOCOL { + void *PollMem; + void *PollIo; + EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Mem; + EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Io; + EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Pci; + /* remaining fields omitted */ +}; + +#define PCI_VENDOR_ID_REDHAT 0x1af4U +#define PCI_DEVICE_ID_VIRTIO_CONSOLE 0x1003U diff --git a/src/boot/random-seed.c b/src/boot/random-seed.c index 30e74af214f49..a31215c3b0262 100644 --- a/src/boot/random-seed.c +++ b/src/boot/random-seed.c @@ -12,14 +12,6 @@ #define RANDOM_MAX_SIZE_MIN (32U) #define RANDOM_MAX_SIZE_MAX (32U*1024U) -struct linux_efi_random_seed { - uint32_t size; - uint8_t seed[]; -}; - -#define LINUX_EFI_RANDOM_SEED_TABLE_GUID \ - { 0x1ce1e5bc, 0x7ceb, 0x42f2, { 0x81, 0xe5, 0x8a, 0xad, 0xf1, 0x80, 0xf5, 0x7b } } - /* SHA256 gives us 256/8=32 bytes */ #define HASH_VALUE_SIZE 32 @@ -193,46 +185,66 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir) { explicit_bzero_safe(system_token, size); } + bool created = false; err = root_dir->Open( root_dir, &handle, (char16_t *) u"\\loader\\random-seed", EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0); + if (err == EFI_NOT_FOUND && seeded_by_efi) { + /* If the file does not exist, but we are reasonably well seeded, create the seed file */ + created = true; + err = root_dir->Open( + root_dir, + &handle, + (char16_t *) u"\\loader\\random-seed", + EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, + 0); + } if (err != EFI_SUCCESS) { if (!IN_SET(err, EFI_NOT_FOUND, EFI_WRITE_PROTECTED)) log_error_status(err, "Failed to open random seed file: %m"); return err; } - err = get_file_info(handle, &info, NULL); - if (err != EFI_SUCCESS) - return log_error_status(err, "Failed to get file info for random seed: %m"); + if (!created) { + err = get_file_info(handle, &info, /* ret_size= */ NULL); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to get file info for random seed: %m"); - size = info->FileSize; - if (size < RANDOM_MAX_SIZE_MIN) - return log_error_status(EFI_INVALID_PARAMETER, "Random seed file is too short."); + /* Treat a short file just like a freshly created one for robustness reasons: consider a case + * where in a previous run a file was just created and the system was then powered off. In + * such a case the file will already exist, but be too short. */ + created = info->FileSize < RANDOM_MAX_SIZE_MIN; + } - if (size > RANDOM_MAX_SIZE_MAX) - return log_error_status(EFI_INVALID_PARAMETER, "Random seed file is too large."); + if (created) { + size = 0; + sha256_process_bytes(&size, sizeof(size), &hash); + } else { + size = info->FileSize; + if (size > RANDOM_MAX_SIZE_MAX) + return log_error_status(EFI_INVALID_PARAMETER, "Random seed file is too large."); - seed = xmalloc(size); - rsize = size; - err = handle->Read(handle, &rsize, seed); - if (err != EFI_SUCCESS) - return log_error_status(err, "Failed to read random seed file: %m"); - if (rsize != size) { - explicit_bzero_safe(seed, rsize); - return log_error_status(EFI_PROTOCOL_ERROR, "Short read on random seed file."); - } + seed = xmalloc(size); + rsize = size; + err = handle->Read(handle, &rsize, seed); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to read random seed file: %m"); + if (rsize != size) { + explicit_bzero_safe(seed, rsize); + return log_error_status(EFI_PROTOCOL_ERROR, "Short read on random seed file."); + } - sha256_process_bytes(&size, sizeof(size), &hash); - sha256_process_bytes(seed, size, &hash); - explicit_bzero_safe(seed, size); + sha256_process_bytes(&size, sizeof(size), &hash); + sha256_process_bytes(seed, size, &hash); + explicit_bzero_safe(seed, size); - err = handle->SetPosition(handle, 0); - if (err != EFI_SUCCESS) - return log_error_status(err, "Failed to seek to beginning of random seed file: %m"); + err = handle->SetPosition(handle, 0); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to seek to beginning of random seed file: %m"); + } /* Let's also include the UEFI monotonic counter (which is supposedly increasing on every single * boot) in the hash, so that even if the changes to the ESP for some reason should not be @@ -261,7 +273,7 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir) { size = sizeof(random_bytes); /* If the file size is too large, zero out the remaining bytes on disk. */ - if (size < info->FileSize) { + if (!created && size < info->FileSize) { err = handle->SetPosition(handle, size); if (err != EFI_SUCCESS) return log_error_status(err, "Failed to seek to offset of random seed file: %m"); diff --git a/src/boot/random-seed.h b/src/boot/random-seed.h index 67f005dff54f0..4a9f01bf45330 100644 --- a/src/boot/random-seed.h +++ b/src/boot/random-seed.h @@ -3,4 +3,12 @@ #include "efi.h" +struct linux_efi_random_seed { + uint32_t size; + uint8_t seed[]; +}; + +#define LINUX_EFI_RANDOM_SEED_TABLE_GUID \ + { 0x1ce1e5bc, 0x7ceb, 0x42f2, { 0x81, 0xe5, 0x8a, 0xad, 0xf1, 0x80, 0xf5, 0x7b } } + EFI_STATUS process_random_seed(EFI_FILE *root_dir); diff --git a/src/boot/splash.c b/src/boot/splash.c index 451909eb4ca29..487f212e65c1e 100644 --- a/src/boot/splash.c +++ b/src/boot/splash.c @@ -95,10 +95,17 @@ static EFI_STATUS bmp_parse_header( return EFI_UNSUPPORTED; } - size_t row_size = ((size_t) dib->depth * dib->x + 31) / 32 * 4; - if (file->size - file->offset < dib->y * row_size) + if (dib->x == 0 || dib->y == 0) + return EFI_INVALID_PARAMETER; + + /* Bound dimensions before computing row_size to prevent overflow + * in the (size_t) dib->depth * dib->x multiplication on 32-bit. */ + if (dib->x > (size_t) 64 * 1024 * 1024 / dib->depth || + dib->y > (size_t) 64 * 1024 * 1024 / dib->depth / dib->x) return EFI_INVALID_PARAMETER; - if (row_size * dib->y > 64 * 1024 * 1024) + + size_t row_size = ((size_t) dib->depth * dib->x + 31) / 32 * 4; + if (file->size - file->offset < dib->y * row_size) return EFI_INVALID_PARAMETER; /* check color table */ @@ -119,6 +126,12 @@ static EFI_STATUS bmp_parse_header( return EFI_INVALID_PARAMETER; } + /* Ensure there can be no OOB accesses in bmp_to_blt() due to malformed images (e.g.: color depth 8 + * but smaller color map) via map[*in]. */ + if (IN_SET(dib->depth, 1, 4, 8) && + file->offset - (sizeof(struct bmp_file) + dib->size) < sizeof(struct bmp_map) * (1U << dib->depth)) + return EFI_INVALID_PARAMETER; + *ret_map = map; *ret_dib = dib; *pixmap = bmp + file->offset; @@ -127,7 +140,7 @@ static EFI_STATUS bmp_parse_header( } enum Channels { R, G, B, A, _CHANNELS_MAX }; -static void read_channel_maks( +static EFI_STATUS read_channel_mask( const struct bmp_dib *dib, uint32_t channel_mask[static _CHANNELS_MAX], uint8_t channel_shift[static _CHANNELS_MAX], @@ -136,20 +149,34 @@ static void read_channel_maks( assert(dib); if (IN_SET(dib->depth, 16, 32) && dib->size >= SIZEOF_BMP_DIB_RGB) { + if (dib->channel_mask_r == 0 || dib->channel_mask_g == 0 || dib->channel_mask_b == 0) + return EFI_INVALID_PARAMETER; + + /* Reject masks where all bits are set (popcount == 32), since + * 1U << 32 is undefined behavior and causes division by zero + * on architectures where it evaluates to zero. */ + if (popcount(dib->channel_mask_r) >= 32 || + popcount(dib->channel_mask_g) >= 32 || + popcount(dib->channel_mask_b) >= 32) + return EFI_INVALID_PARAMETER; + channel_mask[R] = dib->channel_mask_r; channel_mask[G] = dib->channel_mask_g; channel_mask[B] = dib->channel_mask_b; channel_shift[R] = __builtin_ctz(dib->channel_mask_r); channel_shift[G] = __builtin_ctz(dib->channel_mask_g); channel_shift[B] = __builtin_ctz(dib->channel_mask_b); - channel_scale[R] = 0xff / ((1 << popcount(dib->channel_mask_r)) - 1); - channel_scale[G] = 0xff / ((1 << popcount(dib->channel_mask_g)) - 1); - channel_scale[B] = 0xff / ((1 << popcount(dib->channel_mask_b)) - 1); + channel_scale[R] = 0xff / ((1U << popcount(dib->channel_mask_r)) - 1); + channel_scale[G] = 0xff / ((1U << popcount(dib->channel_mask_g)) - 1); + channel_scale[B] = 0xff / ((1U << popcount(dib->channel_mask_b)) - 1); if (dib->size >= SIZEOF_BMP_DIB_RGBA && dib->channel_mask_a != 0) { + if (popcount(dib->channel_mask_a) >= 32) + return EFI_INVALID_PARAMETER; + channel_mask[A] = dib->channel_mask_a; channel_shift[A] = __builtin_ctz(dib->channel_mask_a); - channel_scale[A] = 0xff / ((1 << popcount(dib->channel_mask_a)) - 1); + channel_scale[A] = 0xff / ((1U << popcount(dib->channel_mask_a)) - 1); } else { channel_mask[A] = 0; channel_shift[A] = 0; @@ -170,6 +197,8 @@ static void read_channel_maks( channel_scale[B] = bpp16 ? 0x08 : 0x1; channel_scale[A] = bpp16 ? 0x00 : 0x0; } + + return EFI_SUCCESS; } static EFI_STATUS bmp_to_blt( @@ -187,7 +216,10 @@ static EFI_STATUS bmp_to_blt( uint32_t channel_mask[_CHANNELS_MAX]; uint8_t channel_shift[_CHANNELS_MAX], channel_scale[_CHANNELS_MAX]; - read_channel_maks(dib, channel_mask, channel_shift, channel_scale); + + EFI_STATUS status = read_channel_mask(dib, channel_mask, channel_shift, channel_scale); + if (status != EFI_SUCCESS) + return status; /* transform and copy pixels */ in = pixmap; diff --git a/src/boot/stub.c b/src/boot/stub.c index 65950262c69d5..8632a603a21de 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -1,12 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "boot-secret.h" +#include "console.h" #include "cpio.h" #include "device-path-util.h" #include "devicetree.h" #include "efi-efivars.h" #include "efi-log.h" +#include "efi-string.h" #include "export-vars.h" #include "graphics.h" +#include "initrd.h" #include "iovec-util-fundamental.h" #include "linux.h" #include "measure.h" @@ -29,13 +33,10 @@ /* The list of initrds we combine into one, in the order we want to merge them */ enum { - /* The first two are part of the PE binary */ - INITRD_UCODE, - INITRD_BASE, - - /* The rest are dynamically generated, and hence in dynamic memory */ - _INITRD_DYNAMIC_FIRST, - INITRD_CREDENTIAL = _INITRD_DYNAMIC_FIRST, + INITRD_UCODE, /* Part of the PE binary */ + INITRD_PREVIOUS, /* initrd already configured via the EFI protocol before we were invoked */ + INITRD_BASE, /* Part of the PE binary */ + INITRD_CREDENTIAL, INITRD_GLOBAL_CREDENTIAL, INITRD_SYSEXT, INITRD_GLOBAL_SYSEXT, @@ -45,9 +46,12 @@ enum { INITRD_PCRPKEY, INITRD_OSREL, INITRD_PROFILE, + INITRD_BOOT_SECRET, _INITRD_MAX, }; +#define INITRD_IS_STATIC(idx) IN_SET(idx, INITRD_UCODE, INITRD_BASE) + /* magic string to find in the binary image */ DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-stub " GIT_VERSION " ####"); @@ -98,50 +102,6 @@ static void combine_measured_flag(int *value, int measured) { *value = *value < 0 ? measured : *value && measured; } -/* Combine initrds by concatenation in memory */ -static EFI_STATUS combine_initrds( - const struct iovec initrds[], size_t n_initrds, - Pages *ret_initrd_pages, size_t *ret_initrd_size) { - - size_t n = 0; - - assert(initrds || n_initrds == 0); - assert(ret_initrd_pages); - assert(ret_initrd_size); - - FOREACH_ARRAY(i, initrds, n_initrds) { - /* some initrds (the ones from UKI sections) need padding, pad all to be safe */ - size_t initrd_size = ALIGN4(i->iov_len); - if (n > SIZE_MAX - initrd_size) - return EFI_OUT_OF_RESOURCES; - - n += initrd_size; - } - - _cleanup_pages_ Pages pages = xmalloc_initrd_pages(n); - uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr); - - FOREACH_ARRAY(i, initrds, n_initrds) { - size_t pad; - - p = mempcpy(p, i->iov_base, i->iov_len); - - pad = ALIGN4(i->iov_len) - i->iov_len; - if (pad == 0) - continue; - - memzero(p, pad); - p += pad; - } - - assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p); - - *ret_initrd_pages = TAKE_STRUCT(pages); - *ret_initrd_size = n; - - return EFI_SUCCESS; -} - static void export_stub_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, unsigned profile) { static const uint64_t stub_features = EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */ @@ -408,14 +368,7 @@ static void named_addon_done(NamedAddon *a) { iovec_done(&a->blob); } -static void named_addon_free_many(NamedAddon *a, size_t n) { - assert(a || n == 0); - - FOREACH_ARRAY(i, a, n) - named_addon_done(i); - - free(a); -} +static DEFINE_ARRAY_FREE_FUNC(named_addon_free_array, NamedAddon, named_addon_done); static void install_addon_devicetrees( struct devicetree_state *dt_state, @@ -553,6 +506,21 @@ static void extend_initrds( iovec_array_extend(all_initrds, n_all_initrds, *i); } +static void acquire_previous_initrd(struct iovec initrds[static _INITRD_MAX]) { + EFI_STATUS err; + + /* NB: the assumption here is that any previously installed initrd are measured by whatever + * registered them, and we just pass them on here. */ + + err = initrd_read_previous(initrds + INITRD_PREVIOUS); + if (err == EFI_NOT_FOUND) + log_debug_status(err, "No previous initrd registered."); + else if (err != EFI_SUCCESS) + log_warning_status(err, "Failed to read previously registered initrd, ignoring."); + else + log_debug("Successfully loaded previously registered initrd (%zu bytes).", initrds[INITRD_PREVIOUS].iov_len); +} + static EFI_STATUS load_addons( EFI_HANDLE stub_image, EFI_LOADED_IMAGE_PROTOCOL *loaded_image, @@ -824,10 +792,11 @@ static void cmdline_append_and_measure_smbios(char16_t **cmdline, int *parameter static void initrds_free(struct iovec (*initrds)[_INITRD_MAX]) { assert(initrds); - /* Free the dynamic initrds, but leave the non-dynamic ones around */ + /* Free the non-static initrds, but leave the static (i.e. PE embedded) ones around */ - for (size_t i = _INITRD_DYNAMIC_FIRST; i < _INITRD_MAX; i++) - iovec_done((*initrds) + i); + for (size_t i = 0; i < _INITRD_MAX; i++) + if (!INITRD_IS_STATIC(i)) + iovec_done((*initrds) + i); } static void generate_sidecar_initrds( @@ -849,9 +818,7 @@ static void generate_sidecar_initrds( /* dropin_dir= */ NULL, u".cred", /* exclude_suffix= */ NULL, - ".extra/credentials", - /* dir_mode= */ 0500, - /* access_mode= */ 0400, + &cpio_target_credentials, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Credentials initrd", initrds + INITRD_CREDENTIAL, @@ -862,9 +829,7 @@ static void generate_sidecar_initrds( u"\\loader\\credentials", u".cred", /* exclude_suffix= */ NULL, - ".extra/global_credentials", - /* dir_mode= */ 0500, - /* access_mode= */ 0400, + &cpio_target_global_credentials, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Global credentials initrd", initrds + INITRD_GLOBAL_CREDENTIAL, @@ -875,9 +840,7 @@ static void generate_sidecar_initrds( /* dropin_dir= */ NULL, u".raw", /* ideally we'd pick up only *.sysext.raw here, but for compat we pick up *.raw instead … */ u".confext.raw", /* … but then exclude *.confext.raw again */ - ".extra/sysext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, + &cpio_target_sysext, /* tpm_pcr= */ TPM2_PCR_SYSEXTS, u"System extension initrd", initrds + INITRD_SYSEXT, @@ -888,9 +851,7 @@ static void generate_sidecar_initrds( u"\\loader\\extensions", u".raw", /* as above */ u".confext.raw", - ".extra/global_sysext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, + &cpio_target_global_sysext, /* tpm_pcr= */ TPM2_PCR_SYSEXTS, u"Global system extension initrd", initrds + INITRD_GLOBAL_SYSEXT, @@ -901,9 +862,7 @@ static void generate_sidecar_initrds( /* dropin_dir= */ NULL, u".confext.raw", /* exclude_suffix= */ NULL, - ".extra/confext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, + &cpio_target_confext, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Configuration extension initrd", initrds + INITRD_CONFEXT, @@ -914,9 +873,7 @@ static void generate_sidecar_initrds( u"\\loader\\extensions", u".confext.raw", /* exclude_suffix= */ NULL, - ".extra/global_confext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, + &cpio_target_global_confext, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Global configuration extension initrd", initrds + INITRD_GLOBAL_CONFEXT, @@ -967,10 +924,8 @@ static void generate_embedded_initrds( (void) pack_cpio_literal( (const uint8_t*) loaded_image->ImageBase + sections[t->section].memory_offset, sections[t->section].memory_size, - ".extra", + &cpio_target_meta, t->filename, - /* dir_mode= */ 0555, - /* access_mode= */ 0444, /* tpm_pcr= */ UINT32_MAX, /* tpm_description= */ NULL, initrds + t->initrd_index, @@ -978,6 +933,27 @@ static void generate_embedded_initrds( } } +static void generate_boot_secret_initrd( + const uint8_t boot_secret[static BOOT_SECRET_SIZE], + struct iovec initrds[static _INITRD_MAX]) { + + assert(initrds); + + /* All zero means: no boot secret acquired */ + if (memeqzero(boot_secret, BOOT_SECRET_SIZE)) + return; + + (void) pack_cpio_literal( + boot_secret, + BOOT_SECRET_SIZE, + &cpio_target_meta_secret, + u"boot-secret", + /* tpm_pcr= */ UINT32_MAX, + /* tpm_description= */ NULL, + initrds + INITRD_BOOT_SECRET, + /* ret_measured= */ NULL); +} + static void lookup_embedded_initrds( EFI_LOADED_IMAGE_PROTOCOL *loaded_image, const PeSectionVector sections[static _UNIFIED_SECTION_MAX], @@ -1232,6 +1208,8 @@ static EFI_STATUS run(EFI_HANDLE image) { unsigned profile = 0; EFI_STATUS err; + log_set_max_level_from_smbios(); + err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image); if (err != EFI_SUCCESS) return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m"); @@ -1254,6 +1232,10 @@ static EFI_STATUS run(EFI_HANDLE image) { refresh_random_seed(loaded_image); + uint8_t boot_secret[BOOT_SECRET_SIZE] = {}; /* all zeroes means: not acquired */ + CLEANUP_ERASE(boot_secret); + (void) prepare_boot_secret(loaded_image, sections + UNIFIED_SECTION_OSREL, boot_secret); + uname = pe_section_to_str8(loaded_image, sections + UNIFIED_SECTION_UNAME); /* Let's now check if we actually want to use the command line, measure it if it was passed in. */ @@ -1261,9 +1243,9 @@ static EFI_STATUS run(EFI_HANDLE image) { /* Now that we have the UKI sections loaded, also load global first and then local (per-UKI) * addons. The data is loaded at once, and then used later. */ - CLEANUP_ARRAY(dt_addons, n_dt_addons, named_addon_free_many); - CLEANUP_ARRAY(initrd_addons, n_initrd_addons, named_addon_free_many); - CLEANUP_ARRAY(ucode_addons, n_ucode_addons, named_addon_free_many); + CLEANUP_ARRAY(dt_addons, n_dt_addons, named_addon_free_array); + CLEANUP_ARRAY(initrd_addons, n_initrd_addons, named_addon_free_array); + CLEANUP_ARRAY(ucode_addons, n_ucode_addons, named_addon_free_array); load_all_addons(image, loaded_image, uname, &cmdline_addons, &dt_addons, &n_dt_addons, &initrd_addons, &n_initrd_addons, &ucode_addons, &n_ucode_addons); /* If we have any extra command line to add via PE addons, load them now and append, and measure the @@ -1273,6 +1255,8 @@ static EFI_STATUS run(EFI_HANDLE image) { cmdline_append_and_measure_addons(cmdline_addons, &cmdline, ¶meters_measured); cmdline_append_and_measure_smbios(&cmdline, ¶meters_measured); + cmdline_append_console(&cmdline); + export_common_variables(loaded_image); export_stub_variables(loaded_image, profile); @@ -1281,8 +1265,10 @@ static EFI_STATUS run(EFI_HANDLE image) { install_addon_devicetrees(&dt_state, dt_addons, n_dt_addons, ¶meters_measured); /* Generate & find all initrds */ + acquire_previous_initrd(initrds); generate_sidecar_initrds(loaded_image, initrds, ¶meters_measured, &sysext_measured, &confext_measured); generate_embedded_initrds(loaded_image, sections, initrds); + generate_boot_secret_initrd(boot_secret, initrds); lookup_embedded_initrds(loaded_image, sections, initrds); /* Add initrds in the right order. Generally, later initrds can overwrite files in earlier ones, @@ -1290,9 +1276,10 @@ static EFI_STATUS run(EFI_HANDLE image) { * We want addons to take precedence over the base initrds, so the order is: * 1. Ucode addons * 2. UKI ucode - * 3. UKI initrd - * 4. Generated initrds - * 5. initrd addons */ + * 3. Previous initrds + * 4. UKI initrd + * 5. Generated initrds + * 6. initrd addons */ measure_and_append_ucode_addons(&all_initrds, &n_all_initrds, ucode_addons, n_ucode_addons, ¶meters_measured); extend_initrds(initrds, &all_initrds, &n_all_initrds); measure_and_append_initrd_addons(&all_initrds, &n_all_initrds, initrd_addons, n_initrd_addons, ¶meters_measured); @@ -1325,4 +1312,5 @@ static EFI_STATUS run(EFI_HANDLE image) { return err; } +// NOLINTNEXTLINE(misc-use-internal-linkage) DEFINE_EFI_MAIN_FUNCTION(run, "systemd-stub", /* wait_for_debugger= */ false); diff --git a/src/boot/test-bcd.c b/src/boot/test-bcd.c index 0924c94fa07f9..27102c236b8ab 100644 --- a/src/boot/test-bcd.c +++ b/src/boot/test-bcd.c @@ -17,7 +17,7 @@ static void load_bcd(const char *path, void **ret_bcd, size_t *ret_bcd_len) { assert_se(get_testdata_dir(path, &fn) >= 0); assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, SIZE_MAX, 0, NULL, &compressed, &len) >= 0); - assert_se(decompress_blob_zstd(compressed, len, ret_bcd, ret_bcd_len, SIZE_MAX) >= 0); + assert_se(decompress_blob(COMPRESSION_ZSTD, compressed, len, ret_bcd, ret_bcd_len, SIZE_MAX) >= 0); } static void test_get_bcd_title_one( diff --git a/src/boot/test-efi-string.c b/src/boot/test-efi-string.c index 76a891ec1fa69..7633534dd4c07 100644 --- a/src/boot/test-efi-string.c +++ b/src/boot/test-efi-string.c @@ -475,6 +475,12 @@ TEST(efi_fnmatch) { TEST_FNMATCH_ONE_MAY_SKIP_LIBC("[a\\-z]", "b", false); TEST_FNMATCH_ONE("?a*b[.-0]c", "/a/b/c", true); TEST_FNMATCH_ONE("debian-*-*-*.*", "debian-jessie-2018-06-17-kernel-image-5.10.0-16-amd64.efi", true); + TEST_FNMATCH_ONE("console=*", "console=xxx", true); + TEST_FNMATCH_ONE("* console=*", "opt1 console=ttyS0 opt2", true); + TEST_FNMATCH_ONE("console=*", " console=xxx", false); + TEST_FNMATCH_ONE("* console=", "opt1 console=ttyS0 opt2", false); + TEST_FNMATCH_ONE("console=*", "netconsole=@/eth0,@10.0.0.1/", false); + TEST_FNMATCH_ONE("* console=*", "netconsole=@/eth0,@10.0.0.1/", false); /* These would take forever with a backtracking implementation. */ TEST_FNMATCH_ONE( diff --git a/src/boot/util.c b/src/boot/util.c index 4a4c4e9365012..c40a9aad65b0d 100644 --- a/src/boot/util.c +++ b/src/boot/util.c @@ -344,6 +344,7 @@ EFI_STATUS open_directory( EFI_STATUS err; assert(root); + assert(ret); /* Opens a file, and then verifies it is actually a directory */ diff --git a/src/bootctl/bootctl-cleanup.c b/src/bootctl/bootctl-cleanup.c index 8cc51dd597ed0..1e8819bea1813 100644 --- a/src/bootctl/bootctl-cleanup.c +++ b/src/bootctl/bootctl-cleanup.c @@ -33,7 +33,7 @@ static int list_remove_orphaned_file( if (event != RECURSE_DIR_ENTRY) return RECURSE_DIR_CONTINUE; - if (hashmap_get(known_files, path)) + if (hashmap_contains(known_files, path)) return RECURSE_DIR_CONTINUE; /* keep! */ if (arg_dry_run) @@ -87,33 +87,31 @@ static int cleanup_orphaned_files( return r; } -int verb_cleanup(int argc, char *argv[], void *userdata) { +int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) { dev_t esp_devid = 0, xbootldr_devid = 0; int r; r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, + /* ret_fd= */ NULL, /* ret_part= */ NULL, /* ret_pstart= */ NULL, /* ret_psize= */ NULL, /* ret_uuid= */ NULL, &esp_devid); - if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */ - return log_error_errno(r, "Failed to determine ESP location: %m"); if (r < 0) return r; r = acquire_xbootldr( /* unprivileged_mode= */ false, + /* ret_fd= */ NULL, /* ret_uuid= */ NULL, &xbootldr_devid); - if (r == -EACCES) - return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); if (r < 0) return r; _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; - r = boot_config_load_and_select(&config, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); + r = boot_config_load_and_select(&config, arg_root, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); if (r < 0) return r; diff --git a/src/bootctl/bootctl-cleanup.h b/src/bootctl/bootctl-cleanup.h index ffe930b90073d..22d087374eb0b 100644 --- a/src/bootctl/bootctl-cleanup.h +++ b/src/bootctl/bootctl-cleanup.h @@ -3,4 +3,4 @@ #include "shared-forward.h" -int verb_cleanup(int argc, char *argv[], void *userdata); +int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 1a8d5ffb30cb2..20958d0b0bc6f 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -7,6 +7,7 @@ #include "sd-varlink.h" #include "alloc-util.h" +#include "ask-password-api.h" #include "blockdev-util.h" #include "boot-entry.h" #include "bootctl.h" @@ -15,6 +16,7 @@ #include "bootctl-util.h" #include "chase.h" #include "copy.h" +#include "crypto-util.h" #include "dirent-util.h" #include "efi-api.h" #include "efi-fundamental.h" @@ -31,7 +33,6 @@ #include "json-util.h" #include "kernel-config.h" #include "log.h" -#include "openssl-util.h" #include "parse-argument.h" #include "path-util.h" #include "pe-binary.h" @@ -117,11 +118,11 @@ static void install_context_done(InstallContext *c) { c->xbootldr_fd = safe_close(c->xbootldr_fd); #if HAVE_OPENSSL if (c->secure_boot_private_key) { - EVP_PKEY_free(c->secure_boot_private_key); + sym_EVP_PKEY_free(c->secure_boot_private_key); c->secure_boot_private_key = NULL; } if (c->secure_boot_certificate) { - X509_free(c->secure_boot_certificate); + sym_X509_free(c->secure_boot_certificate); c->secure_boot_certificate = NULL; } #endif @@ -163,6 +164,7 @@ static int install_context_from_cmdline( r = acquire_esp(/* unprivileged_mode= */ false, b.graceful, + &b.esp_fd, &b.esp_part, &b.esp_pstart, &b.esp_psize, @@ -189,6 +191,7 @@ static int install_context_from_cmdline( r = acquire_xbootldr( /* unprivileged_mode= */ false, + &b.xbootldr_fd, /* ret_uuid= */ NULL, /* ret_devid= */ NULL); if (r < 0) @@ -213,55 +216,16 @@ static int install_context_from_cmdline( return !!ret->esp_path; /* return positive if we found an ESP */ } -static int acquire_esp_fd(InstallContext *c) { - int r; - - assert(c); - - if (c->esp_fd >= 0) - return c->esp_fd; - - assert(c->esp_path); - - _cleanup_free_ char *j = path_join(c->root, c->esp_path); - if (!j) - return log_oom(); - - r = chaseat(c->root_fd, - c->esp_path, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS|CHASE_MUST_BE_DIRECTORY, - /* ret_path= */ NULL, - &c->esp_fd); - if (r < 0) - return log_error_errno(r, "Failed to open ESP '%s': %m", j); - - return c->esp_fd; -} - static int acquire_dollar_boot_fd(InstallContext *c) { - int r; - assert(c); if (c->xbootldr_fd >= 0) return c->xbootldr_fd; - if (!c->xbootldr_path) - return acquire_esp_fd(c); - - _cleanup_free_ char *j = path_join(c->root, c->xbootldr_path); - if (!j) - return log_oom(); - - r = chaseat(c->root_fd, - c->xbootldr_path, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS|CHASE_MUST_BE_DIRECTORY, - /* ret_path= */ NULL, - &c->xbootldr_fd); - if (r < 0) - return log_error_errno(r, "Failed to open XBOOTLDR '%s': %m", j); + if (c->esp_fd >= 0) + return c->esp_fd; - return c->xbootldr_fd; + return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Cannot access $BOOT, as neither ESP nor XBOOTLDR have been found."); } static const char* dollar_boot_path(InstallContext *c) { @@ -313,9 +277,10 @@ static int load_etc_machine_info(InstallContext *c) { _cleanup_close_ int fd = chase_and_openat( + c->root_fd, c->root_fd, "/etc/machine-info", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, + CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC, /* ret_path= */ NULL); if (fd == -ENOENT) @@ -429,8 +394,9 @@ static int settle_make_entry_directory(InstallContext *c) { _cleanup_close_ int fd = -EBADF; r = chaseat(c->root_fd, + c->root_fd, "/etc/machine-id", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, + CHASE_MUST_BE_REGULAR, /* ret_path= */ NULL, &fd); if (r < 0) @@ -554,10 +520,7 @@ static int copy_file_with_version_check( * might be left at the end of the file. (Resetting before rather than after a copy attempt is safer * because a previous attempt might have failed half-way, leaving the file offset at some undefined * place.) */ - if (lseek(source_fd, 0, SEEK_SET) < 0) - return log_error_errno(errno, "Failed to seek in \"%s\": %m", source_path); - - r = copy_bytes(source_fd, write_fd, UINT64_MAX, COPY_REFLINK); + r = copy_bytes(source_fd, write_fd, UINT64_MAX, COPY_REFLINK|COPY_SEEK0_SOURCE); if (r < 0) return log_error_errno(r, "Failed to copy data from \"%s\" to \"%s\": %m", source_path, dest_path); @@ -586,8 +549,9 @@ static int mkdir_one(const char *root, int root_fd, const char *path) { return log_oom(); r = chaseat(root_fd, + root_fd, path, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, /* ret_fd= */ NULL); if (r < 0) @@ -642,9 +606,8 @@ static int update_efi_boot_binaries( assert(c); assert(source_path); - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_free_ char *j = path_join(c->root, c->esp_path); if (!j) @@ -652,9 +615,10 @@ static int update_efi_boot_binaries( _cleanup_closedir_ DIR *d = NULL; r = chase_and_opendirat( - esp_fd, + c->esp_fd, + c->esp_fd, "/EFI/BOOT", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &d); if (r == -ENOENT) @@ -720,9 +684,10 @@ static int copy_one_file( _cleanup_close_ int source_fd = -EBADF; if (IN_SET(c->install_source, INSTALL_SOURCE_AUTO, INSTALL_SOURCE_IMAGE)) { source_fd = chase_and_openat( + c->root_fd, c->root_fd, sp, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, + CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC, &source_path); if (source_fd < 0 && (source_fd != -ENOENT || c->install_source != INSTALL_SOURCE_AUTO)) @@ -741,18 +706,18 @@ static int copy_one_file( return log_error_errno(source_fd, "Failed to resolve path '%s': %m", sp); } - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_free_ char *j = path_join(c->root, c->esp_path); if (!j) return log_oom(); _cleanup_close_ int dest_parent_fd = -EBADF; - r = chaseat(esp_fd, + r = chaseat(c->esp_fd, + c->esp_fd, "/EFI/systemd", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &dest_parent_fd); if (r < 0) @@ -781,9 +746,10 @@ static int copy_one_file( ascii_strupper(boot_dot_efi); _cleanup_close_ int default_dest_parent_fd = -EBADF; - r = chaseat(esp_fd, + r = chaseat(c->esp_fd, + c->esp_fd, "/EFI/BOOT", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &default_dest_parent_fd); if (r < 0) @@ -820,9 +786,10 @@ static int install_binaries( _cleanup_closedir_ DIR *d = NULL; if (IN_SET(c->install_source, INSTALL_SOURCE_AUTO, INSTALL_SOURCE_IMAGE)) { r = chase_and_opendirat( + c->root_fd, c->root_fd, BOOTLIBDIR, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_DIRECTORY, + CHASE_MUST_BE_DIRECTORY, &source_path, &d); if (r < 0 && (r != -ENOENT || c->install_source != INSTALL_SOURCE_AUTO)) @@ -878,18 +845,18 @@ static int install_loader_config(InstallContext *c) { assert(c); assert(c->make_entry_directory >= 0); - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_free_ char *j = path_join(c->root, c->esp_path); if (!j) return log_oom(); _cleanup_close_ int loader_dir_fd = -EBADF; - r = chaseat(esp_fd, + r = chaseat(c->esp_fd, + c->esp_fd, "loader", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &loader_dir_fd); if (r < 0) @@ -942,8 +909,9 @@ static int install_loader_specification(InstallContext *c) { _cleanup_close_ int loader_dir_fd = -EBADF; r = chaseat(dollar_boot_fd, + dollar_boot_fd, "loader", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &loader_dir_fd); if (r < 0) @@ -1016,8 +984,9 @@ static int install_entry_token(InstallContext *c) { _cleanup_close_ int dfd = -EBADF; r = chaseat(c->root_fd, + c->root_fd, confdir, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &dfd); if (r < 0) @@ -1067,25 +1036,29 @@ static int install_secure_boot_auto_enroll(InstallContext *c) { if (!c->secure_boot_certificate || !c->secure_boot_private_key) return 0; + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + _cleanup_free_ uint8_t *dercert = NULL; int dercertsz; - dercertsz = i2d_X509(c->secure_boot_certificate, &dercert); + dercertsz = sym_i2d_X509(c->secure_boot_certificate, &dercert); if (dercertsz < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert X.509 certificate to DER: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_free_ char *j = path_join(c->root, c->esp_path); if (!j) return log_oom(); _cleanup_close_ int keys_fd = -EBADF; - r = chaseat(esp_fd, + r = chaseat(c->esp_fd, + c->esp_fd, "loader/keys/auto", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &keys_fd); if (r < 0) @@ -1119,7 +1092,7 @@ static int install_secure_boot_auto_enroll(InstallContext *c) { FOREACH_STRING(db, "PK", "KEK", "db") { _cleanup_(BIO_freep) BIO *bio = NULL; - bio = BIO_new(BIO_s_mem()); + bio = sym_BIO_new(sym_BIO_s_mem()); if (!bio) return log_oom(); @@ -1128,34 +1101,34 @@ static int install_secure_boot_auto_enroll(InstallContext *c) { return log_oom(); /* Don't count the trailing NUL terminator. */ - if (BIO_write(bio, db16, char16_strsize(db16) - sizeof(char16_t)) < 0) + if (sym_BIO_write(bio, db16, char16_strsize(db16) - sizeof(char16_t)) < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable name to bio"); EFI_GUID *guid = STR_IN_SET(db, "PK", "KEK") ? &(EFI_GUID) EFI_GLOBAL_VARIABLE : &(EFI_GUID) EFI_IMAGE_SECURITY_DATABASE_GUID; - if (BIO_write(bio, guid, sizeof(*guid)) < 0) + if (sym_BIO_write(bio, guid, sizeof(*guid)) < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable GUID to bio"); - if (BIO_write(bio, &attrs, sizeof(attrs)) < 0) + if (sym_BIO_write(bio, &attrs, sizeof(attrs)) < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable attributes to bio"); - if (BIO_write(bio, ×tamp, sizeof(timestamp)) < 0) + if (sym_BIO_write(bio, ×tamp, sizeof(timestamp)) < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write timestamp to bio"); - if (BIO_write(bio, siglist, siglistsz) < 0) + if (sym_BIO_write(bio, siglist, siglistsz) < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write signature list to bio"); _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL; - p7 = PKCS7_sign(c->secure_boot_certificate, c->secure_boot_private_key, /* certs= */ NULL, bio, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY|PKCS7_NOSMIMECAP); + p7 = sym_PKCS7_sign(c->secure_boot_certificate, c->secure_boot_private_key, /* certs= */ NULL, bio, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY|PKCS7_NOSMIMECAP); if (!p7) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to calculate PKCS7 signature: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); _cleanup_free_ uint8_t *sig = NULL; - int sigsz = i2d_PKCS7(p7, &sig); + int sigsz = sym_i2d_PKCS7(p7, &sig); if (sigsz < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); size_t authsz = offsetof(EFI_VARIABLE_AUTHENTICATION_2, AuthInfo.CertData) + sigsz; _cleanup_free_ EFI_VARIABLE_AUTHENTICATION_2 *auth = malloc(authsz); @@ -1233,6 +1206,8 @@ static bool same_entry(uint16_t id, sd_id128_t uuid, const char *path) { static int find_slot(sd_id128_t uuid, const char *path, uint16_t *id) { _cleanup_free_ uint16_t *options = NULL; + assert(id); + int n = efi_get_boot_options(&options); if (n < 0) return n; @@ -1386,18 +1361,18 @@ static int install_variables( assert(c); - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_free_ char *j = path_join(c->root, c->esp_path); if (!j) return log_oom(); r = chase_and_accessat( - esp_fd, + c->esp_fd, + c->esp_fd, path, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_REGULAR, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_REGULAR, F_OK, /* ret_path= */ NULL); if (r == -ENOENT) @@ -1423,7 +1398,7 @@ static int install_variables( if (c->operation == INSTALL_NEW || !existing) { _cleanup_free_ char *description = NULL; - r = pick_efi_boot_option_description(esp_fd, &description); + r = pick_efi_boot_option_description(c->esp_fd, &description); if (r < 0) return r; @@ -1475,14 +1450,14 @@ static int are_we_installed(InstallContext *c) { if (!p) return log_oom(); - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_close_ int fd = chase_and_openat( - esp_fd, + c->esp_fd, + c->esp_fd, "/EFI/systemd", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, O_RDONLY|O_CLOEXEC|O_DIRECTORY, /* ret_path= */ NULL); if (fd == -ENOENT) @@ -1583,9 +1558,8 @@ static int run_install(InstallContext *c) { const char *arch = arg_arch_all ? "" : get_efi_arch(); - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_free_ char *j = path_join(c->root, c->esp_path); if (!j) @@ -1605,7 +1579,7 @@ static int run_install(InstallContext *c) { * we'll drop-in our files (unless there are newer ones already), but we won't create * the directories for them in the first place. */ - r = create_subdirs(j, esp_fd, esp_subdirs); + r = create_subdirs(j, c->esp_fd, esp_subdirs); if (r < 0) return r; @@ -1632,7 +1606,7 @@ static int run_install(InstallContext *c) { return r; if (arg_install_random_seed && !c->root) { - r = install_random_seed(c->esp_path); + r = install_random_seed(c->esp_path, c->esp_fd); if (r < 0) return r; } @@ -1661,7 +1635,7 @@ static int run_install(InstallContext *c) { return install_variables(c, path); } -int verb_install(int argc, char *argv[], void *userdata) { +int verb_install(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; /* Invoked for both "update" and "install" */ @@ -1690,9 +1664,8 @@ static int remove_boot_efi(InstallContext *c) { assert(c); - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_free_ char *w = path_join(c->root, c->esp_path); if (!w) @@ -1701,9 +1674,10 @@ static int remove_boot_efi(InstallContext *c) { _cleanup_closedir_ DIR *d = NULL; _cleanup_free_ char *p = NULL; r = chase_and_opendirat( - esp_fd, + c->esp_fd, + c->esp_fd, "/EFI/BOOT", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, &p, &d); if (r == -ENOENT) @@ -1771,9 +1745,10 @@ static int unlink_inode(const char *root, int root_fd, const char *path, mode_t return log_oom(); r = chase_and_unlinkat( + root_fd, root_fd, path, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS, + CHASE_PROHIBIT_SYMLINKS, S_ISDIR(type) ? AT_REMOVEDIR : 0, /* ret_path= */ NULL); if (r < 0) { @@ -1819,8 +1794,9 @@ static int remove_binaries(InstallContext *c) { _cleanup_close_ int efi_fd = -EBADF; r = chaseat(c->esp_fd, + c->esp_fd, "EFI", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &efi_fd); if (r < 0) { @@ -1882,7 +1858,7 @@ static int remove_loader_variables(void) { return r; } -int verb_remove(int argc, char *argv[], void *userdata) { +int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t uuid = SD_ID128_NULL; int r; @@ -1899,15 +1875,14 @@ int verb_remove(int argc, char *argv[], void *userdata) { if (r < 0) return r; - int esp_fd = acquire_esp_fd(&c); - if (esp_fd < 0) - return esp_fd; + if (c.esp_fd < 0) + return c.esp_fd; _cleanup_free_ char *j = path_join(c.root, c.esp_path); if (!j) return log_oom(); - int dollar_boot_fd = acquire_dollar_boot_fd(&c); /* this will initialize .xbootldr_fd */ + int dollar_boot_fd = acquire_dollar_boot_fd(&c); if (dollar_boot_fd < 0) return dollar_boot_fd; @@ -1916,23 +1891,23 @@ int verb_remove(int argc, char *argv[], void *userdata) { return log_oom(); r = remove_binaries(&c); - RET_GATHER(r, unlink_inode(j, esp_fd, "/loader/loader.conf", S_IFREG)); - RET_GATHER(r, unlink_inode(j, esp_fd, "/loader/random-seed", S_IFREG)); - RET_GATHER(r, unlink_inode(j, esp_fd, "/loader/entries.srel", S_IFREG)); + RET_GATHER(r, unlink_inode(j, c.esp_fd, "/loader/loader.conf", S_IFREG)); + RET_GATHER(r, unlink_inode(j, c.esp_fd, "/loader/random-seed", S_IFREG)); + RET_GATHER(r, unlink_inode(j, c.esp_fd, "/loader/entries.srel", S_IFREG)); FOREACH_STRING(db, "PK.auth", "KEK.auth", "db.auth") { _cleanup_free_ char *p = path_join("/loader/keys/auto", db); if (!p) return log_oom(); - RET_GATHER(r, unlink_inode(j, esp_fd, p, S_IFREG)); + RET_GATHER(r, unlink_inode(j, c.esp_fd, p, S_IFREG)); } - RET_GATHER(r, unlink_inode(j, esp_fd, "/loader/keys/auto", S_IFDIR)); - RET_GATHER(r, unlink_inode(j, esp_fd, "/loader/entries.srel", S_IFREG)); + RET_GATHER(r, unlink_inode(j, c.esp_fd, "/loader/keys/auto", S_IFDIR)); + RET_GATHER(r, unlink_inode(j, c.esp_fd, "/loader/entries.srel", S_IFREG)); - RET_GATHER(r, remove_subdirs(j, esp_fd, esp_subdirs)); - RET_GATHER(r, remove_subdirs(j, esp_fd, dollar_boot_subdirs)); - RET_GATHER(r, remove_entry_directory(&c, j, esp_fd)); + RET_GATHER(r, remove_subdirs(j, c.esp_fd, esp_subdirs)); + RET_GATHER(r, remove_subdirs(j, c.esp_fd, dollar_boot_subdirs)); + RET_GATHER(r, remove_entry_directory(&c, j, c.esp_fd)); if (c.xbootldr_fd >= 0) { /* Remove a subset of these also from the XBOOTLDR partition if it exists */ @@ -1956,7 +1931,7 @@ int verb_remove(int argc, char *argv[], void *userdata) { return RET_GATHER(r, remove_loader_variables()); } -int verb_is_installed(int argc, char *argv[], void *userdata) { +int verb_is_installed(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(install_context_done) InstallContext c = INSTALL_CONTEXT_NULL; @@ -2062,11 +2037,12 @@ int vl_method_install( if (p.context.entry_token_type < 0) p.context.entry_token_type = BOOT_ENTRY_TOKEN_AUTO; - r = find_esp_and_warn_at( + r = find_esp_and_warn_at_full( p.context.root_fd, /* path= */ NULL, /* unprivileged_mode= */ false, &p.context.esp_path, + &p.context.esp_fd, &p.context.esp_part, &p.context.esp_pstart, &p.context.esp_psize, @@ -2082,8 +2058,7 @@ int vl_method_install( /* path= */ NULL, /* unprivileged_mode= */ false, &p.context.xbootldr_path, - /* ret_uuid= */ NULL, - /* ret_devid= */ NULL); + &p.context.xbootldr_fd); if (r == -ENOKEY) log_debug_errno(r, "Didn't find an XBOOTLDR partition, using ESP as $BOOT."); else if (r < 0) diff --git a/src/bootctl/bootctl-install.h b/src/bootctl/bootctl-install.h index f2d7fab5c965e..c9019520cc9e9 100644 --- a/src/bootctl/bootctl-install.h +++ b/src/bootctl/bootctl-install.h @@ -3,8 +3,8 @@ #include "shared-forward.h" -int verb_install(int argc, char *argv[], void *userdata); -int verb_remove(int argc, char *argv[], void *userdata); -int verb_is_installed(int argc, char *argv[], void *userdata); +int verb_install(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_is_installed(int argc, char *argv[], uintptr_t _data, void *userdata); int vl_method_install(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/bootctl/bootctl-random-seed.c b/src/bootctl/bootctl-random-seed.c index 62ff8fa07ffe2..be33d9f950fbf 100644 --- a/src/bootctl/bootctl-random-seed.c +++ b/src/bootctl/bootctl-random-seed.c @@ -111,8 +111,8 @@ static int set_system_token(void) { return 0; } -int install_random_seed(const char *esp) { - _cleanup_close_ int esp_fd = -EBADF, loader_dir_fd = -EBADF, fd = -EBADF; +int install_random_seed(const char *esp, int esp_fd) { + _cleanup_close_ int loader_dir_fd = -EBADF, fd = -EBADF; _cleanup_free_ char *tmp = NULL; uint8_t buffer[RANDOM_EFI_SEED_SIZE]; struct sha256_ctx hash_state; @@ -120,16 +120,13 @@ int install_random_seed(const char *esp) { int r; assert(esp); + assert(esp_fd >= 0); assert_cc(RANDOM_EFI_SEED_SIZE == SHA256_DIGEST_SIZE); if (!arg_install_random_seed) return 0; - esp_fd = open(esp, O_DIRECTORY|O_RDONLY|O_CLOEXEC); - if (esp_fd < 0) - return log_error_errno(errno, "Failed to open ESP directory '%s': %m", esp); - (void) random_seed_verify_permissions(esp_fd, S_IFDIR); loader_dir_fd = open_mkdir_at(esp_fd, "loader", O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOFOLLOW, 0775); @@ -201,10 +198,11 @@ int install_random_seed(const char *esp) { return set_system_token(); } -int verb_random_seed(int argc, char *argv[], void *userdata) { +int verb_random_seed(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; - r = find_esp_and_warn(arg_root, arg_esp_path, false, &arg_esp_path, NULL, NULL, NULL, NULL, NULL); + _cleanup_close_ int esp_fd = -EBADF; + r = find_esp_and_warn(arg_root, arg_esp_path, /* unprivileged_mode= */ false, &arg_esp_path, &esp_fd); if (r == -ENOKEY) { /* find_esp_and_warn() doesn't warn about ENOKEY, so let's do that on our own */ if (arg_graceful() == ARG_GRACEFUL_NO) @@ -216,7 +214,7 @@ int verb_random_seed(int argc, char *argv[], void *userdata) { if (r < 0) return r; - r = install_random_seed(arg_esp_path); + r = install_random_seed(arg_esp_path, esp_fd); if (r < 0) return r; diff --git a/src/bootctl/bootctl-random-seed.h b/src/bootctl/bootctl-random-seed.h index 91596d3c818e2..1764668b3a3d5 100644 --- a/src/bootctl/bootctl-random-seed.h +++ b/src/bootctl/bootctl-random-seed.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int install_random_seed(const char *esp); +#include "shared-forward.h" -int verb_random_seed(int argc, char *argv[], void *userdata); +int install_random_seed(const char *esp, int esp_fd); + +int verb_random_seed(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/bootctl/bootctl-reboot-to-firmware.c b/src/bootctl/bootctl-reboot-to-firmware.c index be20a4a13227a..aa5f186af9d4a 100644 --- a/src/bootctl/bootctl-reboot-to-firmware.c +++ b/src/bootctl/bootctl-reboot-to-firmware.c @@ -12,7 +12,7 @@ #include "log.h" #include "parse-util.h" -int verb_reboot_to_firmware(int argc, char *argv[], void *userdata) { +int verb_reboot_to_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = verify_touch_variables_allowed(argv[0]); diff --git a/src/bootctl/bootctl-reboot-to-firmware.h b/src/bootctl/bootctl-reboot-to-firmware.h index c8b55004480be..0262c861c4006 100644 --- a/src/bootctl/bootctl-reboot-to-firmware.h +++ b/src/bootctl/bootctl-reboot-to-firmware.h @@ -3,7 +3,7 @@ #include "shared-forward.h" -int verb_reboot_to_firmware(int argc, char *argv[], void *userdata); +int verb_reboot_to_firmware(int argc, char *argv[], uintptr_t _data, void *userdata); int vl_method_set_reboot_to_firmware(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_get_reboot_to_firmware(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/bootctl/bootctl-set-efivar.c b/src/bootctl/bootctl-set-efivar.c index bb853d65afe88..5edcd29f313be 100644 --- a/src/bootctl/bootctl-set-efivar.c +++ b/src/bootctl/bootctl-set-efivar.c @@ -129,7 +129,7 @@ static int parse_loader_entry_target_arg(const char *arg1, char16_t **ret_target return 0; } -int verb_set_efivar(int argc, char *argv[], void *userdata) { +int verb_set_efivar(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = verify_touch_variables_allowed(argv[0]); diff --git a/src/bootctl/bootctl-set-efivar.h b/src/bootctl/bootctl-set-efivar.h index 6441681081ae1..ee5e518b440a9 100644 --- a/src/bootctl/bootctl-set-efivar.h +++ b/src/bootctl/bootctl-set-efivar.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_set_efivar(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_set_efivar(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 0804070d24dfc..2c0eb4d1d00d4 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -22,7 +22,6 @@ #include "pretty-print.h" #include "string-util.h" #include "tpm2-util.h" -#include "varlink-util.h" static int status_entries( const BootConfig *config, @@ -326,7 +325,7 @@ static void print_yes_no_line(bool first, bool good, const char *name) { name); } -int verb_status(int argc, char *argv[], void *userdata) { +int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t esp_uuid = SD_ID128_NULL, xbootldr_uuid = SD_ID128_NULL; dev_t esp_devid = 0, xbootldr_devid = 0; int r, k; @@ -335,6 +334,7 @@ int verb_status(int argc, char *argv[], void *userdata) { r = acquire_esp(/* unprivileged_mode= */ -1, /* graceful= */ false, + /* ret_fd= */ NULL, /* ret_part= */ NULL, /* ret_pstart= */ NULL, /* ret_psize= */ NULL, @@ -353,6 +353,7 @@ int verb_status(int argc, char *argv[], void *userdata) { r = acquire_xbootldr( /* unprivileged_mode= */ -1, + /* ret_fd= */ NULL, &xbootldr_uuid, &xbootldr_devid); if (arg_print_dollar_boot_path) { @@ -477,6 +478,16 @@ int verb_status(int argc, char *argv[], void *userdata) { printf(" Measured UKI: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal()); } + k = efi_measured_os(LOG_DEBUG); + if (k > 0) + printf(" Measured OS: %syes%s\n", ansi_highlight_green(), ansi_normal()); + else if (k == 0) + printf(" Measured OS: no\n"); + else { + errno = -k; + printf(" Measured OS: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal()); + } + k = efi_get_reboot_to_firmware(); if (k > 0) printf(" Boot into FW: %sactive%s\n", ansi_highlight_yellow(), ansi_normal()); @@ -606,9 +617,11 @@ int verb_status(int argc, char *argv[], void *userdata) { if (arg_esp_path || arg_xbootldr_path) { _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; - k = boot_config_load_and_select(&config, - arg_esp_path, esp_devid, - arg_xbootldr_path, xbootldr_devid); + k = boot_config_load_and_select( + &config, + arg_root, + arg_esp_path, esp_devid, + arg_xbootldr_path, xbootldr_devid); RET_GATHER(r, k); if (k >= 0) @@ -621,7 +634,7 @@ int verb_status(int argc, char *argv[], void *userdata) { return r; } -int verb_list(int argc, char *argv[], void *userdata) { +int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; dev_t esp_devid = 0, xbootldr_devid = 0; int r; @@ -633,19 +646,30 @@ int verb_list(int argc, char *argv[], void *userdata) { (void) touch_variables(); - r = acquire_esp(/* unprivileged_mode= */ -1, /* graceful= */ false, NULL, NULL, NULL, NULL, &esp_devid); + r = acquire_esp(/* unprivileged_mode= */ -1, + /* graceful= */ false, + /* ret_fd= */ NULL, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid= */ NULL, + &esp_devid); if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */ return log_error_errno(r, "Failed to determine ESP location: %m"); if (r < 0) return r; - r = acquire_xbootldr(/* unprivileged_mode= */ -1, NULL, &xbootldr_devid); + r = acquire_xbootldr( + /* unprivileged_mode= */ -1, + /* ret_fd= */ NULL, + /* ret_uuid= */ NULL, + &xbootldr_devid); if (r == -EACCES) return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); if (r < 0) return r; - r = boot_config_load_and_select(&config, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); + r = boot_config_load_and_select(&config, arg_root, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); if (r < 0) return r; @@ -672,6 +696,7 @@ int vl_method_list_boot_entries(sd_varlink *link, sd_json_variant *parameters, s r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, + /* ret_fd= */ NULL, /* ret_part= */ NULL, /* ret_pstart= */ NULL, /* ret_psize= */ NULL, @@ -684,6 +709,7 @@ int vl_method_list_boot_entries(sd_varlink *link, sd_json_variant *parameters, s r = acquire_xbootldr( /* unprivileged_mode= */ false, + /* ret_fd= */ NULL, /* ret_uuid= */ NULL, &xbootldr_devid); if (r == -EACCES) @@ -691,11 +717,11 @@ int vl_method_list_boot_entries(sd_varlink *link, sd_json_variant *parameters, s if (r < 0) return r; - r = boot_config_load_and_select(&config, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); + r = boot_config_load_and_select(&config, arg_root, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); if (r < 0) return r; - r = varlink_set_sentinel(link, "io.systemd.BootControl.NoSuchBootEntry"); + r = sd_varlink_set_sentinel(link, "io.systemd.BootControl.NoSuchBootEntry"); if (r < 0) return r; diff --git a/src/bootctl/bootctl-status.h b/src/bootctl/bootctl-status.h index 36609fb075b64..941bcdd9aca79 100644 --- a/src/bootctl/bootctl-status.h +++ b/src/bootctl/bootctl-status.h @@ -3,7 +3,7 @@ #include "shared-forward.h" -int verb_status(int argc, char *argv[], void *userdata); -int verb_list(int argc, char *argv[], void *userdata); +int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata); int vl_method_list_boot_entries(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/bootctl/bootctl-uki.c b/src/bootctl/bootctl-uki.c index 2f71ccd36e1de..7c37081cbfdd7 100644 --- a/src/bootctl/bootctl-uki.c +++ b/src/bootctl/bootctl-uki.c @@ -5,27 +5,28 @@ #include "alloc-util.h" #include "bootctl-uki.h" #include "kernel-image.h" +#include "log.h" -int verb_kernel_identify(int argc, char *argv[], void *userdata) { +int verb_kernel_identify(int argc, char *argv[], uintptr_t _data, void *userdata) { KernelImageType t; int r; - r = inspect_kernel(AT_FDCWD, argv[1], &t, NULL, NULL, NULL); + r = inspect_kernel(AT_FDCWD, argv[1], &t); if (r < 0) - return r; + return log_error_errno(r, "Failed to inspect '%s': %m", argv[1]); puts(kernel_image_type_to_string(t)); return 0; } -int verb_kernel_inspect(int argc, char *argv[], void *userdata) { +int verb_kernel_inspect(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *cmdline = NULL, *uname = NULL, *pname = NULL; KernelImageType t; int r; - r = inspect_kernel(AT_FDCWD, argv[1], &t, &cmdline, &uname, &pname); + r = inspect_kernel_full(AT_FDCWD, argv[1], &t, &cmdline, &uname, &pname); if (r < 0) - return r; + return log_error_errno(r, "Failed to inspect '%s': %m", argv[1]); printf("Kernel Type: %s\n", kernel_image_type_to_string(t)); if (cmdline) diff --git a/src/bootctl/bootctl-uki.h b/src/bootctl/bootctl-uki.h index 99c8ff5c8bf9f..febac7394d354 100644 --- a/src/bootctl/bootctl-uki.h +++ b/src/bootctl/bootctl-uki.h @@ -3,5 +3,5 @@ #include "shared-forward.h" -int verb_kernel_identify(int argc, char *argv[], void *userdata); -int verb_kernel_inspect(int argc, char *argv[], void *userdata); +int verb_kernel_identify(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_kernel_inspect(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/bootctl/bootctl-unlink.c b/src/bootctl/bootctl-unlink.c index 287b2b7904c92..0d0e7ad076b60 100644 --- a/src/bootctl/bootctl-unlink.c +++ b/src/bootctl/bootctl-unlink.c @@ -199,12 +199,13 @@ static int unlink_entry(const BootConfig *config, const char *root, const char * return 0; } -int verb_unlink(int argc, char *argv[], void *userdata) { +int verb_unlink(int argc, char *argv[], uintptr_t _data, void *userdata) { dev_t esp_devid = 0, xbootldr_devid = 0; int r; r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, + /* ret_fd= */ NULL, /* ret_part= */ NULL, /* ret_pstart= */ NULL, /* ret_psize= */ NULL, @@ -217,6 +218,7 @@ int verb_unlink(int argc, char *argv[], void *userdata) { r = acquire_xbootldr( /* unprivileged_mode= */ false, + /* ret_fd= */ NULL, /* ret_uuid= */ NULL, &xbootldr_devid); if (r == -EACCES) @@ -227,6 +229,7 @@ int verb_unlink(int argc, char *argv[], void *userdata) { _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; r = boot_config_load_and_select( &config, + arg_root, arg_esp_path, esp_devid, arg_xbootldr_path, diff --git a/src/bootctl/bootctl-unlink.h b/src/bootctl/bootctl-unlink.h index 5737977ae5ecf..5c33088859437 100644 --- a/src/bootctl/bootctl-unlink.h +++ b/src/bootctl/bootctl-unlink.h @@ -3,6 +3,6 @@ #include "shared-forward.h" -int verb_unlink(int argc, char *argv[], void *userdata); +int verb_unlink(int argc, char *argv[], uintptr_t _data, void *userdata); int boot_config_count_known_files(const BootConfig *config, const char* root, Hashmap **ret_known_files); diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index da4592a7240a8..942ef4d681875 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-varlink.h" @@ -16,19 +15,23 @@ #include "bootctl-status.h" #include "bootctl-uki.h" #include "bootctl-unlink.h" +#include "bootctl-util.h" #include "build.h" +#include "crypto-util.h" #include "devnum-util.h" #include "dissect-image.h" #include "efi-loader.h" #include "efivars.h" #include "escape.h" +#include "fd-util.h" #include "find-esp.h" +#include "format-table.h" #include "image-policy.h" #include "log.h" #include "loop-util.h" #include "main-func.h" #include "mount-util.h" -#include "openssl-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "path-util.h" @@ -36,7 +39,6 @@ #include "string-table.h" #include "string-util.h" #include "strv.h" -#include "utf8.h" #include "varlink-io.systemd.BootControl.h" #include "varlink-util.h" #include "verbs.h" @@ -50,6 +52,7 @@ bool arg_print_esp_path = false; bool arg_print_dollar_boot_path = false; bool arg_print_loader_path = false; bool arg_print_stub_path = false; +bool arg_print_efi_architecture = false; unsigned arg_print_root_device = 0; int arg_touch_variables = -1; bool arg_install_random_seed = true; @@ -99,16 +102,16 @@ static const char* const install_source_table[_INSTALL_SOURCE_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(install_source, InstallSource); -int acquire_esp( - int unprivileged_mode, +int acquire_esp(int unprivileged_mode, bool graceful, + int *ret_fd, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid) { - char *np; + _cleanup_free_ char *np = NULL; int r; /* Find the ESP, and log about errors. Note that find_esp_and_warn() will log in all error cases on @@ -117,7 +120,7 @@ int acquire_esp( * we simply eat up the error here, so that --list and --status work too, without noise about * this). */ - r = find_esp_and_warn(arg_root, arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); + r = find_esp_and_warn_full(arg_root, arg_esp_path, unprivileged_mode, &np, ret_fd, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); if (r == -ENOKEY) { if (graceful) return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_INFO, r, @@ -133,27 +136,44 @@ int acquire_esp( free_and_replace(arg_esp_path, np); log_debug("Using EFI System Partition at %s.", arg_esp_path); - return 0; + return 1; /* for symmetry with acquire_xbootldr() below: found */ } int acquire_xbootldr( int unprivileged_mode, + int *ret_fd, sd_id128_t *ret_uuid, dev_t *ret_devid) { - char *np; int r; - r = find_xbootldr_and_warn(arg_root, arg_xbootldr_path, unprivileged_mode, &np, ret_uuid, ret_devid); - if (r == -ENOKEY || path_equal(np, arg_esp_path)) { - log_debug("Didn't find an XBOOTLDR partition, using the ESP as $BOOT."); + _cleanup_free_ char *np = NULL; + _cleanup_close_ int fd = -EBADF; + r = find_xbootldr_and_warn_full( + arg_root, + arg_xbootldr_path, + unprivileged_mode, + &np, + ret_fd ? &fd : NULL, + ret_uuid, + ret_devid); + if (r == -ENOKEY || (r >= 0 && arg_esp_path && path_equal(np, arg_esp_path))) { + + if (arg_esp_path) + log_debug("Didn't find an XBOOTLDR partition, using the ESP as $BOOT."); + else + log_debug("Found neither an XBOOTLDR partition, nor an ESP."); + arg_xbootldr_path = mfree(arg_xbootldr_path); + if (ret_fd) + *ret_fd = -EBADF; if (ret_uuid) *ret_uuid = SD_ID128_NULL; if (ret_devid) *ret_devid = 0; - return 0; + + return 0; /* not found */ } if (r < 0) return r; @@ -161,7 +181,10 @@ int acquire_xbootldr( free_and_replace(arg_xbootldr_path, np); log_debug("Using XBOOTLDR partition at %s as $BOOT.", arg_xbootldr_path); - return 1; + if (ret_fd) + *ret_fd = TAKE_FD(fd); + + return 1; /* found */ } static int print_loader_or_stub_path(void) { @@ -198,9 +221,14 @@ static int print_loader_or_stub_path(void) { } sd_id128_t esp_uuid; - r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, - /* ret_part= */ NULL, /* ret_pstart= */ NULL, /* ret_psize= */ NULL, - &esp_uuid, /* ret_devid= */ NULL); + r = acquire_esp(/* unprivileged_mode= */ false, + /* graceful= */ false, + /* ret_fd= */ NULL, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + &esp_uuid, + /* ret_devid= */ NULL); if (r < 0) return r; @@ -210,7 +238,10 @@ static int print_loader_or_stub_path(void) { else if (arg_print_stub_path) { /* In case of the stub, also look for things in the xbootldr partition */ sd_id128_t xbootldr_uuid; - r = acquire_xbootldr(/* unprivileged_mode= */ false, &xbootldr_uuid, /* ret_devid= */ NULL); + r = acquire_xbootldr(/* unprivileged_mode= */ false, + /* ret_fd= */ NULL, + &xbootldr_uuid, + /* ret_devid= */ NULL); if (r < 0) return r; @@ -244,7 +275,7 @@ GracefulMode arg_graceful(void) { return _arg_graceful; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -254,293 +285,272 @@ static int help(int argc, char *argv[], void *userdata) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sControl EFI firmware boot settings and manage boot loader.%6$s\n" - "\n%3$sGeneric EFI Firmware/Boot Loader Commands:%4$s\n" - " status Show status of installed boot loader and EFI variables\n" - " reboot-to-firmware [BOOL]\n" - " Query or set reboot-to-firmware EFI flag\n" - "\n%3$sBoot Loader Specification Commands:%4$s\n" - " list List boot loader entries\n" - " unlink ID Remove boot loader entry\n" - " cleanup Remove files in ESP not referenced in any boot entry\n" - "\n%3$sBoot Loader Interface Commands:%4$s\n" - " set-default ID Set default boot loader entry\n" - " set-oneshot ID Set default boot loader entry, for next boot only\n" - " set-sysfail ID Set boot loader entry used in case of a system failure\n" - " set-timeout SECONDS Set the menu timeout\n" - " set-timeout-oneshot SECONDS\n" - " Set the menu timeout for the next boot only\n" - "\n%3$ssystemd-boot Commands:%4$s\n" - " install Install systemd-boot to the ESP and EFI variables\n" - " update Update systemd-boot in the ESP and EFI variables\n" - " remove Remove systemd-boot from the ESP and EFI variables\n" - " is-installed Test whether systemd-boot is installed in the ESP\n" - " random-seed Initialize or refresh random seed in ESP and EFI\n" - " variables\n" - "\n%3$sKernel Image Commands:%4$s\n" - " kernel-identify KERNEL-IMAGE\n" - " Identify kernel image type\n" - " kernel-inspect KERNEL-IMAGE\n" - " Prints details about the kernel image\n" - "\n%3$sBlock Device Discovery Commands:%4$s\n" - " -p --print-esp-path Print path to the EFI System Partition mount point\n" - " -x --print-boot-path Print path to the $BOOT partition mount point\n" - " --print-loader-path\n" - " Print path to currently booted boot loader binary\n" - " --print-stub-path Print path to currently booted unified kernel binary\n" - " -R --print-root-device\n" - " Print path to the block device node backing the\n" - " root file system (returns e.g. /dev/nvme0n1p5)\n" - " -RR Print path to the whole disk block device node\n" - " backing the root FS (returns e.g. /dev/nvme0n1)\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --esp-path=PATH Path to the EFI System Partition (ESP)\n" - " --boot-path=PATH Path to the $BOOT partition\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY\n" - " Specify disk image dissection policy\n" - " --install-source=auto|image|host\n" - " Where to pick files when using --root=/--image=\n" - " --variables=yes|no\n" - " Whether to modify EFI variables\n" - " --random-seed=yes|no\n" - " Whether to create random-seed file during install\n" - " --no-pager Do not pipe output into a pager\n" - " --graceful Don't fail when the ESP cannot be found or EFI\n" - " variables cannot be written\n" - " -q --quiet Suppress output\n" - " --make-entry-directory=yes|no|auto\n" - " Create $BOOT/ENTRY-TOKEN/ directory\n" - " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n" - " Entry token to use for this installation\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " --all-architectures\n" - " Install all supported EFI architectures\n" - " --efi-boot-option-description=DESCRIPTION\n" - " Description of the entry in the boot option list\n" - " --efi-boot-option-description-with-device=yes\n" - " Suffix description with disk vendor/model/serial\n" - " --dry-run Dry run (unlink and cleanup)\n" - " --secure-boot-auto-enroll=yes|no\n" - " Set up secure boot auto-enrollment\n" - " --private-key=PATH|URI\n" - " Private key to use when setting up secure boot\n" - " auto-enrollment or an engine or provider specific\n" - " designation if --private-key-source= is used\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used when setting\n" - " up secure boot auto-enrollment\n" - " --certificate=PATH|URI\n" - " PEM certificate to use when setting up Secure Boot\n" - " auto-enrollment, or a provider specific designation\n" - " if --certificate-source= is used\n" - " --certificate-source=file|provider:PROVIDER\n" - " Specify how to interpret the certificate from\n" - " --certificate=. Allows the certificate to be loaded\n" - " from an OpenSSL provider\n" - "\nSee the %2$s for details.\n", + static const char *const verb_groups[] = { + "Generic EFI Firmware/Boot Loader Commands", + "Boot Loader Specification Commands", + "Boot Loader Interface Commands", + "systemd-boot Commands", + "Kernel Image Commands", + }; + + static const char *const option_groups[] = { + "Block Device Discovery Commands", + "Options", + }; + + _cleanup_(table_unref_many) Table *verb_tables[ELEMENTSOF(verb_groups) + 1] = {}; + _cleanup_(table_unref_many) Table *option_tables[ELEMENTSOF(option_groups) + 1] = {}; + + for (size_t i = 0; i < ELEMENTSOF(verb_groups); i++) { + r = verbs_get_help_table_group(verb_groups[i], &verb_tables[i]); + if (r < 0) + return r; + } + + for (size_t i = 0; i < ELEMENTSOF(option_groups); i++) { + r = option_parser_get_help_table_group(option_groups[i], &option_tables[i]); + if (r < 0) + return r; + } + + (void) table_sync_column_widths(0, + verb_tables[0], verb_tables[1], verb_tables[2], + verb_tables[3], verb_tables[4], + option_tables[0], option_tables[1]); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sControl EFI firmware boot settings and manage boot loader.%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + for (size_t i = 0; i < ELEMENTSOF(verb_groups); i++) { + printf("\n%s%s:%s\n", ansi_underline(), verb_groups[i], ansi_normal()); + + r = table_print_or_warn(verb_tables[i]); + if (r < 0) + return r; + } + + for (size_t i = 0; i < ELEMENTSOF(option_groups); i++) { + printf("\n%s%s:%s\n", ansi_underline(), option_groups[i], ansi_normal()); + + r = table_print_or_warn(option_tables[i]); + if (r < 0) + return r; + } + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_ESP_PATH = 0x100, - ARG_BOOT_PATH, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_INSTALL_SOURCE, - ARG_VERSION, - ARG_VARIABLES, - ARG_NO_VARIABLES, - ARG_RANDOM_SEED, - ARG_NO_PAGER, - ARG_GRACEFUL, - ARG_MAKE_ENTRY_DIRECTORY, - ARG_ENTRY_TOKEN, - ARG_JSON, - ARG_ARCH_ALL, - ARG_EFI_BOOT_OPTION_DESCRIPTION, - ARG_EFI_BOOT_OPTION_DESCRIPTION_WITH_DEVICE, - ARG_DRY_RUN, - ARG_PRINT_LOADER_PATH, - ARG_PRINT_STUB_PATH, - ARG_SECURE_BOOT_AUTO_ENROLL, - ARG_CERTIFICATE, - ARG_CERTIFICATE_SOURCE, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - }; +VERB_COMMON_HELP(help); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "esp-path", required_argument, NULL, ARG_ESP_PATH }, - { "path", required_argument, NULL, ARG_ESP_PATH }, /* Compatibility alias */ - { "boot-path", required_argument, NULL, ARG_BOOT_PATH }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "install-source", required_argument, NULL, ARG_INSTALL_SOURCE }, - { "print-esp-path", no_argument, NULL, 'p' }, - { "print-path", no_argument, NULL, 'p' }, /* Compatibility alias */ - { "print-boot-path", no_argument, NULL, 'x' }, - { "print-loader-path", no_argument, NULL, ARG_PRINT_LOADER_PATH }, - { "print-stub-path", no_argument, NULL, ARG_PRINT_STUB_PATH }, - { "print-root-device", no_argument, NULL, 'R' }, - { "variables", required_argument, NULL, ARG_VARIABLES }, - { "no-variables", no_argument, NULL, ARG_NO_VARIABLES }, /* Compatibility alias */ - { "random-seed", required_argument, NULL, ARG_RANDOM_SEED }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "graceful", no_argument, NULL, ARG_GRACEFUL }, - { "quiet", no_argument, NULL, 'q' }, - { "make-entry-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY }, - { "make-machine-id-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY }, /* Compatibility alias */ - { "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN }, - { "json", required_argument, NULL, ARG_JSON }, - { "all-architectures", no_argument, NULL, ARG_ARCH_ALL }, - { "efi-boot-option-description", required_argument, NULL, ARG_EFI_BOOT_OPTION_DESCRIPTION }, - { "efi-boot-option-description-with-device", required_argument, NULL, ARG_EFI_BOOT_OPTION_DESCRIPTION_WITH_DEVICE }, - { "dry-run", no_argument, NULL, ARG_DRY_RUN }, - { "secure-boot-auto-enroll", required_argument, NULL, ARG_SECURE_BOOT_AUTO_ENROLL }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - {} - }; +VERB_GROUP("Generic EFI Firmware/Boot Loader Commands"); + +VERB_SCOPE(, verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Show status of installed boot loader and EFI variables"); + +VERB_SCOPE(, verb_reboot_to_firmware, "reboot-to-firmware", "[BOOL]", VERB_ANY, 2, 0, + "Query or set reboot-to-firmware EFI flag"); + +VERB_GROUP("Boot Loader Specification Commands"); + +VERB_SCOPE_NOARG(, verb_list, "list", + "List boot loader entries"); + +VERB_SCOPE(, verb_unlink, "unlink", "ID", 2, 2, 0, + "Remove boot loader entry"); + +VERB_SCOPE_NOARG(, verb_cleanup, "cleanup", + "Remove files in ESP not referenced in any boot entry"); + +VERB_GROUP("Boot Loader Interface Commands"); + +VERB_SCOPE(, verb_set_efivar, "set-default", "ID", 2, 2, 0, + "Set default boot loader entry"); + +VERB_SCOPE(, verb_set_efivar, "set-oneshot", "ID", 2, 2, 0, + "Set default boot loader entry, for next boot only"); + +VERB_SCOPE(, verb_set_efivar, "set-sysfail", "ID", 2, 2, 0, + "Set boot loader entry used in case of a system failure"); + +VERB_SCOPE(, verb_set_efivar, "set-timeout", "SECONDS", 2, 2, 0, + "Set the menu timeout"); + +VERB_SCOPE(, verb_set_efivar, "set-timeout-oneshot", "SECONDS", 2, 2, 0, + "Set the menu timeout for the next boot only"); + +VERB_SCOPE(, verb_set_efivar, "set-preferred", "ID", 2, 2, 0, + /* help= */ NULL); + +VERB_GROUP("systemd-boot Commands"); + +VERB_SCOPE(, verb_install, "install", NULL, VERB_ANY, 1, 0, + "Install systemd-boot to the ESP and EFI variables"); + +VERB_SCOPE(, verb_install, "update", NULL, VERB_ANY, 1, 0, + "Update systemd-boot in the ESP and EFI variables"); + +VERB_SCOPE_NOARG(, verb_remove, "remove", + "Remove systemd-boot from the ESP and EFI variables"); + +VERB_SCOPE_NOARG(, verb_is_installed, "is-installed", + "Test whether systemd-boot is installed in the ESP"); + +VERB_SCOPE_NOARG(, verb_random_seed, "random-seed", + "Initialize or refresh random seed in ESP and EFI variables"); - int c, r; +VERB_GROUP("Kernel Image Commands"); + +VERB_SCOPE(, verb_kernel_identify, "kernel-identify", "KERNEL-IMAGE", 2, 2, 0, + "Identify kernel image type"); + +VERB_SCOPE(, verb_kernel_inspect, "kernel-inspect", "KERNEL-IMAGE", 2, 2, 0, + "Prints details about the kernel image"); + +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hpxRq", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - help(0, NULL, NULL); - return 0; + OPTION_GROUP("Block Device Discovery Commands"): {} + + OPTION('p', "print-esp-path", NULL, "Print path to the EFI System Partition mount point"): {} + OPTION_LONG("print-path", NULL, /* help= */ NULL): /* Compatibility alias */ + arg_print_esp_path = true; + break; + + OPTION('x', "print-boot-path", NULL, "Print path to the $BOOT partition mount point"): + arg_print_dollar_boot_path = true; + break; + + OPTION_LONG("print-loader-path", NULL, "Print path to currently booted boot loader binary"): + arg_print_loader_path = true; + break; + + OPTION_LONG("print-stub-path", NULL, "Print path to currently booted unified kernel binary"): + arg_print_stub_path = true; + break; + + OPTION('R', "print-root-device", NULL, + "Print path to the block device node backing the root file system" + " (returns e.g. /dev/nvme0n1p5)"): {} + OPTION_HELP_VERBATIM("-RR", + "Print path to the whole disk block device node backing the root FS" + " (returns e.g. /dev/nvme0n1)"): + arg_print_root_device++; + break; - case ARG_VERSION: + OPTION_LONG("print-efi-architecture", NULL, "Print the local EFI architecture string"): + arg_print_efi_architecture = true; + break; + + OPTION_GROUP("Options"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: return version(); - case ARG_ESP_PATH: - r = free_and_strdup(&arg_esp_path, optarg); + OPTION_LONG("esp-path", "PATH", "Path to the EFI System Partition (ESP)"): {} + OPTION_LONG("path", "PATH", /* help= */ NULL): /* Compatibility alias */ + r = free_and_strdup(&arg_esp_path, arg); if (r < 0) return log_oom(); break; - case ARG_BOOT_PATH: - r = free_and_strdup(&arg_xbootldr_path, optarg); + OPTION_LONG("boot-path", "PATH", "Path to the $BOOT partition"): + r = free_and_strdup(&arg_xbootldr_path, arg); if (r < 0) return log_oom(); break; - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_INSTALL_SOURCE: { - InstallSource is = install_source_from_string(optarg); + OPTION_LONG("install-source", "SOURCE", + "Where to pick files when using --root=/--image= (auto, image, host)"): { + InstallSource is = install_source_from_string(arg); if (is < 0) - return log_error_errno(is, "Unexpected parameter for --install-source=: %s", optarg); + return log_error_errno(is, "Unexpected parameter for --install-source=: %s", arg); arg_install_source = is; break; } - case 'p': - arg_print_esp_path = true; - break; - - case 'x': - arg_print_dollar_boot_path = true; - break; - - case ARG_PRINT_LOADER_PATH: - arg_print_loader_path = true; - break; - - case ARG_PRINT_STUB_PATH: - arg_print_stub_path = true; - break; - - case 'R': - arg_print_root_device++; - break; - - case ARG_VARIABLES: - r = parse_tristate_argument_with_auto("--variables=", optarg, &arg_touch_variables); + OPTION_LONG("variables", "BOOL", "Whether to modify EFI variables"): + r = parse_tristate_argument_with_auto("--variables=", arg, &arg_touch_variables); if (r < 0) return r; #if !ENABLE_EFI if (arg_touch_variables > 0) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Compiled without support for EFI, --variables=%s cannot be specified.", optarg); + "Compiled without support for EFI, --variables=%s cannot be specified.", arg); #endif break; - case ARG_NO_VARIABLES: + OPTION_LONG("no-variables", NULL, /* help= */ NULL): /* Compatibility alias */ arg_touch_variables = false; break; - case ARG_RANDOM_SEED: - r = parse_boolean_argument("--random-seed=", optarg, &arg_install_random_seed); + OPTION_LONG("random-seed", "BOOL", "Whether to create random-seed file during install"): + r = parse_boolean_argument("--random-seed=", arg, &arg_install_random_seed); if (r < 0) return r; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_GRACEFUL: + OPTION_LONG("graceful", NULL, + "Don't fail when the ESP cannot be found or EFI variables cannot be written"): _arg_graceful = ARG_GRACEFUL_YES; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress output"): arg_quiet = true; break; - case ARG_ENTRY_TOKEN: - r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token); + OPTION_LONG("entry-token", "TOKEN", + "Entry token to use for this installation" + " (machine-id, os-id, os-image-id, auto, literal:…)"): + r = parse_boot_entry_token_type(arg, &arg_entry_token_type, &arg_entry_token); if (r < 0) return r; break; - case ARG_MAKE_ENTRY_DIRECTORY: - if (streq(optarg, "auto")) /* retained for backwards compatibility */ + OPTION_LONG("make-entry-directory", "yes|no|auto", "Create $BOOT/ENTRY-TOKEN/ directory"): {} + OPTION_LONG("make-machine-id-directory", "BOOL", /* help= */ NULL): /* Compatibility alias */ + if (streq(arg, "auto")) /* retained for backwards compatibility */ arg_make_entry_directory = -1; /* yes if machine-id is permanent */ else { - r = parse_boolean_argument("--make-entry-directory=", optarg, NULL); + r = parse_boolean_argument("--make-entry-directory=", arg, NULL); if (r < 0) return r; @@ -548,95 +558,91 @@ static int parse_argv(int argc, char *argv[]) { } break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; break; - case ARG_ARCH_ALL: + OPTION_LONG("all-architectures", NULL, "Install all supported EFI architectures"): arg_arch_all = true; break; - case ARG_EFI_BOOT_OPTION_DESCRIPTION: - if (isempty(optarg) || !(string_is_safe(optarg) && utf8_is_valid(optarg))) { - _cleanup_free_ char *escaped = cescape(optarg); + OPTION_LONG("efi-boot-option-description", "DESCRIPTION", + "Description of the entry in the boot option list"): + if (!string_is_safe(arg, STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS)) { + _cleanup_free_ char *escaped = cescape(arg); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid --efi-boot-option-description=: %s", strna(escaped)); } - if (strlen(optarg) > EFI_BOOT_OPTION_DESCRIPTION_MAX) + if (strlen(arg) > EFI_BOOT_OPTION_DESCRIPTION_MAX) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--efi-boot-option-description= too long: %zu > %zu", - strlen(optarg), EFI_BOOT_OPTION_DESCRIPTION_MAX); - r = free_and_strdup_warn(&arg_efi_boot_option_description, optarg); + strlen(arg), EFI_BOOT_OPTION_DESCRIPTION_MAX); + r = free_and_strdup_warn(&arg_efi_boot_option_description, arg); if (r < 0) return r; break; - case ARG_EFI_BOOT_OPTION_DESCRIPTION_WITH_DEVICE: - r = parse_boolean_argument("--efi-boot-option-description-with-device=", optarg, &arg_efi_boot_option_description_with_device); + OPTION_LONG("efi-boot-option-description-with-device", "BOOL", + "Suffix description with disk vendor/model/serial"): + r = parse_boolean_argument("--efi-boot-option-description-with-device=", arg, + &arg_efi_boot_option_description_with_device); if (r < 0) return r; - break; - case ARG_DRY_RUN: + OPTION_LONG("dry-run", NULL, "Dry run (unlink and cleanup)"): arg_dry_run = true; break; - case ARG_SECURE_BOOT_AUTO_ENROLL: - r = parse_boolean_argument("--secure-boot-auto-enroll=", optarg, &arg_secure_boot_auto_enroll); + OPTION_LONG("secure-boot-auto-enroll", "BOOL", "Set up secure boot auto-enrollment"): + r = parse_boolean_argument("--secure-boot-auto-enroll=", arg, + &arg_secure_boot_auto_enroll); if (r < 0) return r; break; - case ARG_CERTIFICATE: - r = free_and_strdup_warn(&arg_certificate, optarg); + OPTION_COMMON_PRIVATE_KEY("Private key for Secure Boot auto-enrollment"): + r = free_and_strdup_warn(&arg_private_key, arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: - r = parse_openssl_certificate_source_argument( - optarg, - &arg_certificate_source, - &arg_certificate_source_type); + OPTION_COMMON_PRIVATE_KEY_SOURCE: + r = parse_openssl_key_source_argument(arg, + &arg_private_key_source, + &arg_private_key_source_type); if (r < 0) return r; break; - case ARG_PRIVATE_KEY: { - r = free_and_strdup_warn(&arg_private_key, optarg); + OPTION_COMMON_CERTIFICATE("PEM certificate to use when setting up Secure Boot auto-enrollment"): + r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - } - case ARG_PRIVATE_KEY_SOURCE: - r = parse_openssl_key_source_argument( - optarg, - &arg_private_key_source, - &arg_private_key_source_type); + OPTION_COMMON_CERTIFICATE_SOURCE: + r = parse_openssl_certificate_source_argument(arg, + &arg_certificate_source, + &arg_certificate_source_type); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (!!arg_print_esp_path + !!arg_print_dollar_boot_path + (arg_print_root_device > 0) + arg_print_loader_path + arg_print_stub_path > 1) + char **args = option_parser_get_args(&state); + + if (!!arg_print_esp_path + !!arg_print_dollar_boot_path + (arg_print_root_device > 0) + arg_print_loader_path + arg_print_stub_path + arg_print_efi_architecture > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--print-esp-path/-p, --print-boot-path/-x, --print-root-device=/-R, --print-loader-path, --print-stub-path cannot be combined."); + "--print-esp-path/-p, --print-boot-path/-x, --print-root-device=/-R, --print-loader-path, --print-stub-path, --print-efi-architecture cannot be combined."); - if ((arg_root || arg_image) && argv[optind] && !STR_IN_SET(argv[optind], "status", "list", + if ((arg_root || arg_image) && args[0] && !STR_IN_SET(args[0], "status", "list", "install", "update", "remove", "is-installed", "random-seed", "unlink", "cleanup")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Options --root= and --image= are not supported with verb %s.", - argv[optind]); + args[0]); if (arg_root && arg_image) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); @@ -644,7 +650,7 @@ static int parse_argv(int argc, char *argv[]) { if (arg_install_source != INSTALL_SOURCE_AUTO && !arg_root && !arg_image) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--install-from-host is only supported with --root= or --image=."); - if (arg_dry_run && argv[optind] && !STR_IN_SET(argv[optind], "unlink", "cleanup")) + if (arg_dry_run && args[0] && !STR_IN_SET(args[0], "unlink", "cleanup")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--dry-run is only supported with --unlink or --cleanup"); if (arg_secure_boot_auto_enroll) { @@ -667,36 +673,10 @@ static int parse_argv(int argc, char *argv[]) { arg_pager_flags |= PAGER_DISABLE; } + *ret_args = args; return 1; } -static int bootctl_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "install", VERB_ANY, 1, 0, verb_install }, - { "update", VERB_ANY, 1, 0, verb_install }, - { "remove", VERB_ANY, 1, 0, verb_remove }, - { "is-installed", VERB_ANY, 1, 0, verb_is_installed }, - { "kernel-identify", 2, 2, 0, verb_kernel_identify }, - { "kernel-inspect", 2, 2, 0, verb_kernel_inspect }, - { "list", VERB_ANY, 1, 0, verb_list }, - { "unlink", 2, 2, 0, verb_unlink }, - { "cleanup", VERB_ANY, 1, 0, verb_cleanup }, - { "set-default", 2, 2, 0, verb_set_efivar }, - { "set-preferred", 2, 2, 0, verb_set_efivar }, - { "set-oneshot", 2, 2, 0, verb_set_efivar }, - { "set-timeout", 2, 2, 0, verb_set_efivar }, - { "set-timeout-oneshot", 2, 2, 0, verb_set_efivar }, - { "set-sysfail", 2, 2, 0, verb_set_efivar }, - { "random-seed", VERB_ANY, 1, 0, verb_random_seed }, - { "reboot-to-firmware", VERB_ANY, 2, 0, verb_reboot_to_firmware }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int vl_server(void) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; int r; @@ -737,7 +717,8 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -775,6 +756,11 @@ static int run(int argc, char *argv[]) { if (arg_print_loader_path || arg_print_stub_path) return print_loader_or_stub_path(); + if (arg_print_efi_architecture) { + printf("%s\n", get_efi_arch()); + return 0; + } + /* Open up and mount the image */ if (arg_image) { assert(!arg_root); @@ -797,7 +783,7 @@ static int run(int argc, char *argv[]) { return log_oom(); } - return bootctl_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/bootctl/bootctl.h b/src/bootctl/bootctl.h index d979411c78226..d0daab9dd12b3 100644 --- a/src/bootctl/bootctl.h +++ b/src/bootctl/bootctl.h @@ -21,7 +21,10 @@ extern char *arg_esp_path; extern char *arg_xbootldr_path; extern bool arg_print_esp_path; extern bool arg_print_dollar_boot_path; +extern bool arg_print_loader_path; +extern bool arg_print_stub_path; extern unsigned arg_print_root_device; +extern bool arg_print_efi_architecture; extern int arg_touch_variables; extern bool arg_install_random_seed; extern PagerFlags arg_pager_flags; @@ -56,8 +59,8 @@ static inline const char* arg_dollar_boot_path(void) { GracefulMode arg_graceful(void); -int acquire_esp(int unprivileged_mode, bool graceful, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); -int acquire_xbootldr(int unprivileged_mode, sd_id128_t *ret_uuid, dev_t *ret_devid); +int acquire_esp(int unprivileged_mode, bool graceful, int *ret_fd, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); +int acquire_xbootldr(int unprivileged_mode, int *ret_fd, sd_id128_t *ret_uuid, dev_t *ret_devid); /* EFI_BOOT_OPTION_DESCRIPTION_MAX sets the maximum length for the boot option description * stored in NVRAM. The UEFI spec does not specify a minimum or maximum length for this diff --git a/src/bootctl/bootspec-util.c b/src/bootctl/bootspec-util.c index ec3339600bb10..b96687430ca32 100644 --- a/src/bootctl/bootspec-util.c +++ b/src/bootctl/bootspec-util.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "bootctl.h" #include "bootspec-util.h" #include "devnum-util.h" #include "efi-loader.h" @@ -10,6 +9,7 @@ int boot_config_load_and_select( BootConfig *config, + const char *root, const char *esp_path, dev_t esp_devid, const char *xbootldr_path, @@ -25,7 +25,7 @@ int boot_config_load_and_select( if (r < 0) return r; - if (!arg_root) { + if (!root) { _cleanup_strv_free_ char **efi_entries = NULL; r = efi_loader_get_entries(&efi_entries); @@ -37,5 +37,5 @@ int boot_config_load_and_select( (void) boot_config_augment_from_loader(config, efi_entries, /* auto_only= */ false); } - return boot_config_select_special_entries(config, /* skip_efivars= */ !!arg_root); + return boot_config_select_special_entries(config, /* skip_efivars= */ !!root); } diff --git a/src/bootctl/bootspec-util.h b/src/bootctl/bootspec-util.h index a00e002caafdc..51dac12b9f44b 100644 --- a/src/bootctl/bootspec-util.h +++ b/src/bootctl/bootspec-util.h @@ -3,4 +3,4 @@ #include "bootspec.h" -int boot_config_load_and_select(BootConfig *config, const char *esp_path, dev_t esp_devid, const char *xbootldr_path, dev_t xbootldr_devid); +int boot_config_load_and_select(BootConfig *config, const char *root, const char *esp_path, dev_t esp_devid, const char *xbootldr_path, dev_t xbootldr_devid); diff --git a/src/bootctl/meson.build b/src/bootctl/meson.build index 8cfbb7c14acb0..f8349df7168e3 100644 --- a/src/bootctl/meson.build +++ b/src/bootctl/meson.build @@ -23,6 +23,6 @@ executables += [ ], 'sources' : bootctl_sources, 'link_with' : boot_link_with, - 'dependencies' : [libopenssl], + 'dependencies' : [libopenssl_cflags], }, ] diff --git a/src/bpf/.clang-tidy b/src/bpf/.clang-tidy new file mode 100644 index 0000000000000..43b0c59f49477 --- /dev/null +++ b/src/bpf/.clang-tidy @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +--- +InheritParentConfig: true +Checks: '-misc-use-internal-linkage' +CheckOptions: + misc-include-cleaner.IgnoreHeaders: 'errno\.h' +... diff --git a/src/bpf/.clangd b/src/bpf/.clangd new file mode 100644 index 0000000000000..ec2f81817b928 --- /dev/null +++ b/src/bpf/.clangd @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +Diagnostics: + Includes: + IgnoreHeader: [errno\.h] diff --git a/src/core/bpf/bind-iface/bind-iface.bpf.c b/src/bpf/bind-iface.bpf.c similarity index 100% rename from src/core/bpf/bind-iface/bind-iface.bpf.c rename to src/bpf/bind-iface.bpf.c diff --git a/src/nsresourced/bpf/userns-restrict/userns-restrict-skel.h b/src/bpf/bpf-skel-wrapper.h.in similarity index 78% rename from src/nsresourced/bpf/userns-restrict/userns-restrict-skel.h rename to src/bpf/bpf-skel-wrapper.h.in index 7ed12dea9577c..3dc3cede3e2e1 100644 --- a/src/nsresourced/bpf/userns-restrict/userns-restrict-skel.h +++ b/src/bpf/bpf-skel-wrapper.h.in @@ -7,12 +7,13 @@ * fine given that LGPL-2.1-or-later downgrades to GPL if needed. */ -#include "bpf-dlopen.h" /* IWYU pragma: keep */ +#include "bpf-dlopen.h" /* IWYU pragma: keep */ /* libbpf is used via dlopen(), so rename symbols */ #define bpf_object__attach_skeleton sym_bpf_object__attach_skeleton #define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton +#define bpf_object__detach_skeleton sym_bpf_object__detach_skeleton #define bpf_object__load_skeleton sym_bpf_object__load_skeleton #define bpf_object__open_skeleton sym_bpf_object__open_skeleton -#include "bpf/userns-restrict/userns-restrict.skel.h" /* IWYU pragma: export */ +#include "@NAME@.bpf.skel.h" /* IWYU pragma: export */ diff --git a/src/bpf/merge-bpf-compdb.py b/src/bpf/merge-bpf-compdb.py new file mode 100755 index 0000000000000..c93e685afd942 --- /dev/null +++ b/src/bpf/merge-bpf-compdb.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-2.1-or-later + +import json +import os +import sys + + +def main() -> int: + build_root = os.environ['MESON_BUILD_ROOT'] + + sep = sys.argv.index('--') + sources = sys.argv[1:sep] + command = sys.argv[sep + 1:] + + db_path = os.path.join(build_root, 'compile_commands.json') + try: + with open(db_path) as f: + db = json.load(f) + except FileNotFoundError: + db = [] + + sources_set = set(sources) + db = [entry for entry in db if entry['file'] not in sources_set] + + for source in sources: + db.append({ + 'directory': build_root, + 'file': source, + 'arguments': [source if a == '@INPUT@' else a for a in command], + }) + + with open(db_path, 'w') as f: + json.dump(db, f, indent=2) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/bpf/meson.build b/src/bpf/meson.build new file mode 100644 index 0000000000000..32ab32e93dcdb --- /dev/null +++ b/src/bpf/meson.build @@ -0,0 +1,397 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +if not libbpf.found() + conf.set10('BPF_FRAMEWORK', false) +else + clang_found = false + clang_supports_bpf = false + bpf_gcc_found = false + bpftool_strip = false + deps_found = false + + if bpf_compiler == 'clang' + # Support 'versioned' clang/llvm-strip binaries, as seen on Debian/Ubuntu + # (like clang-10/llvm-strip-10) + if meson.is_cross_build() or cc.get_id() != 'clang' or cc.cmd_array()[0].contains('afl-clang') or cc.cmd_array()[0].contains('hfuzz-clang') + r = find_program('clang', + required : bpf_framework, + version : '>= 10.0.0') + clang_found = r.found() + if clang_found + clang = r.full_path() + endif + else + clang_found = true + clang = cc.cmd_array() + endif + + if clang_found + # Check if 'clang -target bpf' is supported. + clang_supports_bpf = run_command(clang, '-target', 'bpf', '--print-supported-cpus', check : false).returncode() == 0 + endif + if bpf_framework.enabled() and not clang_supports_bpf + error('bpf-framework was enabled but clang does not support bpf') + endif + elif bpf_compiler == 'gcc' + bpf_gcc = find_program('bpf-gcc', + 'bpf-none-gcc', + 'bpf-unknown-none-gcc', + required : true, + version : '>= 13.1.0') + bpf_gcc_found = bpf_gcc.found() + endif + + if clang_supports_bpf or bpf_gcc_found + # Debian installs this in /usr/sbin/ which is not in $PATH. + # We check for 'bpftool' first, honouring $PATH, and in /usr/sbin/ for Debian. + # We use 'bpftool gen object' subcommand for bpftool strip, it was added by d80b2fcbe0a023619e0fc73112f2a02c2662f6ab (v5.13). + bpftool = find_program('bpftool', + '/usr/sbin/bpftool', + required : bpf_framework.enabled() and bpf_compiler == 'gcc', + version : bpf_compiler == 'gcc' ? '>= 7.0.0' : '>= 5.13.0') + + if bpftool.found() + bpftool_strip = true + deps_found = true + elif bpf_compiler == 'clang' + # We require the 'bpftool gen skeleton' subcommand, it was added by 985ead416df39d6fe8e89580cc1db6aa273e0175 (v5.6). + bpftool = find_program('bpftool', + '/usr/sbin/bpftool', + required : bpf_framework, + version : '>= 5.6.0') + endif + + # We use `llvm-strip` as a fallback if `bpftool gen object` strip support is not available. + if not bpftool_strip and bpftool.found() and clang_supports_bpf + if not meson.is_cross_build() + llvm_strip_bin = run_command(clang, '--print-prog-name', 'llvm-strip', + check : true).stdout().strip() + else + llvm_strip_bin = 'llvm-strip' + endif + llvm_strip = find_program(llvm_strip_bin, + required : bpf_framework, + version : '>= 10.0.0') + deps_found = llvm_strip.found() + endif + endif + + # Can build BPF program from source code in restricted C + conf.set10('BPF_FRAMEWORK', deps_found) +endif + +if conf.get('BPF_FRAMEWORK') == 1 + bpf_clang_flags = [ + '-std=gnu17', + '-Wno-compare-distinct-pointer-types', + '-Wno-microsoft-anon-tag', + '-fms-extensions', + '-fno-stack-protector', + '-O2', + '-target', + 'bpf', + '-g', + '-c', + ] + + bpf_gcc_flags = [ + '-std=gnu17', + '-fms-extensions', + '-fno-stack-protector', + '-fno-ssa-phiopt', + '-O2', + '-mcpu=v3', + '-mco-re', + '-gbtf', + '-c', + ] + + # If c_args contains these flags copy them along with the values, in order to avoid breaking + # reproducible builds and other functionality + propagate_cflags = [ + '-ffile-prefix-map=', + '-fdebug-prefix-map=', + '-fmacro-prefix-map=', + '--sysroot=', + ] + + foreach opt : c_args + foreach flag : propagate_cflags + if opt.startswith(flag) + bpf_clang_flags += [opt] + bpf_gcc_flags += [opt] + break + endif + endforeach + endforeach + + # Generate defines that are appropriate to tell the compiler what architecture + # we're compiling for. By default we just map meson's cpu_family to ____. + # This dictionary contains the exceptions where this doesn't work. + # + # C.f. https://mesonbuild.com/Reference-tables.html#cpu-families + # and src/basic/missing_syscall_def.h. + + # Start with older ABI. When define is missing, we're likely targeting that. + ppc64_elf_version = '1' + + if host_machine.cpu_family() == 'ppc64' + # cc doesn't have to be bpf_compiler, but they should be targeting the same ABI + call_elf_value = cc.get_define('_CALL_ELF') + if call_elf_value != '' + ppc64_elf_version = call_elf_value + endif + endif + + cpu_arch_defines = { + 'ppc' : ['-D__powerpc__', '-D__TARGET_ARCH_powerpc'], + 'ppc64' : ['-D__powerpc64__', '-D__TARGET_ARCH_powerpc', '-D_CALL_ELF=' + ppc64_elf_version], + 'riscv32' : ['-D__riscv', '-D__riscv_xlen=32', '-D__TARGET_ARCH_riscv'], + 'riscv64' : ['-D__riscv', '-D__riscv_xlen=64', '-D__TARGET_ARCH_riscv'], + 'x86' : ['-D__i386__', '-D__TARGET_ARCH_x86'], + 's390x' : ['-D__s390__', '-D__s390x__', '-D__TARGET_ARCH_s390'], + + # For arm, assume hardware fp is available. + 'arm' : ['-D__arm__', '-D__ARM_PCS_VFP', '-D__TARGET_ARCH_arm'], + 'loongarch64' : ['-D__loongarch__', '-D__loongarch_grlen=64', '-D__TARGET_ARCH_loongarch'] + } + + bpf_arch_flags = cpu_arch_defines.get(host_machine.cpu_family(), + ['-D__@0@__'.format(host_machine.cpu_family())]) + if bpf_compiler == 'gcc' + bpf_arch_flags += ['-m' + host_machine.endian() + '-endian'] + endif + + libbpf_include_dir = libbpf.get_variable(pkgconfig : 'includedir') + + bpf_o_unstripped_cmd = [] + if bpf_compiler == 'clang' + bpf_o_unstripped_cmd += [ + clang, + bpf_clang_flags, + bpf_arch_flags, + ] + elif bpf_compiler == 'gcc' + bpf_o_unstripped_cmd += [ + bpf_gcc, + bpf_gcc_flags, + bpf_arch_flags, + ] + endif + + bpf_o_unstripped_cmd += ['-I.', '-include', 'config.h'] + + if cc.get_id() == 'gcc' or meson.is_cross_build() + if cc.get_id() != 'gcc' + warning('Cross compiler is not gcc. Guessing the target triplet for bpf likely fails.') + endif + target_triplet_cmd = run_command(cc.cmd_array(), '-print-multiarch', check: false) + else + # clang does not support -print-multiarch (D133170) and its -dump-machine + # does not match multiarch. Query gcc instead. + target_triplet_cmd = run_command('gcc', '-print-multiarch', check: false) + endif + if target_triplet_cmd.returncode() == 0 + sysroot = meson.get_external_property('sys_root', '/') + target_triplet = target_triplet_cmd.stdout().strip() + target_include_dir = sysroot / 'usr' / 'include' + target_triple_include_dir = target_include_dir / target_triplet + isystem_dir = '' + if fs.is_dir(target_triple_include_dir) + isystem_dir = target_triple_include_dir + elif fs.is_dir(target_include_dir) + isystem_dir = target_include_dir + endif + if isystem_dir != '' + bpf_o_unstripped_cmd += [ + '-isystem', isystem_dir + ] + endif + endif + + bpf_o_unstripped_cmd += [ + '-idirafter', + libbpf_include_dir, + '@INPUT@', + '-o', + '@OUTPUT@' + ] + + if bpftool_strip + bpf_o_cmd = [ + bpftool, + 'gen', + 'object', + '@OUTPUT@', + '@INPUT@' + ] + elif bpf_compiler == 'clang' + bpf_o_cmd = [ + llvm_strip, + '-g', + '@INPUT@', + '-o', + '@OUTPUT@' + ] + endif + + skel_h_cmd = [ + bpftool, + 'gen', + 'skeleton', + '@INPUT@' + ] +endif + +use_provided_vmlinux_h = false +use_generated_vmlinux_h = false +provided_vmlinux_h_path = get_option('vmlinux-h-path') + +# For the more complex BPF programs we really want a vmlinux.h (which is arch +# specific, but only somewhat bound to kernel version). Ideally the kernel +# development headers would ship that, but right now they don't. Hence address +# this in two ways: +# +# 1. Provide a vmlinux.h at build time +# 2. Generate the file on the fly where possible (which requires /sys/ to be mounted) +# +# We generally prefer the former (to support reproducible builds), but will +# fallback to the latter. + +if conf.get('BPF_FRAMEWORK') == 1 + enable_vmlinux_h = get_option('vmlinux-h') + + if enable_vmlinux_h == 'auto' + if provided_vmlinux_h_path != '' + use_provided_vmlinux_h = true + elif fs.exists('/sys/kernel/btf/vmlinux') and \ + bpftool.found() and \ + (host_machine.cpu_family() == build_machine.cpu_family()) and \ + host_machine.cpu_family() in ['x86_64', 'aarch64'] + + # We will only generate a vmlinux.h from the running + # kernel if the host and build machine are of the same + # family. Also for now we focus on x86_64 and aarch64, + # since other archs don't seem to be ready yet. + + use_generated_vmlinux_h = true + endif + elif enable_vmlinux_h == 'provided' + use_provided_vmlinux_h = true + elif enable_vmlinux_h == 'generated' + if not fs.exists('/sys/kernel/btf/vmlinux') + error('BTF data from kernel not available (/sys/kernel/btf/vmlinux missing), cannot generate vmlinux.h, but was asked to.') + endif + if not bpftool.found() + error('bpftool not available, cannot generate vmlinux.h, but was asked to.') + endif + use_generated_vmlinux_h = true + endif +endif + +vmlinux_h_dependency = [] +if use_provided_vmlinux_h + if not fs.exists(provided_vmlinux_h_path) + error('Path to provided vmlinux.h does not exist.') + endif + bpf_o_unstripped_cmd += ['-I' + fs.parent(provided_vmlinux_h_path)] + message(f'Using provided @provided_vmlinux_h_path@') +elif use_generated_vmlinux_h + vmlinux_h_dependency = custom_target( + output: 'vmlinux.h', + command : [ bpftool, 'btf', 'dump', 'file', '/sys/kernel/btf/vmlinux', 'format', 'c' ], + capture : true) + + bpf_o_unstripped_cmd += ['-I' + fs.parent(vmlinux_h_dependency.full_path())] + generated_sources += vmlinux_h_dependency + message('Using generated @0@'.format(vmlinux_h_dependency.full_path())) +else + message('Using neither provided nor generated vmlinux.h, some features will not be available.') +endif + +conf.set10('HAVE_VMLINUX_H', use_provided_vmlinux_h or use_generated_vmlinux_h) + +conf.set10('ENABLE_SYSCTL_BPF', conf.get('HAVE_VMLINUX_H') == 1 and libbpf.version().version_compare('>= 0.7')) + +bpf_programs = [ + { + 'source' : files('bind-iface.bpf.c'), + 'condition' : 'BPF_FRAMEWORK', + }, + { + 'source' : files('restrict-fs.bpf.c'), + 'condition' : 'BPF_FRAMEWORK', + }, + { + 'source' : files('restrict-ifaces.bpf.c'), + 'condition' : 'BPF_FRAMEWORK', + }, + { + 'source' : files('socket-bind.bpf.c'), + 'condition' : 'BPF_FRAMEWORK', + }, + { + 'source' : files('sysctl-monitor.bpf.c'), + 'condition' : 'ENABLE_SYSCTL_BPF', + 'depends' : vmlinux_h_dependency, + }, + { + 'source' : files('userns-restrict.bpf.c'), + 'condition' : 'HAVE_VMLINUX_H', + 'depends' : vmlinux_h_dependency, + }, +] + +bpf_programs_by_name = {} +bpf_sources = [] + +foreach program : bpf_programs + if conf.get(program['condition']) != 1 + continue + endif + + source = program['source'][0] + # Strip .bpf.c extension + name = fs.stem(fs.stem(source)) + + bpf_o_unstripped = custom_target( + input : source, + output : name + '.bpf.unstripped.o', + command : bpf_o_unstripped_cmd, + depends : program.get('depends', [])) + + bpf_o = custom_target( + input : bpf_o_unstripped, + output : name + '.bpf.o', + command : bpf_o_cmd) + + skel_h = custom_target( + input : bpf_o, + output : name + '.bpf.skel.h', + command : skel_h_cmd, + capture : true) + + # The wrapper is written at meson setup time and found via the + # include path, so we don't need to list it as a build-time source. + # Keeping it out of bpf_programs_by_name also keeps it out of the + # clang-tidy per-source test loop, which would otherwise fall back + # to a BPF compile_commands.json entry (no -Isrc/shared) and fail + # to resolve bpf-dlopen.h. + configure_file( + input : 'bpf-skel-wrapper.h.in', + output : name + '-skel.h', + configuration : { 'NAME' : name }) + + bpf_programs_by_name += { name : skel_h } + generated_sources += skel_h + bpf_sources += source +endforeach + +if bpf_sources.length() > 0 + meson.add_postconf_script( + python, + files('merge-bpf-compdb.py'), + bpf_sources, + '--', + bpf_o_unstripped_cmd) +endif diff --git a/src/core/bpf/restrict-fs/restrict-fs.bpf.c b/src/bpf/restrict-fs.bpf.c similarity index 100% rename from src/core/bpf/restrict-fs/restrict-fs.bpf.c rename to src/bpf/restrict-fs.bpf.c diff --git a/src/core/bpf/restrict-ifaces/restrict-ifaces.bpf.c b/src/bpf/restrict-ifaces.bpf.c similarity index 100% rename from src/core/bpf/restrict-ifaces/restrict-ifaces.bpf.c rename to src/bpf/restrict-ifaces.bpf.c diff --git a/src/core/bpf/socket-bind/socket-bind-api.bpf.h b/src/bpf/socket-bind-api.bpf.h similarity index 100% rename from src/core/bpf/socket-bind/socket-bind-api.bpf.h rename to src/bpf/socket-bind-api.bpf.h diff --git a/src/core/bpf/socket-bind/socket-bind.bpf.c b/src/bpf/socket-bind.bpf.c similarity index 100% rename from src/core/bpf/socket-bind/socket-bind.bpf.c rename to src/bpf/socket-bind.bpf.c diff --git a/src/network/bpf/sysctl-monitor/sysctl-monitor.bpf.c b/src/bpf/sysctl-monitor.bpf.c similarity index 100% rename from src/network/bpf/sysctl-monitor/sysctl-monitor.bpf.c rename to src/bpf/sysctl-monitor.bpf.c diff --git a/src/network/bpf/sysctl-monitor/sysctl-write-event.h b/src/bpf/sysctl-write-event.h similarity index 94% rename from src/network/bpf/sysctl-monitor/sysctl-write-event.h rename to src/bpf/sysctl-write-event.h index 77b71fb4f9c27..c0603638d99d0 100644 --- a/src/network/bpf/sysctl-monitor/sysctl-write-event.h +++ b/src/bpf/sysctl-write-event.h @@ -2,6 +2,13 @@ #pragma once +#ifdef __bpf__ +#include "vmlinux.h" +#else +#include +#include +#endif + #ifndef TASK_COMM_LEN #define TASK_COMM_LEN 16 #endif diff --git a/src/nsresourced/bpf/userns-restrict/userns-restrict.bpf.c b/src/bpf/userns-restrict.bpf.c similarity index 96% rename from src/nsresourced/bpf/userns-restrict/userns-restrict.bpf.c rename to src/bpf/userns-restrict.bpf.c index 25d609bf38fc8..54473d7210ea6 100644 --- a/src/nsresourced/bpf/userns-restrict/userns-restrict.bpf.c +++ b/src/bpf/userns-restrict.bpf.c @@ -15,7 +15,7 @@ #include "vmlinux.h" -#include +#include /* IWYU pragma: keep */ #include #include #include @@ -119,6 +119,7 @@ static int userns_owns_mount(struct user_namespace *userns, struct vfsmount *v) static int validate_mount(struct vfsmount *v, int ret) { struct user_namespace *task_userns; unsigned task_userns_inode; + const struct cred *cred; struct task_struct *task; void *mnt_id_map; struct mount *m; @@ -129,7 +130,10 @@ static int validate_mount(struct vfsmount *v, int ret) { /* Get user namespace from task */ task = (struct task_struct*) bpf_get_current_task_btf(); - task_userns = task->cred->user_ns; + cred = task->cred; + if (!cred) + return -EPERM; + task_userns = cred->user_ns; /* fsuid/fsgid are the UID/GID in the initial user namespace, before any idmapped mounts have been * applied. There is no way (yet) to figure out what the UID/GID that will be written to disk will be @@ -138,7 +142,7 @@ static int validate_mount(struct vfsmount *v, int ret) { * translate the transient UID range to something else. For other UIDs/GIDs, there's no need to do * these checks as we don't insist on idmapped mounts or such for UIDs/GIDs outside the transient * ranges. */ - if (!uid_is_transient(task->cred->fsuid.val) && !uid_is_transient((uid_t) task->cred->fsgid.val)) + if (!uid_is_transient(cred->fsuid.val) && !uid_is_transient((uid_t) cred->fsgid.val)) return 0; r = userns_owns_mount(task_userns, v); @@ -170,6 +174,7 @@ SEC("lsm/path_chown") int BPF_PROG(userns_restrict_path_chown, struct path *path, unsigned long long uid, unsigned long long gid, int ret) { struct user_namespace *task_userns; unsigned task_userns_inode; + const struct cred *cred; struct task_struct *task; struct vfsmount *v; void *mnt_id_map; @@ -180,7 +185,10 @@ int BPF_PROG(userns_restrict_path_chown, struct path *path, unsigned long long u /* Get user namespace from task */ task = (struct task_struct*) bpf_get_current_task_btf(); - task_userns = task->cred->user_ns; + cred = task->cred; + if (!cred) + return -EPERM; + task_userns = cred->user_ns; v = path->mnt; r = userns_owns_mount(task_userns, v); diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 8c523dc02bae2..a895c3fe91edd 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -175,7 +175,7 @@ static void notify_bus_error(const sd_bus_error *error) { (void) sd_notifyf(/* unset_environment= */ false, "BUSERROR=%s", error->name); } -static int list_bus_names(int argc, char **argv, void *userdata) { +static int verb_list_bus_names(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **acquired = NULL, **activatable = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_hashmap_free_ Hashmap *names = NULL; @@ -393,6 +393,8 @@ static int list_bus_names(int argc, char **argv, void *userdata) { } static void print_subtree(const char *prefix, const char *path, char **l) { + assert(l); + /* We assume the list is sorted. Let's first skip over the * entry we are looking at. */ for (;;) { @@ -535,7 +537,7 @@ static int tree_one(sd_bus *bus, const char *service) { return r; } -static int tree(int argc, char **argv, void *userdata) { +static int verb_tree(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -994,7 +996,7 @@ static int members_flags_to_string(const Member *m, char **ret) { return 0; } -static int introspect(int argc, char **argv, void *userdata) { +static int verb_introspect(int argc, char *argv[], uintptr_t _data, void *userdata) { static const XMLIntrospectOps ops = { .on_interface = on_interface, .on_method = on_method, @@ -1186,13 +1188,7 @@ static int introspect(int argc, char **argv, void *userdata) { return table_log_add_error(r); } - pager_open(arg_pager_flags); - - r = table_print(table, NULL); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); } static int message_dump(sd_bus_message *m, FILE *f) { @@ -1381,11 +1377,11 @@ static int monitor(int argc, char **argv, int (*dump)(sd_bus_message *m, FILE *f } } -static int verb_monitor(int argc, char **argv, void *userdata) { +static int verb_monitor(int argc, char *argv[], uintptr_t _data, void *userdata) { return monitor(argc, argv, sd_json_format_enabled(arg_json_format_flags) ? message_json : message_dump); } -static int verb_capture(int argc, char **argv, void *userdata) { +static int verb_capture(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *osname = NULL; static const char info[] = "busctl (systemd) " PROJECT_VERSION_FULL " (Git " GIT_VERSION ")"; @@ -1412,7 +1408,7 @@ static int verb_capture(int argc, char **argv, void *userdata) { return r; } -static int status(int argc, char **argv, void *userdata) { +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; pid_t pid; @@ -1782,7 +1778,7 @@ static int bus_message_dump(sd_bus_message *m, uint64_t flags) { return 0; } -static int call(int argc, char **argv, void *userdata) { +static int verb_call(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; @@ -1849,7 +1845,7 @@ static int call(int argc, char **argv, void *userdata) { return bus_message_dump(reply, /* flags= */ 0); } -static int emit_signal(int argc, char **argv, void *userdata) { +static int verb_emit_signal(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_fdset_free_ FDSet *passed_fdset = NULL; @@ -1894,7 +1890,7 @@ static int emit_signal(int argc, char **argv, void *userdata) { return 0; } -static int get_property(int argc, char **argv, void *userdata) { +static int verb_get_property(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -1952,7 +1948,7 @@ static int on_bus_signal(sd_bus_message *msg, void *userdata, sd_bus_error *ret_ return 0; } -static int wait_signal(int argc, char **argv, void *userdata) { +static int verb_wait_signal(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_event_unrefp) sd_event *e = NULL; _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL; @@ -2000,7 +1996,7 @@ static int wait_signal(int argc, char **argv, void *userdata) { return sd_event_loop(e); } -static int set_property(int argc, char **argv, void *userdata) { +static int verb_set_property(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -2131,7 +2127,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } @@ -2405,18 +2401,18 @@ static int parse_argv(int argc, char *argv[]) { static int busctl_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "list", VERB_ANY, 1, VERB_DEFAULT, list_bus_names }, - { "status", VERB_ANY, 2, 0, status }, - { "monitor", VERB_ANY, VERB_ANY, 0, verb_monitor }, - { "capture", VERB_ANY, VERB_ANY, 0, verb_capture }, - { "tree", VERB_ANY, VERB_ANY, 0, tree }, - { "introspect", 3, 4, 0, introspect }, - { "call", 5, VERB_ANY, 0, call }, - { "emit", 4, VERB_ANY, 0, emit_signal }, - { "wait", 4, 5, 0, wait_signal }, - { "get-property", 5, VERB_ANY, 0, get_property }, - { "set-property", 6, VERB_ANY, 0, set_property }, - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list_bus_names }, + { "status", VERB_ANY, 2, 0, verb_status }, + { "monitor", VERB_ANY, VERB_ANY, 0, verb_monitor }, + { "capture", VERB_ANY, VERB_ANY, 0, verb_capture }, + { "tree", VERB_ANY, VERB_ANY, 0, verb_tree }, + { "introspect", 3, 4, 0, verb_introspect }, + { "call", 5, VERB_ANY, 0, verb_call }, + { "emit", 4, VERB_ANY, 0, verb_emit_signal }, + { "wait", 4, 5, 0, verb_wait_signal }, + { "get-property", 5, VERB_ANY, 0, verb_get_property }, + { "set-property", 6, VERB_ANY, 0, verb_set_property }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, {} }; diff --git a/src/cgls/cgls.c b/src/cgls/cgls.c index c224a892e41ed..60d8e7701235b 100644 --- a/src/cgls/cgls.c +++ b/src/cgls/cgls.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" @@ -10,8 +9,10 @@ #include "bus-util.h" #include "cgroup-show.h" #include "cgroup-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "output-mode.h" #include "pager.h" #include "parse-util.h" @@ -35,155 +36,132 @@ static char **arg_names = NULL; static int arg_full = -1; static const char* arg_machine = NULL; -STATIC_DESTRUCTOR_REGISTER(arg_names, freep); /* don't free the strings */ +STATIC_DESTRUCTOR_REGISTER(arg_names, strv_freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-cgls", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] [CGROUP...]\n\n" - "Recursively show control group contents.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " -a --all Show all groups, including empty\n" - " -u --unit Show the subtrees of specified system units\n" - " --user-unit Show the subtrees of specified user units\n" - " -x --xattr=BOOL Show cgroup extended attributes\n" - " -c --cgroup-id=BOOL Show cgroup ID\n" - " -l --full Do not ellipsize output\n" - " -k Include kernel threads in output\n" - " -M --machine=NAME Show container NAME\n" - "\nSee the %s for details.\n", + "%sRecursively show control group contents.%s\n\n", program_invocation_short_name, - link); + ansi_highlight(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_NO_PAGER = 0x100, - ARG_VERSION, - ARG_USER_UNIT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "all", no_argument, NULL, 'a' }, - { "full", no_argument, NULL, 'l' }, - { "machine", required_argument, NULL, 'M' }, - { "unit", optional_argument, NULL, 'u' }, - { "user-unit", optional_argument, NULL, ARG_USER_UNIT }, - { "xattr", required_argument, NULL, 'x' }, - { "cgroup-id", required_argument, NULL, 'c' }, - {} - }; - - int c, r; - assert(argc >= 1); assert(argv); - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; - while ((c = getopt_long(argc, argv, "-hkalM:u::xc", options, NULL)) >= 0) + OptionParser state = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case 'a': + OPTION('a', "all", NULL, "Show all groups, including empty"): arg_output_flags |= OUTPUT_SHOW_ALL; break; - case 'u': + OPTION_FULL(OPTION_OPTIONAL_ARG, 'u', "unit", "UNIT", + "Show the subtrees of specified system units"): if (arg_show_unit == SHOW_UNIT_USER) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --unit with --user-unit."); arg_show_unit = SHOW_UNIT_SYSTEM; - if (strv_push(&arg_names, optarg) < 0) /* push optarg if not empty */ + if (strv_extend(&arg_names, arg) < 0) /* push arg if not empty */ return log_oom(); break; - case ARG_USER_UNIT: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "user-unit", "UNIT", + "Show the subtrees of specified user units"): if (arg_show_unit == SHOW_UNIT_SYSTEM) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --user-unit with --unit."); arg_show_unit = SHOW_UNIT_USER; - if (strv_push(&arg_names, optarg) < 0) /* push optarg if not empty */ - return log_oom(); - break; - - case 1: - /* positional argument */ - if (strv_push(&arg_names, optarg) < 0) + if (strv_extend(&arg_names, arg) < 0) /* push arg if not empty */ return log_oom(); break; - case 'l': - arg_full = true; - break; - - case 'k': - arg_output_flags |= OUTPUT_KERNEL_THREADS; - break; - - case 'M': - arg_machine = optarg; - break; - - case 'x': - if (optarg) { - r = parse_boolean(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "xattr", "BOOL", + "Show cgroup extended attributes"): {} + OPTION_SHORT('x', NULL, "Same as --xattr=true"): + if (arg) { + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --xattr= value: %s", optarg); + return log_error_errno(r, "Failed to parse --xattr= value: %s", arg); } else r = true; SET_FLAG(arg_output_flags, OUTPUT_CGROUP_XATTRS, r); break; - case 'c': - if (optarg) { - r = parse_boolean(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "cgroup-id", "BOOL", + "Show cgroup ID"): {} + OPTION_SHORT('c', NULL, "Same as --cgroup-id=true"): + if (arg) { + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --cgroup-id= value: %s", optarg); + return log_error_errno(r, "Failed to parse --cgroup-id= value: %s", arg); } else r = true; SET_FLAG(arg_output_flags, OUTPUT_CGROUP_ID, r); break; - case '?': - return -EINVAL; + OPTION('l', "full", NULL, "Do not ellipsize output"): + arg_full = true; + break; - default: - assert_not_reached(); + OPTION_SHORT('k', NULL, "Include kernel threads in output"): + arg_output_flags |= OUTPUT_KERNEL_THREADS; + break; + + OPTION_COMMON_MACHINE: + arg_machine = arg; + break; + + OPTION_POSITIONAL: + if (strv_extend(&arg_names, arg) < 0) /* push arg */ + return log_oom(); + break; } if (arg_machine && arg_show_unit != SHOW_UNIT_NONE) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --unit or --user-unit with --machine=."); + assert(option_parser_get_n_args(&state) == 0); + return 1; } diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index 60181caffc122..b8194de3d3eb6 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -10,9 +9,11 @@ #include "cgroup-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "hashmap.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" @@ -72,13 +73,15 @@ typedef enum { _CPU_INVALID = -EINVAL, } CPUType; -static unsigned arg_depth = 3; +#define DEFAULT_MAXIMUM_DEPTH 3 + +static unsigned arg_depth = DEFAULT_MAXIMUM_DEPTH; static unsigned arg_iterations = UINT_MAX; static bool arg_batch = false; static bool arg_raw = false; static usec_t arg_delay = 1*USEC_PER_SEC; -static char* arg_machine = NULL; -static char* arg_root = NULL; +static const char *arg_machine = NULL; +static const char *arg_root = NULL; static bool arg_recursive = true; static bool arg_recursive_unset = false; static PidsCount arg_count = COUNT_PIDS; @@ -285,19 +288,14 @@ static int process_cpu(Group *g, unsigned iteration) { if (r < 0) return r; } else { - _cleanup_free_ char *val = NULL; uint64_t u; - r = cg_get_keyed_attribute(g->path, "cpu.stat", STRV_MAKE("usage_usec"), &val); + r = cg_get_keyed_attribute_uint64(g->path, "cpu.stat", "usage_usec", &u); if (IN_SET(r, -ENOENT, -ENXIO)) return 0; if (r < 0) return r; - r = safe_atou64(val, &u); - if (r < 0) - return r; - new_usage = u * NSEC_PER_USEC; } @@ -509,7 +507,7 @@ static int refresh( } static int group_compare(Group * const *a, Group * const *b) { - const Group *x = *a, *y = *b; + const Group *x = *ASSERT_PTR(a), *y = *ASSERT_PTR(b); int r; if (arg_order != ORDER_TASKS || arg_recursive) { @@ -692,194 +690,151 @@ static void display(Hashmap *a) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-cgtop", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] [CGROUP]\n\n" - "Show top control groups by their resource usage.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - - " --order=path|tasks|cpu|memory|io\n" - " Order by specified property\n" - " -p Same as --order=path, order by path\n" - " -t Same as --order=tasks, order by number of\n" - " tasks/processes\n" - " -c Same as --order=cpu, order by CPU load\n" - " -m Same as --order=memory, order by memory load\n" - " -i Same as --order=io, order by IO load\n" - " -r --raw Provide raw (not human-readable) numbers\n" - " --cpu[=percentage]\n" - " Show CPU usage as percentage (default)\n" - " --cpu=time Show CPU usage as time\n" - " -P Count userspace processes instead of tasks (excl. kernel)\n" - " -k Count all processes instead of tasks (incl. kernel)\n" - " --recursive=BOOL Sum up process count recursively\n" - " -d --delay=DELAY Delay between updates\n" - " -n --iterations=N Run for N iterations before exiting\n" - " -1 Shortcut for --iterations=1\n" - " -b --batch Run in batch mode, accepting no input\n" - " --depth=DEPTH Maximum traversal depth (default: %u)\n" - " -M --machine= Show container\n" - "\nSee the %s for details.\n", + "%sShow top control groups by their resource usage.%s\n\n", program_invocation_short_name, - arg_depth, - link); + ansi_highlight(), + ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_DEPTH, - ARG_CPU_TYPE, - ARG_ORDER, - ARG_RECURSIVE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "delay", required_argument, NULL, 'd' }, - { "iterations", required_argument, NULL, 'n' }, - { "batch", no_argument, NULL, 'b' }, - { "raw", no_argument, NULL, 'r' }, - { "depth", required_argument, NULL, ARG_DEPTH }, - { "cpu", optional_argument, NULL, ARG_CPU_TYPE }, - { "order", required_argument, NULL, ARG_ORDER }, - { "recursive", required_argument, NULL, ARG_RECURSIVE }, - { "machine", required_argument, NULL, 'M' }, - {} - }; - - int c, r; - assert(argc >= 1); assert(argv); - while ((c = getopt_long(argc, argv, "hptcmin:brd:kPM:1", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_CPU_TYPE: - if (optarg) { - arg_cpu_type = cpu_type_from_string(optarg); - if (arg_cpu_type < 0) - return log_error_errno(arg_cpu_type, - "Unknown argument to --cpu=: %s", - optarg); - } else - arg_cpu_type = CPU_TIME; - + OPTION_LONG("order", "PROPERTY", + "Order by specified property (path, tasks, cpu, memory, io)"): + arg_order = order_from_string(arg); + if (arg_order < 0) + return log_error_errno(arg_order, + "Invalid argument to --order=: %s", + arg); break; - case ARG_DEPTH: - r = safe_atou(optarg, &arg_depth); - if (r < 0) - return log_error_errno(r, "Failed to parse depth parameter '%s': %m", optarg); - + OPTION_SHORT('p', NULL, "Same as --order=path, order by path"): + arg_order = ORDER_PATH; break; - case 'd': - r = parse_sec(optarg, &arg_delay); - if (r < 0) - return log_error_errno(r, "Failed to parse delay parameter '%s': %m", optarg); - if (arg_delay <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid delay parameter '%s'", - optarg); - + OPTION_SHORT('t', NULL, "Same as --order=tasks, order by number of tasks/processes"): + arg_order = ORDER_TASKS; break; - case 'n': - r = safe_atou(optarg, &arg_iterations); - if (r < 0) - return log_error_errno(r, "Failed to parse iterations parameter '%s': %m", optarg); - + OPTION_SHORT('c', NULL, "Same as --order=cpu, order by CPU load"): + arg_order = ORDER_CPU; break; - case '1': - arg_iterations = 1; + OPTION_SHORT('m', NULL, "Same as --order=memory, order by memory load"): + arg_order = ORDER_MEMORY; break; - case 'b': - arg_batch = true; + OPTION_SHORT('i', NULL, "Same as --order=io, order by IO load"): + arg_order = ORDER_IO; break; - case 'r': + OPTION('r', "raw", NULL, "Provide raw (not human-readable) numbers"): arg_raw = true; break; - case 'p': - arg_order = ORDER_PATH; + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "cpu", "percentage|time", + "Show CPU usage as percentage (default) or time"): + if (arg) { + arg_cpu_type = cpu_type_from_string(arg); + if (arg_cpu_type < 0) + return log_error_errno(arg_cpu_type, + "Unknown argument to --cpu=: %s", + arg); + } else + arg_cpu_type = CPU_TIME; break; - case 't': - arg_order = ORDER_TASKS; + OPTION_SHORT('P', NULL, "Count userspace processes instead of tasks (excl. kernel)"): + arg_count = COUNT_USERSPACE_PROCESSES; break; - case 'c': - arg_order = ORDER_CPU; + OPTION_SHORT('k', NULL, "Count all processes instead of tasks (incl. kernel)"): + arg_count = COUNT_ALL_PROCESSES; break; - case 'm': - arg_order = ORDER_MEMORY; + OPTION_LONG("recursive", "BOOL", "Sum up process count recursively"): + r = parse_boolean_argument("--recursive=", arg, &arg_recursive); + if (r < 0) + return r; + + arg_recursive_unset = !r; break; - case 'i': - arg_order = ORDER_IO; + OPTION('d', "delay", "DELAY", "Delay between updates"): + r = parse_sec(arg, &arg_delay); + if (r < 0) + return log_error_errno(r, "Failed to parse delay parameter '%s': %m", arg); + if (arg_delay <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid delay parameter '%s'", + arg); break; - case ARG_ORDER: - arg_order = order_from_string(optarg); - if (arg_order < 0) - return log_error_errno(arg_order, - "Invalid argument to --order=: %s", - optarg); + OPTION('n', "iterations", "N", "Run for N iterations before exiting"): + r = safe_atou(arg, &arg_iterations); + if (r < 0) + return log_error_errno(r, "Failed to parse iterations parameter '%s': %m", arg); break; - case 'k': - arg_count = COUNT_ALL_PROCESSES; + OPTION_SHORT('1', NULL, "Shortcut for --iterations=1"): + arg_iterations = 1; break; - case 'P': - arg_count = COUNT_USERSPACE_PROCESSES; + OPTION('b', "batch", NULL, "Run in batch mode, accepting no input"): + arg_batch = true; break; - case ARG_RECURSIVE: - r = parse_boolean_argument("--recursive=", optarg, &arg_recursive); + OPTION_LONG("depth", "DEPTH", + "Maximum traversal depth (default: "STRINGIFY(DEFAULT_MAXIMUM_DEPTH)")"): + r = safe_atou(arg, &arg_depth); if (r < 0) - return r; - - arg_recursive_unset = !r; + return log_error_errno(r, "Failed to parse depth parameter '%s': %m", arg); break; - case 'M': - arg_machine = optarg; + OPTION_COMMON_MACHINE: + arg_machine = arg; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind == argc - 1) - arg_root = argv[optind]; - else if (optind < argc) + size_t n_args = option_parser_get_n_args(&state); + if (n_args > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments."); + if (n_args == 1) + arg_root = option_parser_get_args(&state)[0]; return 1; } diff --git a/src/clonesetup/clonesetup-generator.c b/src/clonesetup/clonesetup-generator.c new file mode 100644 index 0000000000000..32c5bb144a39d --- /dev/null +++ b/src/clonesetup/clonesetup-generator.c @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "alloc-util.h" +#include "errno-util.h" +#include "dropin.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "generator.h" +#include "log.h" +#include "path-util.h" +#include "special.h" +#include "string-util.h" +#include "unit-name.h" + +static const char *arg_dest = NULL; + +/* Generate unit files that call the systemd-clonesetup binary to create or remove clone devices. */ +static int generate_clone_units(const char *clone_name, const char *source_dev, const char *dest_dev, + const char *metadata_dev, const char *options) { + + /* unit files for each device */ + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *source_unit = NULL, *dest_unit = NULL, *metadata_unit = NULL, + *escaped_source = NULL, *escaped_dest = NULL, *escaped_metadata = NULL, + *escaped_options = NULL, *e = NULL, *unit = NULL, *clone_dev_path = NULL, + *dmname = NULL; + int r; + + assert(clone_name); + assert(source_dev); + assert(dest_dev); + assert(metadata_dev); + + /* create clone_dev_path that holds path for new cloned device */ + clone_dev_path = path_join("/dev/mapper", clone_name); + if (!clone_dev_path) + return log_oom(); + + /* escape clone name */ + e = unit_name_escape(clone_name); + if (!e) + return log_oom(); + + /* Generate unit name for the clone service */ + r = unit_name_build("systemd-clonesetup", e, ".service", &unit); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + /* Generate unit names for dependencies */ + r = unit_name_from_path(source_dev, ".device", &source_unit); + if (r < 0) + return log_error_errno(r, "Failed to generate source device unit name: %m"); + + r = unit_name_from_path(dest_dev, ".device", &dest_unit); + if (r < 0) + return log_error_errno(r, "Failed to generate dest device unit name: %m"); + + r = unit_name_from_path(metadata_dev, ".device", &metadata_unit); + if (r < 0) + return log_error_errno(r, "Failed to generate metadata device unit name: %m"); + + /* Escape device paths for ExecStart command */ + escaped_source = cescape(source_dev); + if (!escaped_source) + return log_oom(); + + escaped_dest = cescape(dest_dev); + if (!escaped_dest) + return log_oom(); + + escaped_metadata = cescape(metadata_dev); + if (!escaped_metadata) + return log_oom(); + + if (options) { + escaped_options = cescape(options); + if (!escaped_options) + return log_oom(); + } + + r = generator_open_unit_file(arg_dest, /* source = */ NULL, unit, &f); + if (r < 0) + return r; + + fprintf(f, + "[Unit]\n" + "Description=Create dm-clone device %1$s\n" + "Documentation=man:clonetab(5) man:systemd-clonesetup-generator(8)\n" + "DefaultDependencies=no\n" + "BindsTo=%2$s %3$s %4$s\n" + "Requires=%2$s %3$s %4$s\n" + "After=%2$s %3$s %4$s\n" + "Before=blockdev@dev-mapper-%5$s.target\n" + "Wants=blockdev@dev-mapper-%5$s.target\n" + "Conflicts=shutdown.target\n" + "\n" + "[Service]\n" + "Type=oneshot\n" + "RemainAfterExit=yes\n" + "ExecStart=" SYSTEMD_CLONESETUP_PATH " add '%6$s' '%7$s' '%8$s' '%9$s' '%10$s'\n" + "ExecStop=" SYSTEMD_CLONESETUP_PATH " remove %11$s\n" + "TimeoutSec=0\n", + clone_dev_path, + source_unit, dest_unit, metadata_unit, + e, + clone_name, escaped_source, escaped_dest, escaped_metadata, escaped_options ?: "", + clone_name); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit %s: %m", unit); + + /* symlink unit file to enable it */ + dmname = strjoin("dev-mapper-", e, ".device"); + if (!dmname) + return log_oom(); + + r = generator_add_symlink(arg_dest, dmname, "requires", unit); + if (r < 0) + return r; + + /* Extend device timeout to allow clone service to complete */ + r = write_drop_in(arg_dest, dmname, 40, "device-timeout", + "# Automatically generated by systemd-clonesetup-generator\n\n" + "[Unit]\n" + "JobTimeoutSec=infinity\n"); + if (r < 0) + log_warning_errno(r, "Failed to write device timeout drop-in: %m"); + + /* Add to clonesetup.target so it starts at boot */ + r = generator_add_symlink(arg_dest, SPECIAL_CLONESETUP_TARGET, "requires", unit); + if (r < 0) + return r; + + return 0; +} + +static int add_clone_devices(void) { + _cleanup_fclose_ FILE *f = NULL; + unsigned clone_line = 0; + int r, ret = 0; + const char *fname; + + fname = secure_getenv("SYSTEMD_CLONETAB") ?: "/etc/clonetab"; + + r = fopen_unlocked(fname, "re", &f); + if (r < 0) { + if (errno != ENOENT) + log_error_errno(errno, "Failed to open %s: %m", fname); + return 0; + } + + for (;;) { + _cleanup_free_ char *line = NULL, *src = NULL, *name = NULL, *dst = NULL, *meta = NULL, *options = NULL; + int k; + + r = read_stripped_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return log_error_errno(r, "Failed to read %s: %m", fname); + if (r == 0) + break; + + clone_line++; + + if (IN_SET(line[0], 0, '#')) + continue; + + k = sscanf(line, "%ms %ms %ms %ms %ms", &name, &src, &dst, &meta, &options); + if (k < 4 || k > 5) { + log_error("Failed to parse %s:%u, ignoring.", fname, clone_line); + continue; + } + + RET_GATHER(ret, generate_clone_units(name, src, dst, meta, options)); + } + + return ret; +} + +/* This generator reads /etc/clonetab and for each entry, writes unit files + * (creates systemd-clonesetup@.service and clonesetup.target.requires/systemd-clonesetup@.service) + * that clonesetup.target requires, and that run systemd-clonesetup (add device at boot, + * remove it at shutdown); systemd-clonesetup (used in systemd-clonesetup@.service) is the binary that + * uses device-mapper ioctls to create and remove the dm-clone devices. + * clonesetup.target groups these units so they run together at boot. + * Boot chain: sysinit.target has clonesetup.target in sysinit.target.wants/ (see units/meson.build), + * so at boot clonesetup.target starts and pulls in these units via clonesetup.target.requires/. */ +static int run(const char *dest, const char *dest_early, const char *dest_late) { + + /* dest usually is /run/systemd/generator */ + assert_se(arg_dest = dest); + + return add_clone_devices(); +} + +DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/clonesetup/clonesetup-ioctl.c b/src/clonesetup/clonesetup-ioctl.c new file mode 100644 index 0000000000000..2d3f29d789316 --- /dev/null +++ b/src/clonesetup/clonesetup-ioctl.c @@ -0,0 +1,213 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include + +#include "clonesetup-ioctl.h" +#include "device-private.h" +#include "fd-util.h" +#include "log.h" +#include "sd-device.h" +#include "string-util.h" + +/* Returns the size in bytes of the block device at dev_path. + * Loading the dm-clone table needs the source device size in sectors; sysfs + * reports size in 512-byte sectors. This reads sysfs and returns bytes so the + * caller can divide by 512 and pass the sector count to dm_clone_load_table(). */ +static int get_size(const char *dev_path, uint64_t *ret_size) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + uint64_t size; + int r; + + assert(dev_path); + assert(ret_size); + + r = sd_device_new_from_devname(&dev, dev_path); + if (r < 0) + return log_error_errno(r, "Failed to create device from '%s': %m", dev_path); + + r = device_get_sysattr_u64(dev, "size", &size); + if (r < 0) + return log_error_errno(r, "Failed to get device size for '%s': %m", dev_path); + + /* sysfs 'size' is in 512-byte sectors */ + *ret_size = size * 512; + return 0; +} + +/* Common helper used to run dm ioctls. */ +static int dm_ioctl_run(const char *name, uint32_t cmd, struct dm_ioctl *data, size_t data_size) { + _cleanup_close_ int fd = -EBADF; + struct dm_ioctl *dm = data; + + assert(name); + assert(data); + assert(data_size >= sizeof(struct dm_ioctl)); + + dm->version[0] = DM_VERSION_MAJOR; + dm->version[1] = DM_VERSION_MINOR; + dm->version[2] = DM_VERSION_PATCHLEVEL; + dm->data_size = data_size; + + assert(strlen(name) < sizeof_field(struct dm_ioctl, name)); + strncpy_exact(dm->name, name, sizeof(dm->name)); + + fd = open("/dev/mapper/control", O_RDWR | O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open /dev/mapper/control: %m"); + + if (ioctl(fd, cmd, dm) < 0) + return log_error_errno(errno, "DM ioctl failed: %m"); + + return 0; +} + +/* First dm ioctl needed to create a device. */ +static int dm_clone_create(const char *name) { + assert(name); + + struct dm_ioctl dm = {}; + return dm_ioctl_run(name, DM_DEV_CREATE, &dm, sizeof(dm)); +} + +/* Second dm ioctl needed to create a device. */ +static int dm_clone_load_table(const char *name, uint64_t size_sectors, const char *target_params) { + _cleanup_free_ void *dm_buf = NULL; + char *params_buf; + size_t params_len, dm_size; + struct dm_ioctl *dm; + struct dm_target_spec *tgt; + + assert(name); + assert(target_params); + + params_len = strlen(target_params) + 1; + dm_size = sizeof(struct dm_ioctl) + sizeof(struct dm_target_spec) + params_len; + dm_buf = malloc0(dm_size); + if (!dm_buf) + return -ENOMEM; + dm = dm_buf; + *dm = (struct dm_ioctl) { + .data_start = sizeof(struct dm_ioctl), + .target_count = 1, + }; + + tgt = (struct dm_target_spec *) ((uint8_t *) dm + dm->data_start); + *tgt = (struct dm_target_spec) { + .length = size_sectors, + .next = 0, + }; + strncpy(tgt->target_type, "clone", sizeof(tgt->target_type)); + + params_buf = (char *) ((uint8_t *) tgt + sizeof(struct dm_target_spec)); + strcpy(params_buf, target_params); + + return dm_ioctl_run(name, DM_TABLE_LOAD, dm, dm_size); +} + +/* Third and final dm ioctl needed to create a device. */ +static int dm_clone_activate(const char *name) { + assert(name); + + struct dm_ioctl dm = {}; + + return dm_ioctl_run(name, DM_DEV_SUSPEND, &dm, sizeof(dm)); +} + +/* Calls multiple dm ioctls to create device. */ +int dm_clone_create_device( + const char *name, + const char *source_dev, + const char *dest_dev, + const char *metadata_dev, + uint64_t region_size) { + + _cleanup_free_ char *target_params = NULL; + uint64_t src_dev_size_sectors, src_dev_size; + int r; + + assert(name); + assert(source_dev); + assert(dest_dev); + assert(metadata_dev); + + r = get_size(source_dev, &src_dev_size); + if (r < 0) + return r; + + /* The device mapper kernel API always uses 512-byte sectors, regardless of the + * physical block size of the device (all DM targets use sector_t which is 512B). + * Sysfs /sys/block//size also reports device size in 512-byte sectors. + * So we divide the byte size by 512 to get the sector count for the DM table. */ + src_dev_size_sectors = src_dev_size / 512; + + /* dm-clone target params: [options] + * region_size = region size in sectors, configurable via clonetab (default 8 = 4KB regions with 512-byte sectors) + * 1 = hydration threshold (regions to hydrate per batch) + * no_hydration = don't start automatic background hydration + * + * The DM table "target_params" string is passed directly to the kernel via ioctl(fd, DM_TABLE_LOAD, ...) in + * dm_clone_load_table as a raw byte buffer. The kernel's DM table parser (drivers/md/dm-table.c) simply splits the params string on whitespace, + * so the only constraint is that the paths in params - metadata_dev, dest_dev, source_dev, and region_size + * must not contain spaces, which standard /dev/ paths never do, so the below args do NOT require + * shell escaping */ + if (asprintf(&target_params, "%s %s %s %" PRIu64 " 1 no_hydration", metadata_dev, dest_dev, source_dev, region_size) < 0) + return log_oom(); + + r = dm_clone_create(name); + if (r < 0) + return r; + + r = dm_clone_load_table(name, src_dev_size_sectors, target_params); + if (r < 0) + return r; + + r = dm_clone_activate(name); + if (r < 0) + return r; + + log_info("Device %s active.", name); + return 0; +} + +/* Calls dm ioctl to send a message to the device. */ +int dm_clone_send_message(const char *name, const char *message) { + _cleanup_free_ void *dm_buf = NULL; + struct dm_ioctl *dm; + struct dm_target_msg *msg; + size_t dm_size, msg_len; + + assert(name); + assert(message); + + msg_len = strlen(message) + 1; + dm_size = sizeof(struct dm_ioctl) + sizeof(struct dm_target_msg) + msg_len; + dm_buf = malloc0(dm_size); + if (!dm_buf) + return -ENOMEM; + dm = dm_buf; + *dm = (struct dm_ioctl) { + .data_start = sizeof(struct dm_ioctl), + }; + + msg = (struct dm_target_msg *) ((uint8_t *) dm + dm->data_start); + strcpy(msg->message, message); + + return dm_ioctl_run(name, DM_TARGET_MSG, dm, dm_size); +} + +/* Calls dm ioctl to remove a device. */ +int dm_clone_remove_device(const char *name) { + struct dm_ioctl dm = {}; + int r; + + assert(name); + r = dm_ioctl_run(name, DM_DEV_REMOVE, &dm, sizeof(dm)); + if (r < 0) + return r; + + log_info("Device %s inactive.", name); + return 0; +} diff --git a/src/clonesetup/clonesetup-ioctl.h b/src/clonesetup/clonesetup-ioctl.h new file mode 100644 index 0000000000000..3c36a3336e08a --- /dev/null +++ b/src/clonesetup/clonesetup-ioctl.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +int dm_clone_create_device( + const char *name, + const char *source_dev, + const char *dest_dev, + const char *metadata_dev, + uint64_t region_size); + +int dm_clone_send_message(const char *name, const char *message); + +int dm_clone_remove_device(const char *name); + diff --git a/src/clonesetup/clonesetup.c b/src/clonesetup/clonesetup.c new file mode 100644 index 0000000000000..49f36960b55ad --- /dev/null +++ b/src/clonesetup/clonesetup.c @@ -0,0 +1,229 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include +#include +#include +#include /* access */ + +#include "alloc-util.h" +#include "argv-util.h" +#include "build.h" +#include "clonesetup-ioctl.h" +#include "log.h" +#include "main-func.h" +#include "pretty-print.h" +#include "verbs.h" +#include "path-util.h" /* path_join */ +#include "time-util.h" /* USEC_PER_SEC */ +#include "udev-util.h" /* device_wait_for_devlink */ +#include "extract-word.h" +#include "string-util.h" +#include "parse-util.h" + +/* region_size: Must be a power of 2 between 8 and 2097152 per dm-clone kernel docs. */ +#define DEFAULT_REGION_SIZE 8 +#define CLONE_MIN_REGION_SIZE (UINT64_C(1) << 3) /* 8 */ +#define CLONE_MAX_REGION_SIZE (UINT64_C(1) << 21) /* 2097152 */ + +static int parse_clone_options(const char *options, uint64_t *ret_region_size) { + *ret_region_size=DEFAULT_REGION_SIZE; + + for (;;) { + _cleanup_free_ char *word=NULL; + const char *val; + int r; + + /* extract_first_word: * + * Returns > 0 — successfully extracted a word * + * Returns 0 — no more words (end of string) * + * Returns < 0 — actual error (e.g. memory allocation failure) */ + r = extract_first_word(&options, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS); + if ( r < 0 ) + return log_error_errno(r, "Failed to parse clone options: %m"); + if ( r == 0 ) + break; + + /* region_size=N; size of each clone region in 512-byte sectors ( default 8 = 4KB ) + * Must be a power of 2 between 8 and 2097152 per dm-clone kernel docs. */ + /* treat "" and "-" as empty — common placeholders for "no options" */ + if (streq(word, "\"\"") || streq(word, "-")) + continue; + + if (( val = startswith(word, "region_size="))) { + uint64_t region_size; + r = safe_atou64(val, ®ion_size); + if (r < 0) { + log_warning("Invalid region size=value '%s', using default", val); + } else if (!ISPOWEROF2(region_size) || region_size < CLONE_MIN_REGION_SIZE || region_size > CLONE_MAX_REGION_SIZE) { + log_warning("region_size=%s must be a power of two between 8 and 2097152, using default.", val); + } else { + *ret_region_size=region_size; + } + } else { + /* currently only region_size is supported */ + log_warning("Unknown clone option '%s', ignoring.", word); + } + } + return 0; +} + +/* dm-clone device creation workflow: + * 1. Create the dm-clone device + * 2. Enable background hydration + * 3. (Optional) Replace with linear mapping to finalize */ +static int clone_device(const char *clone_name, const char *source_dev, const char *dest_dev, + const char *metadata_dev, const char *options) { + + _cleanup_free_ char *clone_dev_path = NULL; + int r; + + assert(clone_name); + assert(source_dev); + assert(dest_dev); + assert(metadata_dev); + + /* create clone device path to check if clone device already exists */ + clone_dev_path = path_join("/dev/mapper", clone_name); + if (!clone_dev_path) + return log_oom(); + + /* Check before calling the DM ioctl to give a cleaner error message; + * DM_DEV_CREATE would return EEXIST too, but with a less obvious message. */ + if (access(clone_dev_path, F_OK) >= 0) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Device '%s' already exists.", clone_dev_path); + + uint64_t region_size; + r = parse_clone_options(options, ®ion_size); + if (r < 0) + return log_error_errno(r, "Failed to parse clone options: %m"); + + r = dm_clone_create_device(clone_name, source_dev, dest_dev, metadata_dev, region_size); + if (r < 0) + return log_error_errno(r, "Failed to create dm-clone device: %m"); + + /* Wait for udev to create /dev/mapper/ */ + r = device_wait_for_devlink(clone_dev_path, "block", 10 * USEC_PER_SEC, /* ret_inode = */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to wait for device %s: %m", clone_dev_path); + + r = dm_clone_send_message(clone_name, "enable_hydration"); + if (r < 0) + return log_error_errno(r, "Failed to send dm message: %m"); + + return 0; +} + +/* Arguments: systemd-clonesetup add NAME SOURCE-DEVICE DST_DEVICE META-DEVICE [OPTIONS] */ +static int verb_add(int argc, char *argv[], uintptr_t data, void *userdata) { + int r; + + assert(argc >= 5 && argc <= 6); + + const char *name = ASSERT_PTR(argv[1]), + *src_dev = ASSERT_PTR(argv[2]), + *dst_dev = ASSERT_PTR(argv[3]), + *meta_dev = ASSERT_PTR(argv[4]); + + const char *options = (argc == 6 ? argv[5] : ""); + + log_debug("%s %s %s %s %s opts=%s ", __func__, + name, src_dev, dst_dev, meta_dev, options); + + r = clone_device(name, src_dev, dst_dev, meta_dev, options); + if (r < 0) + return r; + + return 0; +} + +static int verb_remove(int argc, char *argv[], uintptr_t data, void *userdata) { + const char *name = ASSERT_PTR(argv[1]); + int r; + + r = dm_clone_remove_device(name); + if (r < 0) + return r; + + return 0; +} + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-clonesetup", "8", &link); + if (r < 0) + return log_oom(); + + printf("%1$s add NAME SOURCE-DEVICE DST-DEVICE META-DEVICE [OPTIONS] \n" + "%1$s remove VOLUME\n\n" + "%2$sAdd or remove a dm clone device.%3$s\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + "\nSee the %4$s for details.\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + link); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + if (argv_looks_like_help(argc, argv)) + return help(); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + return 1; +} + +/* systemd-clonesetup uses device-mapper ioctls to create and remove the + * dm-clone devices. */ +static int run(int argc, char *argv[]) { + int r; + + log_setup(); + umask(0022); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + static const Verb verbs[] = { + { "add", 5, 6, 0, verb_add, 0 }, + { "remove", 2, 2, 0, verb_remove, 0 }, + {} + }; + return dispatch_verb(argc, argv, verbs, NULL); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/clonesetup/meson.build b/src/clonesetup/meson.build new file mode 100644 index 0000000000000..9e7cdfe347fa4 --- /dev/null +++ b/src/clonesetup/meson.build @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +systemd_clonesetup_sources = files( + 'clonesetup-ioctl.c', + 'clonesetup.c', +) + +executables += [ + executable_template + { + 'name' : 'systemd-clonesetup', + 'public' : true, + 'sources' : systemd_clonesetup_sources, + }, + generator_template + { + 'name' : 'systemd-clonesetup-generator', + 'sources' : files('clonesetup-generator.c'), + }, +] diff --git a/src/core/bpf-bind-iface.c b/src/core/bpf-bind-iface.c index fdd2e1d8e6318..74cb2b3d52374 100644 --- a/src/core/bpf-bind-iface.c +++ b/src/core/bpf-bind-iface.c @@ -14,7 +14,7 @@ /* libbpf, clang, llvm and bpftool compile time dependencies are satisfied */ #include "bpf-dlopen.h" #include "bpf-link.h" -#include "bpf/bind-iface/bind-iface-skel.h" +#include "bind-iface-skel.h" static struct bind_iface_bpf *bind_iface_bpf_free(struct bind_iface_bpf *obj) { bind_iface_bpf__destroy(obj); @@ -31,7 +31,7 @@ int bpf_bind_network_interface_supported(void) { if (supported >= 0) return supported; - if (dlopen_bpf_full(LOG_WARNING) < 0) + if (dlopen_bpf(LOG_WARNING) < 0) return (supported = false); obj = bind_iface_bpf__open(); diff --git a/src/core/bpf-devices.c b/src/core/bpf-devices.c index aeb01e8575395..432b3d0162555 100644 --- a/src/core/bpf-devices.c +++ b/src/core/bpf-devices.c @@ -16,6 +16,7 @@ #include "nulstr-util.h" #include "parse-util.h" #include "path-util.h" +#include "stat-util.h" #include "string-util.h" #define PASS_JUMP_OFF 4096 @@ -308,8 +309,9 @@ int bpf_devices_allow_list_device( return log_warning_errno(errno, "Couldn't stat device %s: %m", node); } - if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) - return log_warning_errno(SYNTHETIC_ERRNO(ENODEV), "%s is not a device.", node); + r = stat_verify_device_node(&st); + if (r < 0) + return log_warning_errno(r, "'%s' is not a device node.", node); mode = st.st_mode; rdev = (dev_t) st.st_rdev; diff --git a/src/core/bpf-firewall.c b/src/core/bpf-firewall.c index b0c54d3134723..0a3107e8685b8 100644 --- a/src/core/bpf-firewall.c +++ b/src/core/bpf-firewall.c @@ -603,6 +603,8 @@ int bpf_firewall_compile(Unit *u) { } static int load_bpf_progs_from_fs_to_set(Unit *u, char **filter_paths, Set **set) { + assert(set); + set_clear(*set); STRV_FOREACH(bpf_fs_path, filter_paths) { @@ -661,6 +663,8 @@ static int attach_custom_bpf_progs(Unit *u, const char *path, int attach_type, S int r; assert(u); + assert(set); + assert(set_installed); set_clear(*set_installed); r = set_ensure_allocated(set_installed, &bpf_program_hash_ops); diff --git a/src/core/bpf-restrict-fs.c b/src/core/bpf-restrict-fs.c index 8df2f5235c8d9..e60e6ca7efcfe 100644 --- a/src/core/bpf-restrict-fs.c +++ b/src/core/bpf-restrict-fs.c @@ -17,7 +17,7 @@ /* libbpf, clang and llc compile time dependencies are satisfied */ #include "bpf-dlopen.h" #include "bpf-link.h" -#include "bpf/restrict-fs/restrict-fs-skel.h" +#include "restrict-fs-skel.h" #define CGROUP_HASH_SIZE_MAX 2048 @@ -91,7 +91,7 @@ bool bpf_restrict_fs_supported(bool initialize) { if (!initialize) return false; - if (dlopen_bpf_full(LOG_WARNING) < 0) + if (dlopen_bpf(LOG_WARNING) < 0) return (supported = false); r = lsm_supported("bpf"); diff --git a/src/core/bpf-restrict-ifaces.c b/src/core/bpf-restrict-ifaces.c index a1bac8301be34..3c73a682a3d38 100644 --- a/src/core/bpf-restrict-ifaces.c +++ b/src/core/bpf-restrict-ifaces.c @@ -16,7 +16,7 @@ #include "bpf-dlopen.h" #include "bpf-link.h" -#include "bpf/restrict-ifaces/restrict-ifaces-skel.h" +#include "restrict-ifaces-skel.h" static struct restrict_ifaces_bpf *restrict_ifaces_bpf_free(struct restrict_ifaces_bpf *obj) { restrict_ifaces_bpf__destroy(obj); @@ -86,7 +86,7 @@ int bpf_restrict_ifaces_supported(void) { if (supported >= 0) return supported; - if (dlopen_bpf_full(LOG_WARNING) < 0) + if (dlopen_bpf(LOG_WARNING) < 0) return (supported = false); r = prepare_restrict_ifaces_bpf(NULL, true, NULL, &obj); diff --git a/src/core/bpf-socket-bind.c b/src/core/bpf-socket-bind.c index b7158841fa02d..87e7d60c055a9 100644 --- a/src/core/bpf-socket-bind.c +++ b/src/core/bpf-socket-bind.c @@ -11,8 +11,8 @@ /* libbpf, clang, llvm and bpftool compile time dependencies are satisfied */ #include "bpf-dlopen.h" #include "bpf-link.h" -#include "bpf/socket-bind/socket-bind-api.bpf.h" -#include "bpf/socket-bind/socket-bind-skel.h" +#include "socket-bind-api.bpf.h" +#include "socket-bind-skel.h" static struct socket_bind_bpf *socket_bind_bpf_free(struct socket_bind_bpf *obj) { /* socket_bind_bpf__destroy handles object == NULL case */ @@ -125,7 +125,7 @@ int bpf_socket_bind_supported(void) { _cleanup_(socket_bind_bpf_freep) struct socket_bind_bpf *obj = NULL; int r; - if (dlopen_bpf_full(LOG_WARNING) < 0) + if (dlopen_bpf(LOG_WARNING) < 0) return false; r = prepare_socket_bind_bpf(/* unit= */ NULL, /* allow_rules= */ NULL, /* deny_rules= */ NULL, &obj); diff --git a/src/core/bpf/bind-iface/bind-iface-skel.h b/src/core/bpf/bind-iface/bind-iface-skel.h deleted file mode 100644 index 2ec63ca887dd1..0000000000000 --- a/src/core/bpf/bind-iface/bind-iface-skel.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -/* The SPDX header above is actually correct in claiming this was - * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that - * compatible with GPL we will claim this to be GPL however, which should be - * fine given that LGPL-2.1-or-later downgrades to GPL if needed. - */ - -#include "bpf-dlopen.h" /* IWYU pragma: keep */ - -/* libbpf is used via dlopen(), so rename symbols */ -#define bpf_object__open_skeleton sym_bpf_object__open_skeleton -#define bpf_object__load_skeleton sym_bpf_object__load_skeleton -#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton - -#include "bpf/bind-iface/bind-iface.skel.h" /* IWYU pragma: export */ diff --git a/src/core/bpf/bind-iface/meson.build b/src/core/bpf/bind-iface/meson.build deleted file mode 100644 index 222cac16b08ab..0000000000000 --- a/src/core/bpf/bind-iface/meson.build +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -if conf.get('BPF_FRAMEWORK') != 1 - subdir_done() -endif - -bind_network_interface_bpf_o_unstripped = custom_target( - input : 'bind-iface.bpf.c', - output : 'bind-iface.bpf.unstripped.o', - command : bpf_o_unstripped_cmd) - -bind_network_interface_bpf_o = custom_target( - input : bind_network_interface_bpf_o_unstripped, - output : 'bind-iface.bpf.o', - command : bpf_o_cmd) - -bind_network_interface_skel_h = custom_target( - input : bind_network_interface_bpf_o, - output : 'bind-iface.skel.h', - command : skel_h_cmd, - capture : true) - -generated_sources += bind_network_interface_skel_h diff --git a/src/core/bpf/restrict-fs/meson.build b/src/core/bpf/restrict-fs/meson.build deleted file mode 100644 index 41fc130acebe0..0000000000000 --- a/src/core/bpf/restrict-fs/meson.build +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -if conf.get('BPF_FRAMEWORK') != 1 - subdir_done() -endif - -restrict_fs_bpf_o_unstripped = custom_target( - input : 'restrict-fs.bpf.c', - output : 'restrict-fs.bpf.unstripped.o', - command : bpf_o_unstripped_cmd) - -restrict_fs_bpf_o = custom_target( - input : restrict_fs_bpf_o_unstripped, - output : 'restrict-fs.bpf.o', - command : bpf_o_cmd) - -restrict_fs_skel_h = custom_target( - input : restrict_fs_bpf_o, - output : 'restrict-fs.skel.h', - command : skel_h_cmd, - capture : true) - -generated_sources += restrict_fs_skel_h diff --git a/src/core/bpf/restrict-fs/restrict-fs-skel.h b/src/core/bpf/restrict-fs/restrict-fs-skel.h deleted file mode 100644 index 06935f2cd83b5..0000000000000 --- a/src/core/bpf/restrict-fs/restrict-fs-skel.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -/* The SPDX header above is actually correct in claiming this was - * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that - * compatible with GPL we will claim this to be GPL however, which should be - * fine given that LGPL-2.1-or-later downgrades to GPL if needed. - */ - -#include "bpf-dlopen.h" /* IWYU pragma: keep */ - -/* libbpf is used via dlopen(), so rename symbols */ -#define bpf_object__open_skeleton sym_bpf_object__open_skeleton -#define bpf_object__load_skeleton sym_bpf_object__load_skeleton -#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton - -#include "bpf/restrict-fs/restrict-fs.skel.h" /* IWYU pragma: export */ diff --git a/src/core/bpf/restrict-ifaces/meson.build b/src/core/bpf/restrict-ifaces/meson.build deleted file mode 100644 index f9ef4c753e4ff..0000000000000 --- a/src/core/bpf/restrict-ifaces/meson.build +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -if conf.get('BPF_FRAMEWORK') != 1 - subdir_done() -endif - -restrict_ifaces_bpf_o_unstripped = custom_target( - input : 'restrict-ifaces.bpf.c', - output : 'restrict-ifaces.bpf.unstripped.o', - command : bpf_o_unstripped_cmd) - -restrict_ifaces_bpf_o = custom_target( - input : restrict_ifaces_bpf_o_unstripped, - output : 'restrict-ifaces.bpf.o', - command : bpf_o_cmd) - -restrict_ifaces_skel_h = custom_target( - input : restrict_ifaces_bpf_o, - output : 'restrict-ifaces.skel.h', - command : skel_h_cmd, - capture : true) - -generated_sources += restrict_ifaces_skel_h diff --git a/src/core/bpf/restrict-ifaces/restrict-ifaces-skel.h b/src/core/bpf/restrict-ifaces/restrict-ifaces-skel.h deleted file mode 100644 index 53ba2e5b5a020..0000000000000 --- a/src/core/bpf/restrict-ifaces/restrict-ifaces-skel.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -/* The SPDX header above is actually correct in claiming this was - * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that - * compatible with GPL we will claim this to be GPL however, which should be - * fine given that LGPL-2.1-or-later downgrades to GPL if needed. - */ - -#include "bpf-dlopen.h" /* IWYU pragma: keep */ - -/* libbpf is used via dlopen(), so rename symbols */ -#define bpf_object__open_skeleton sym_bpf_object__open_skeleton -#define bpf_object__load_skeleton sym_bpf_object__load_skeleton -#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton - -#include "bpf/restrict-ifaces/restrict-ifaces.skel.h" /* IWYU pragma: export */ diff --git a/src/core/bpf/socket-bind/meson.build b/src/core/bpf/socket-bind/meson.build deleted file mode 100644 index ec9d34637b3ab..0000000000000 --- a/src/core/bpf/socket-bind/meson.build +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -if conf.get('BPF_FRAMEWORK') != 1 - subdir_done() -endif - -socket_bind_bpf_o_unstripped = custom_target( - input : 'socket-bind.bpf.c', - output : 'socket-bind.bpf.unstripped.o', - command : bpf_o_unstripped_cmd) - -socket_bind_bpf_o = custom_target( - input : socket_bind_bpf_o_unstripped, - output : 'socket-bind.bpf.o', - command : bpf_o_cmd) - -socket_bind_skel_h = custom_target( - input : socket_bind_bpf_o, - output : 'socket-bind.skel.h', - command : skel_h_cmd, - capture : true) - -generated_sources += socket_bind_skel_h diff --git a/src/core/bpf/socket-bind/socket-bind-skel.h b/src/core/bpf/socket-bind/socket-bind-skel.h deleted file mode 100644 index d83dd89e52962..0000000000000 --- a/src/core/bpf/socket-bind/socket-bind-skel.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -/* The SPDX header above is actually correct in claiming this was - * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that - * compatible with GPL we will claim this to be GPL however, which should be - * fine given that LGPL-2.1-or-later downgrades to GPL if needed. - */ - -#include "bpf-dlopen.h" /* IWYU pragma: keep */ - -/* libbpf is used via dlopen(), so rename symbols */ -#define bpf_object__open_skeleton sym_bpf_object__open_skeleton -#define bpf_object__load_skeleton sym_bpf_object__load_skeleton -#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton - -#include "bpf/socket-bind/socket-bind.skel.h" /* IWYU pragma: export */ diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 514dabf371b7f..ae5874cd99daa 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -185,8 +185,11 @@ void cgroup_context_init(CGroupContext *c) { * moom_mem_pressure_duration_usec is set to infinity. */ .moom_mem_pressure_duration_usec = USEC_INFINITY, - .memory_pressure_watch = _CGROUP_PRESSURE_WATCH_INVALID, - .memory_pressure_threshold_usec = USEC_INFINITY, + .pressure = { + [PRESSURE_MEMORY] = { .watch = _CGROUP_PRESSURE_WATCH_INVALID, .threshold_usec = USEC_INFINITY }, + [PRESSURE_CPU] = { .watch = _CGROUP_PRESSURE_WATCH_INVALID, .threshold_usec = USEC_INFINITY }, + [PRESSURE_IO] = { .watch = _CGROUP_PRESSURE_WATCH_INVALID, .threshold_usec = USEC_INFINITY }, + }, }; } @@ -313,6 +316,8 @@ static int unit_compare_memory_limit(Unit *u, const char *property_name, uint64_ * - ret_kernel_value will contain the actual value presented by the kernel. */ assert(u); + assert(ret_unit_value); + assert(ret_kernel_value); /* The root slice doesn't have any controller files, so we can't compare anything. */ if (unit_has_name(u, SPECIAL_ROOT_SLICE)) @@ -526,6 +531,8 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { "%sManagedOOMMemoryPressureLimit: " PERMYRIAD_AS_PERCENT_FORMAT_STR "\n" "%sManagedOOMPreference: %s\n" "%sMemoryPressureWatch: %s\n" + "%sCPUPressureWatch: %s\n" + "%sIOPressureWatch: %s\n" "%sCoredumpReceive: %s\n", prefix, yes_no(c->io_accounting), prefix, yes_no(c->memory_accounting), @@ -561,7 +568,9 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { prefix, managed_oom_mode_to_string(c->moom_mem_pressure), prefix, PERMYRIAD_AS_PERCENT_FORMAT_VAL(UINT32_SCALE_TO_PERMYRIAD(c->moom_mem_pressure_limit)), prefix, managed_oom_preference_to_string(c->moom_preference), - prefix, cgroup_pressure_watch_to_string(c->memory_pressure_watch), + prefix, cgroup_pressure_watch_to_string(c->pressure[PRESSURE_MEMORY].watch), + prefix, cgroup_pressure_watch_to_string(c->pressure[PRESSURE_CPU].watch), + prefix, cgroup_pressure_watch_to_string(c->pressure[PRESSURE_IO].watch), prefix, yes_no(c->coredump_receive)); if (c->delegate_subgroup) @@ -572,9 +581,17 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { fprintf(f, "%sBindNetworkInterface: %s\n", prefix, c->bind_network_interface); - if (c->memory_pressure_threshold_usec != USEC_INFINITY) + if (c->pressure[PRESSURE_MEMORY].threshold_usec != USEC_INFINITY) fprintf(f, "%sMemoryPressureThresholdSec: %s\n", - prefix, FORMAT_TIMESPAN(c->memory_pressure_threshold_usec, 1)); + prefix, FORMAT_TIMESPAN(c->pressure[PRESSURE_MEMORY].threshold_usec, 1)); + + if (c->pressure[PRESSURE_CPU].threshold_usec != USEC_INFINITY) + fprintf(f, "%sCPUPressureThresholdSec: %s\n", + prefix, FORMAT_TIMESPAN(c->pressure[PRESSURE_CPU].threshold_usec, 1)); + + if (c->pressure[PRESSURE_IO].threshold_usec != USEC_INFINITY) + fprintf(f, "%sIOPressureThresholdSec: %s\n", + prefix, FORMAT_TIMESPAN(c->pressure[PRESSURE_IO].threshold_usec, 1)); if (c->moom_mem_pressure_duration_usec != USEC_INFINITY) fprintf(f, "%sManagedOOMMemoryPressureDurationSec: %s\n", @@ -2105,12 +2122,13 @@ static int unit_update_cgroup( cgroup_context_apply(u, target_mask, state); cgroup_xattr_apply(u); - /* For most units we expect that memory monitoring is set up before the unit is started and we won't - * touch it after. For PID 1 this is different though, because we couldn't possibly do that given - * that PID 1 runs before init.scope is even set up. Hence, whenever init.scope is realized, let's - * try to open the memory pressure interface anew. */ + /* For most units we expect that pressure monitoring is set up before the unit is started and we + * won't touch it after. For PID 1 this is different though, because we couldn't possibly do that + * given that PID 1 runs before init.scope is even set up. Hence, whenever init.scope is realized, + * let's try to open the pressure interfaces anew. */ if (unit_has_name(u, SPECIAL_INIT_SCOPE)) - (void) manager_setup_memory_pressure_event_source(u->manager); + for (PressureResource t = 0; t < _PRESSURE_RESOURCE_MAX; t++) + (void) manager_setup_pressure_event_source(u->manager, t); return 0; } @@ -2978,9 +2996,8 @@ int unit_check_oomd_kill(Unit *u) { } int unit_check_oom(Unit *u) { - _cleanup_free_ char *oom_kill = NULL; bool increased; - uint64_t c; + uint64_t c = 0; int r; CGroupRuntime *crt = unit_get_cgroup_runtime(u); @@ -2995,33 +3012,25 @@ int unit_check_oom(Unit *u) { * back to reading oom_kill if we can't find the file or field. */ if (ctx->memory_oom_group) { - r = cg_get_keyed_attribute( + r = cg_get_keyed_attribute_uint64( crt->cgroup_path, "memory.events.local", - STRV_MAKE("oom_group_kill"), - &oom_kill); + "oom_group_kill", + &c); if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO)) return log_unit_debug_errno(u, r, "Failed to read oom_group_kill field of memory.events.local cgroup attribute, ignoring: %m"); } - if (isempty(oom_kill)) { - r = cg_get_keyed_attribute( + if (!ctx->memory_oom_group || r < 0) { + r = cg_get_keyed_attribute_uint64( crt->cgroup_path, "memory.events", - STRV_MAKE("oom_kill"), - &oom_kill); + "oom_kill", + &c); if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO)) return log_unit_debug_errno(u, r, "Failed to read oom_kill field of memory.events cgroup attribute: %m"); } - if (!oom_kill) - c = 0; - else { - r = safe_atou64(oom_kill, &c); - if (r < 0) - return log_unit_debug_errno(u, r, "Failed to parse memory.events cgroup oom field: %m"); - } - increased = c > crt->oom_kill_last; crt->oom_kill_last = c; @@ -3189,6 +3198,8 @@ static int cg_bpf_mask_supported(CGroupMask *ret) { CGroupMask mask = 0; int r; + assert(ret); + /* BPF-based firewall, device access control, and pinned foreign prog */ if (bpf_program_supported() > 0) mask |= CGROUP_MASK_BPF_FIREWALL | @@ -3565,14 +3576,9 @@ static int unit_get_cpu_usage_raw(const Unit *u, const CGroupRuntime *crt, nsec_ if (unit_has_host_root_cgroup(u)) return procfs_cpu_get_usage(ret); - _cleanup_free_ char *val = NULL; uint64_t us; - r = cg_get_keyed_attribute(crt->cgroup_path, "cpu.stat", STRV_MAKE("usage_usec"), &val); - if (r < 0) - return r; - - r = safe_atou64(val, &us); + r = cg_get_keyed_attribute_uint64(crt->cgroup_path, "cpu.stat", "usage_usec", &us); if (r < 0) return r; @@ -3982,6 +3988,29 @@ bool unit_cgroup_delegate(Unit *u) { return c->delegate; } +void unit_cgroup_disable_all_controllers(Unit *u) { + int r; + + assert(u); + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return; + + if (!unit_cgroup_delegate(u)) + return; + + /* For delegated units, the previous payload may have enabled controllers (e.g. "pids") in + * cgroup.subtree_control. These persist after the service stops and turn the cgroup into an + * "internal node", causing clone3(CLONE_INTO_CGROUP) to fail with EBUSY. Clear them now, right + * before the new start, so that resource control is preserved for lingering processes as long as + * possible. Ignore errors — if sub-cgroups still have live processes the write will fail, but so + * will the upcoming spawn. */ + r = cg_enable(u->manager->cgroup_supported, /* mask= */ 0, crt->cgroup_path, &crt->cgroup_enabled_mask); + if (r < 0) + log_unit_debug_errno(u, r, "Failed to disable controllers on cgroup %s, ignoring: %m", empty_to_root(crt->cgroup_path)); +} + void manager_invalidate_startup_units(Manager *m) { Unit *u; diff --git a/src/core/cgroup.h b/src/core/cgroup.h index 0cd290e92f25d..d9a6ded110214 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -6,6 +6,7 @@ #include "cpu-set-util.h" #include "firewall-util.h" #include "list.h" +#include "psi-util.h" typedef struct CGroupTasksMax { /* If scale == 0, just use value; otherwise, value / scale. @@ -95,14 +96,19 @@ typedef struct CGroupSocketBindItem { } CGroupSocketBindItem; typedef enum CGroupPressureWatch { - CGROUP_PRESSURE_WATCH_NO, /* → tells the service payload explicitly not to watch for memory pressure */ + CGROUP_PRESSURE_WATCH_NO, /* → tells the service payload explicitly not to watch for pressure */ CGROUP_PRESSURE_WATCH_YES, - CGROUP_PRESSURE_WATCH_AUTO, /* → on if memory account is on anyway for the unit, otherwise off */ - CGROUP_PRESSURE_WATCH_SKIP, /* → doesn't set up memory pressure watch, but also doesn't explicitly tell payload to avoid it */ + CGROUP_PRESSURE_WATCH_AUTO, /* → on if relevant accounting is on anyway for the unit, otherwise off */ + CGROUP_PRESSURE_WATCH_SKIP, /* → doesn't set up pressure watch, but also doesn't explicitly tell payload to avoid it */ _CGROUP_PRESSURE_WATCH_MAX, _CGROUP_PRESSURE_WATCH_INVALID = -EINVAL, } CGroupPressureWatch; +typedef struct CGroupPressure { + CGroupPressureWatch watch; + usec_t threshold_usec; +} CGroupPressure; + /* The user-supplied cgroup-related configuration options. This remains mostly immutable while the service * manager is running (except for an occasional SetProperties() configuration change), outside of reload * cycles. */ @@ -189,11 +195,8 @@ typedef struct CGroupContext { usec_t moom_mem_pressure_duration_usec; ManagedOOMPreference moom_preference; - /* Memory pressure logic */ - CGroupPressureWatch memory_pressure_watch; - usec_t memory_pressure_threshold_usec; - /* NB: For now we don't make the period configurable, not the type, nor do we allow multiple - * triggers, nor triggers for non-memory pressure. We might add that later. */ + /* Pressure logic */ + CGroupPressure pressure[_PRESSURE_RESOURCE_MAX]; NFTSetContext nft_set_context; @@ -353,11 +356,37 @@ void cgroup_context_free_io_device_latency(CGroupContext *c, CGroupIODeviceLaten void cgroup_context_remove_bpf_foreign_program(CGroupContext *c, CGroupBPFForeignProgram *p); void cgroup_context_remove_socket_bind(CGroupSocketBindItem **head); -static inline bool cgroup_context_want_memory_pressure(const CGroupContext *c) { +static inline bool cgroup_context_want_pressure(const CGroupContext *c, PressureResource t) { assert(c); + assert(t >= 0 && t < _PRESSURE_RESOURCE_MAX); + + if (c->pressure[t].watch == CGROUP_PRESSURE_WATCH_YES) + return true; + + if (c->pressure[t].watch != CGROUP_PRESSURE_WATCH_AUTO) + return false; + + switch (t) { - return c->memory_pressure_watch == CGROUP_PRESSURE_WATCH_YES || - (c->memory_pressure_watch == CGROUP_PRESSURE_WATCH_AUTO && c->memory_accounting); + case PRESSURE_MEMORY: + return c->memory_accounting; + + case PRESSURE_CPU: + return c->cpu_weight != CGROUP_WEIGHT_INVALID || + c->startup_cpu_weight != CGROUP_WEIGHT_INVALID || + c->cpu_quota_per_sec_usec != USEC_INFINITY; + + case PRESSURE_IO: + return c->io_accounting || + c->io_weight != CGROUP_WEIGHT_INVALID || + c->startup_io_weight != CGROUP_WEIGHT_INVALID || + c->io_device_weights || + c->io_device_latencies || + c->io_device_limits; + + default: + assert_not_reached(); + } } static inline bool cgroup_context_has_device_policy(const CGroupContext *c) { @@ -442,6 +471,8 @@ void unit_cgroup_catchup(Unit *u); bool unit_cgroup_delegate(Unit *u); +void unit_cgroup_disable_all_controllers(Unit *u); + int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name); int unit_cgroup_freezer_action(Unit *u, FreezerAction action); diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index dcb27f80f8c6a..927c133dd9e47 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -427,8 +427,12 @@ const sd_bus_vtable bus_cgroup_vtable[] = { SD_BUS_PROPERTY("SocketBindDeny", "a(iiqq)", property_get_socket_bind, offsetof(CGroupContext, socket_bind_deny), 0), SD_BUS_PROPERTY("RestrictNetworkInterfaces", "(bas)", property_get_restrict_network_interfaces, 0, 0), SD_BUS_PROPERTY("BindNetworkInterface", "s", NULL, offsetof(CGroupContext, bind_network_interface), 0), - SD_BUS_PROPERTY("MemoryPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(CGroupContext, memory_pressure_watch), 0), - SD_BUS_PROPERTY("MemoryPressureThresholdUSec", "t", bus_property_get_usec, offsetof(CGroupContext, memory_pressure_threshold_usec), 0), + SD_BUS_PROPERTY("MemoryPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(CGroupContext, pressure[PRESSURE_MEMORY].watch), 0), + SD_BUS_PROPERTY("MemoryPressureThresholdUSec", "t", bus_property_get_usec, offsetof(CGroupContext, pressure[PRESSURE_MEMORY].threshold_usec), 0), + SD_BUS_PROPERTY("CPUPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(CGroupContext, pressure[PRESSURE_CPU].watch), 0), + SD_BUS_PROPERTY("CPUPressureThresholdUSec", "t", bus_property_get_usec, offsetof(CGroupContext, pressure[PRESSURE_CPU].threshold_usec), 0), + SD_BUS_PROPERTY("IOPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(CGroupContext, pressure[PRESSURE_IO].watch), 0), + SD_BUS_PROPERTY("IOPressureThresholdUSec", "t", bus_property_get_usec, offsetof(CGroupContext, pressure[PRESSURE_IO].threshold_usec), 0), SD_BUS_PROPERTY("NFTSet", "a(iiss)", property_get_cgroup_nft_set, 0, 0), SD_BUS_PROPERTY("CoredumpReceive", "b", bus_property_get_bool, offsetof(CGroupContext, coredump_receive), 0), @@ -712,10 +716,13 @@ static int bus_cgroup_set_transient_property( return 1; - } else if (streq(name, "MemoryPressureWatch")) { + } else if (STR_IN_SET(name, "MemoryPressureWatch", "CPUPressureWatch", "IOPressureWatch")) { CGroupPressureWatch p; const char *t; + PressureResource pt = streq(name, "MemoryPressureWatch") ? PRESSURE_MEMORY : + streq(name, "CPUPressureWatch") ? PRESSURE_CPU : PRESSURE_IO; + r = sd_bus_message_read(message, "s", &t); if (r < 0) return r; @@ -729,26 +736,29 @@ static int bus_cgroup_set_transient_property( } if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - c->memory_pressure_watch = p; - unit_write_settingf(u, flags, name, "MemoryPressureWatch=%s", strempty(cgroup_pressure_watch_to_string(p))); + c->pressure[pt].watch = p; + unit_write_settingf(u, flags, name, "%s=%s", name, strempty(cgroup_pressure_watch_to_string(p))); } return 1; - } else if (streq(name, "MemoryPressureThresholdUSec")) { + } else if (STR_IN_SET(name, "MemoryPressureThresholdUSec", "CPUPressureThresholdUSec", "IOPressureThresholdUSec")) { uint64_t t; + PressureResource pt = streq(name, "MemoryPressureThresholdUSec") ? PRESSURE_MEMORY : + streq(name, "CPUPressureThresholdUSec") ? PRESSURE_CPU : PRESSURE_IO; + r = sd_bus_message_read(message, "t", &t); if (r < 0) return r; if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - c->memory_pressure_threshold_usec = t; + c->pressure[pt].threshold_usec = t; if (t == UINT64_MAX) - unit_write_setting(u, flags, name, "MemoryPressureThresholdUSec="); + unit_write_settingf(u, flags, name, "%s=", name); else - unit_write_settingf(u, flags, name, "MemoryPressureThresholdUSec=%" PRIu64, t); + unit_write_settingf(u, flags, name, "%s=%" PRIu64, name, t); } return 1; @@ -1010,9 +1020,9 @@ static int bus_cgroup_set_tasks_max_scale( *p = (CGroupTasksMax) { v, UINT32_MAX }; /* .scale is not 0, so this is interpreted as v/UINT32_MAX. */ unit_invalidate_cgroup(u, CGROUP_MASK_PIDS); - uint32_t scaled = DIV_ROUND_UP((uint64_t) v * 100U, (uint64_t) UINT32_MAX); - unit_write_settingf(u, flags, name, "%s=%" PRIu32 ".%" PRIu32 "%%", "TasksMax", - scaled / 10, scaled % 10); + int scaled = UINT32_SCALE_TO_PERMYRIAD(v); + unit_write_settingf(u, flags, name, "TasksMax=" PERMYRIAD_AS_PERCENT_FORMAT_STR, + PERMYRIAD_AS_PERCENT_FORMAT_VAL(scaled)); } return 1; @@ -1721,13 +1731,13 @@ int bus_cgroup_set_property( FORMAT_TIMESPAN(t, USEC_PER_SEC)); if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - c->memory_pressure_threshold_usec = t; - if (c->memory_pressure_threshold_usec == USEC_INFINITY) + c->moom_mem_pressure_duration_usec = t; + if (c->moom_mem_pressure_duration_usec == USEC_INFINITY) unit_write_setting(u, flags, name, "ManagedOOMMemoryPressureDurationSec="); else unit_write_settingf(u, flags, name, "ManagedOOMMemoryPressureDurationSec=%s", - FORMAT_TIMESPAN(c->memory_pressure_threshold_usec, 1)); + FORMAT_TIMESPAN(c->moom_mem_pressure_duration_usec, 1)); } if (c->moom_mem_pressure == MANAGED_OOM_KILL) diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 2bd7b1c07eea3..9843836eaf0df 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -2806,6 +2806,9 @@ int bus_exec_context_set_transient_property( return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Journal field is not valid UTF-8"); if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + if (c->n_log_extra_fields >= LOG_EXTRA_FIELDS_MAX) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Too many extra log fields."); + if (!GREEDY_REALLOC(c->log_extra_fields, c->n_log_extra_fields + 1)) return -ENOMEM; @@ -4052,7 +4055,7 @@ int bus_exec_context_set_transient_property( MountImage *mount_images = NULL; size_t n_mount_images = 0; - CLEANUP_ARRAY(mount_images, n_mount_images, mount_image_free_many); + CLEANUP_ARRAY(mount_images, n_mount_images, mount_image_free_array); r = sd_bus_message_enter_container(message, 'a', "(ssba(ss))"); if (r < 0) @@ -4124,7 +4127,7 @@ int bus_exec_context_set_transient_property( if (!UNIT_WRITE_FLAGS_NOOP(flags)) { if (n_mount_images == 0) { - mount_image_free_many(c->mount_images, c->n_mount_images); + mount_image_free_array(c->mount_images, c->n_mount_images); c->mount_images = NULL; c->n_mount_images = 0; @@ -4155,7 +4158,7 @@ int bus_exec_context_set_transient_property( MountImage *extension_images = NULL; size_t n_extension_images = 0; - CLEANUP_ARRAY(extension_images, n_extension_images, mount_image_free_many); + CLEANUP_ARRAY(extension_images, n_extension_images, mount_image_free_array); r = sd_bus_message_enter_container(message, 'a', "(sba(ss))"); if (r < 0) @@ -4217,7 +4220,7 @@ int bus_exec_context_set_transient_property( if (!UNIT_WRITE_FLAGS_NOOP(flags)) { if (n_extension_images == 0) { - mount_image_free_many(c->extension_images, c->n_extension_images); + mount_image_free_array(c->extension_images, c->n_extension_images); c->extension_images = NULL; c->n_extension_images = 0; diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 5e02d189072e2..1bc73e7b434c9 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -387,13 +387,16 @@ static int property_set_pretimeout_watchdog_governor( sd_bus_error *reterr_error) { Manager *m = ASSERT_PTR(userdata); - char *governor; + const char *governor; int r; r = sd_bus_message_read(value, "s", &governor); if (r < 0) return r; - if (!string_is_safe(governor)) + + if (isempty(governor)) + governor = NULL; + else if (!string_is_safe(governor, /* flags= */ 0)) return -EINVAL; return manager_override_watchdog_pretimeout_governor(m, governor); @@ -1265,10 +1268,6 @@ static int list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_e /* Anyone can call this method */ - r = mac_selinux_access_check(message, "status", reterr_error); - if (r < 0) - return r; - r = sd_bus_message_new_method_return(message, &reply); if (r < 0) return r; @@ -1281,6 +1280,10 @@ static int list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_e if (k != u->id) continue; + r = mac_selinux_unit_access_check(u, message, "status", /* reterr_error= */ NULL); + if (r < 0) + continue; /* silently skip units the caller is not allowed to see */ + if (!unit_passes_filter(u, states, patterns)) continue; @@ -1543,17 +1546,20 @@ static int method_refuse_snapshot(sd_bus_message *message, void *userdata, sd_bu static void log_caller(sd_bus_message *message, Manager *manager, const char *method) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r; assert(message); assert(manager); assert(method); - if (sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID|SD_BUS_CREDS_PIDFD|SD_BUS_CREDS_AUGMENT, &creds) < 0) - return; - - /* We need at least the PID, otherwise there's nothing to log, the rest is optional. */ - if (bus_creds_get_pidref(creds, &pidref) < 0) - return; + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID|SD_BUS_CREDS_PIDFD|SD_BUS_CREDS_AUGMENT, &creds); + if (r < 0) + log_debug_errno(r, "Failed to get dbus sender creds, ignoring: %m"); + else { + r = bus_creds_get_pidref(creds, &pidref); + if (r < 0) + log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); + } manager_log_caller(manager, &pidref, method); } @@ -1690,6 +1696,7 @@ static int method_soft_reboot(sd_bus_message *message, void *userdata, sd_bus_er return sd_bus_error_set(reterr_error, SD_BUS_ERROR_NOT_SUPPORTED, "Soft reboot is only supported by system manager."); + /* Keep the checks in sync with varlink-manager.c:vl_method_soft_reboot_manager() */ r = mac_selinux_access_check(message, "reboot", reterr_error); if (r < 0) return r; @@ -2976,14 +2983,19 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_PROPERTY("DefaultLimitRTTIME", "t", bus_property_get_rlimit, offsetof(Manager, defaults.rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultLimitRTTIMESoft", "t", bus_property_get_rlimit, offsetof(Manager, defaults.rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultTasksMax", "t", bus_property_get_tasks_max, offsetof(Manager, defaults.tasks_max), 0), - SD_BUS_PROPERTY("DefaultMemoryPressureThresholdUSec", "t", bus_property_get_usec, offsetof(Manager, defaults.memory_pressure_threshold_usec), 0), - SD_BUS_PROPERTY("DefaultMemoryPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(Manager, defaults.memory_pressure_watch), 0), + SD_BUS_PROPERTY("DefaultMemoryPressureThresholdUSec", "t", bus_property_get_usec, offsetof(Manager, defaults.pressure[PRESSURE_MEMORY].threshold_usec), 0), + SD_BUS_PROPERTY("DefaultMemoryPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(Manager, defaults.pressure[PRESSURE_MEMORY].watch), 0), + SD_BUS_PROPERTY("DefaultCPUPressureThresholdUSec", "t", bus_property_get_usec, offsetof(Manager, defaults.pressure[PRESSURE_CPU].threshold_usec), 0), + SD_BUS_PROPERTY("DefaultCPUPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(Manager, defaults.pressure[PRESSURE_CPU].watch), 0), + SD_BUS_PROPERTY("DefaultIOPressureThresholdUSec", "t", bus_property_get_usec, offsetof(Manager, defaults.pressure[PRESSURE_IO].threshold_usec), 0), + SD_BUS_PROPERTY("DefaultIOPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(Manager, defaults.pressure[PRESSURE_IO].watch), 0), SD_BUS_PROPERTY("TimerSlackNSec", "t", property_get_timer_slack_nsec, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultOOMPolicy", "s", bus_property_get_oom_policy, offsetof(Manager, defaults.oom_policy), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultOOMScoreAdjust", "i", property_get_oom_score_adjust, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultRestrictSUIDSGID", "b", bus_property_get_bool, offsetof(Manager, defaults.restrict_suid_sgid), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CtrlAltDelBurstAction", "s", bus_property_get_emergency_action, offsetof(Manager, cad_burst_action), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SoftRebootsCount", "u", bus_property_get_unsigned, offsetof(Manager, soft_reboots_count), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultMemoryZSwapWriteback", "b", bus_property_get_bool, offsetof(Manager, defaults.memory_zswap_writeback), SD_BUS_VTABLE_PROPERTY_CONST), /* deprecated cgroup v1 property */ SD_BUS_PROPERTY("DefaultBlockIOAccounting", "b", bus_property_get_bool_false, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_DEPRECATED|SD_BUS_VTABLE_HIDDEN), diff --git a/src/core/dbus.c b/src/core/dbus.c index 30d265ec6cef1..dba79b860266f 100644 --- a/src/core/dbus.c +++ b/src/core/dbus.c @@ -39,7 +39,7 @@ #include "path-util.h" #include "pidref.h" #include "process-util.h" -#include "selinux-access.h" +#include "selinux-access.h" /* IWYU pragma: keep */ #include "serialize.h" #include "set.h" #include "special.h" @@ -229,6 +229,7 @@ static int find_unit(Manager *m, sd_bus *bus, const char *path, Unit **unit, sd_ assert(m); assert(bus); assert(path); + assert(unit); if (streq(path, "/org/freedesktop/systemd1/unit/self")) { _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; diff --git a/src/core/emergency-action.c b/src/core/emergency-action.c index 439228c8995ff..b9a5c66ebff87 100644 --- a/src/core/emergency-action.c +++ b/src/core/emergency-action.c @@ -240,6 +240,8 @@ int parse_emergency_action( EmergencyAction x; + assert(ret); + x = emergency_action_from_string(value); if (x < 0) return -EINVAL; diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index b91a964cdd6ab..8c8502ffab396 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -1086,15 +1086,12 @@ static int enforce_user( #if HAVE_PAM -static void pam_response_free_array(struct pam_response *responses, size_t n_responses) { - assert(responses || n_responses == 0); - - FOREACH_ARRAY(resp, responses, n_responses) - erase_and_free(resp->resp); - - free(responses); +static void pam_response_done(struct pam_response *response) { + erase_and_free(ASSERT_PTR(response)->resp); } +static DEFINE_ARRAY_FREE_FUNC(pam_response_free_array, struct pam_response, pam_response_done); + typedef struct AskPasswordConvData { const ExecContext *context; const ExecParameters *params; @@ -1362,9 +1359,9 @@ static int setup_pam( * parent process will exec() the actual daemon. We do things this way to ensure that the main PID of * the daemon is the one we initially fork()ed. */ - r = dlopen_libpam(); + r = dlopen_libpam(LOG_ERR); if (r < 0) - return log_error_errno(r, "PAM support not available: %m"); + return r; r = barrier_create(&barrier); if (r < 0) @@ -1628,7 +1625,7 @@ static bool seccomp_allows_drop_privileges(const ExecContext *c) { assert(c); /* No libseccomp, all is fine */ - if (dlopen_libseccomp() < 0) + if (dlopen_libseccomp(LOG_DEBUG) < 0) return true; /* No syscall filter, we are allowed to drop privileges */ @@ -1930,7 +1927,7 @@ static int apply_restrict_filesystems(const ExecContext *c, const ExecParameters } /* We are in a new binary, so dl-open again */ - r = dlopen_bpf(); + r = dlopen_bpf(LOG_DEBUG); if (r < 0) return r; @@ -2034,7 +2031,7 @@ static int build_environment( const char *shell, dev_t journal_stream_dev, ino_t journal_stream_ino, - const char *memory_pressure_path, + char *const *pressure_path, bool needs_sandboxing, char ***ret) { @@ -2215,25 +2212,38 @@ static int build_environment( if (r < 0) return r; - if (memory_pressure_path) { - r = strv_extend_joined_with_size(&e, &n, "MEMORY_PRESSURE_WATCH=", memory_pressure_path); + for (PressureResource t = 0; t < _PRESSURE_RESOURCE_MAX; t++) { + if (!pressure_path[t]) + continue; + + const PressureResourceInfo *info = pressure_resource_get_info(t); + + _cleanup_free_ char *env_watch = strjoin(info->env_watch, "="); + if (!env_watch) + return -ENOMEM; + + r = strv_extend_joined_with_size(&e, &n, env_watch, pressure_path[t]); if (r < 0) return r; - if (!path_equal(memory_pressure_path, "/dev/null")) { + if (!path_equal(pressure_path[t], "/dev/null")) { _cleanup_free_ char *b = NULL, *x = NULL; if (asprintf(&b, "%s " USEC_FMT " " USEC_FMT, - MEMORY_PRESSURE_DEFAULT_TYPE, - cgroup_context->memory_pressure_threshold_usec == USEC_INFINITY ? MEMORY_PRESSURE_DEFAULT_THRESHOLD_USEC : - CLAMP(cgroup_context->memory_pressure_threshold_usec, 1U, MEMORY_PRESSURE_DEFAULT_WINDOW_USEC), - MEMORY_PRESSURE_DEFAULT_WINDOW_USEC) < 0) + PRESSURE_DEFAULT_TYPE, + cgroup_context->pressure[t].threshold_usec == USEC_INFINITY ? PRESSURE_DEFAULT_THRESHOLD_USEC : + CLAMP(cgroup_context->pressure[t].threshold_usec, 1U, PRESSURE_DEFAULT_WINDOW_USEC), + PRESSURE_DEFAULT_WINDOW_USEC) < 0) return -ENOMEM; if (base64mem(b, strlen(b) + 1, &x) < 0) return -ENOMEM; - r = strv_extend_joined_with_size(&e, &n, "MEMORY_PRESSURE_WRITE=", x); + _cleanup_free_ char *env_write = strjoin(info->env_write, "="); + if (!env_write) + return -ENOMEM; + + r = strv_extend_joined_with_size(&e, &n, env_write, x); if (r < 0) return r; } @@ -3855,7 +3865,7 @@ static int apply_mount_namespace( const ExecParameters *params, const ExecRuntime *runtime, const PinnedResource *rootfs, - const char *memory_pressure_path, + char *const *pressure_path, bool needs_sandboxing, uid_t exec_directory_uid, gid_t exec_directory_gid, @@ -3887,16 +3897,28 @@ static int apply_mount_namespace( if (r < 0) return r; - /* We need to make the pressure path writable even if /sys/fs/cgroups is made read-only, as the - * service will need to write to it in order to start the notifications. */ - if (exec_is_cgroup_mount_read_only(context) && memory_pressure_path && !streq(memory_pressure_path, "/dev/null")) { + /* We need to make the pressure paths writable even if /sys/fs/cgroups is made read-only, as the + * service will need to write to them in order to start the notifications. */ + bool need_pressure_rw = false; + for (PressureResource t = 0; t < _PRESSURE_RESOURCE_MAX; t++) + if (pressure_path[t] && !streq(pressure_path[t], "/dev/null")) { + need_pressure_rw = true; + break; + } + + if (exec_is_cgroup_mount_read_only(context) && need_pressure_rw) { read_write_paths_cleanup = strv_copy(context->read_write_paths); if (!read_write_paths_cleanup) return -ENOMEM; - r = strv_extend(&read_write_paths_cleanup, memory_pressure_path); - if (r < 0) - return r; + for (PressureResource t = 0; t < _PRESSURE_RESOURCE_MAX; t++) { + if (!pressure_path[t] || streq(pressure_path[t], "/dev/null")) + continue; + + r = strv_extend(&read_write_paths_cleanup, pressure_path[t]); + if (r < 0) + return r; + } read_write_paths = read_write_paths_cleanup; } else @@ -4130,7 +4152,7 @@ static int apply_working_directory( r = chase(wd, runtime->ephemeral_copy ?: context->root_directory, - CHASE_PREFIX_ROOT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, + CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS, /* ret_path= */ NULL, &dfd); if (r >= 0) @@ -4689,7 +4711,7 @@ static int setup_delegated_namespaces( const ExecRuntime *runtime, const PinnedResource *rootfs, bool delegate, - const char *memory_pressure_path, + char *const *pressure_path, uid_t uid, gid_t gid, const ExecCommand *command, @@ -4820,7 +4842,7 @@ static int setup_delegated_namespaces( params, runtime, rootfs, - memory_pressure_path, + pressure_path, needs_sandboxing, uid, gid, @@ -5146,6 +5168,10 @@ static int setup_term_environment(const ExecContext *context, char ***env) { return strv_env_replace_strdup(env, "TERM=" FALLBACK_TERM); } +static inline void free_pressure_paths(char *(*p)[_PRESSURE_RESOURCE_MAX]) { + free_many_charp(*p, _PRESSURE_RESOURCE_MAX); +} + int exec_invoke( const ExecCommand *command, const ExecContext *context, @@ -5157,7 +5183,8 @@ int exec_invoke( _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **joined_exec_search_path = NULL, **accum_env = NULL; int r; const char *username = NULL, *groupname = NULL; - _cleanup_free_ char *home_buffer = NULL, *memory_pressure_path = NULL, *own_user = NULL; + _cleanup_free_ char *home_buffer = NULL, *own_user = NULL; + _cleanup_(free_pressure_paths) char *pressure_path[_PRESSURE_RESOURCE_MAX] = {}; const char *pwent_home = NULL, *shell = NULL; dev_t journal_stream_dev = 0; ino_t journal_stream_ino = 0; @@ -5753,36 +5780,44 @@ int exec_invoke( } if (is_pressure_supported() > 0) { - if (cgroup_context_want_memory_pressure(cgroup_context)) { - r = cg_get_path(params->cgroup_path, "memory.pressure", &memory_pressure_path); - if (r < 0) { - *exit_status = EXIT_MEMORY; - return log_oom(); - } + for (PressureResource t = 0; t < _PRESSURE_RESOURCE_MAX; t++) { + if (cgroup_context_want_pressure(cgroup_context, t)) { + _cleanup_free_ char *pressure_file = strjoin(pressure_resource_to_string(t), ".pressure"); + if (!pressure_file) { + *exit_status = EXIT_MEMORY; + return log_oom(); + } - r = chmod_and_chown(memory_pressure_path, 0644, uid, gid); - if (r < 0) { - log_full_errno(r == -ENOENT || ERRNO_IS_PRIVILEGE(r) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to adjust ownership of '%s', ignoring: %m", memory_pressure_path); - memory_pressure_path = mfree(memory_pressure_path); - } - /* First we use the current cgroup path to chmod and chown the memory pressure path, then pass the path relative - * to the cgroup namespace to environment variables and mounts. If chown/chmod fails, we should not pass memory - * pressure path environment variable or read-write mount to the unit. This is why we check if - * memory_pressure_path != NULL in the conditional below. */ - if (memory_pressure_path && needs_sandboxing && exec_needs_cgroup_namespace(context)) { - memory_pressure_path = mfree(memory_pressure_path); - r = cg_get_path("/", "memory.pressure", &memory_pressure_path); + r = cg_get_path(params->cgroup_path, pressure_file, &pressure_path[t]); if (r < 0) { *exit_status = EXIT_MEMORY; return log_oom(); } - } - } else if (cgroup_context->memory_pressure_watch == CGROUP_PRESSURE_WATCH_NO) { - memory_pressure_path = strdup("/dev/null"); /* /dev/null is explicit indicator for turning of memory pressure watch */ - if (!memory_pressure_path) { - *exit_status = EXIT_MEMORY; - return log_oom(); + + r = chmod_and_chown(pressure_path[t], 0644, uid, gid); + if (r < 0) { + log_full_errno(r == -ENOENT || ERRNO_IS_PRIVILEGE(r) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to adjust ownership of '%s', ignoring: %m", pressure_path[t]); + pressure_path[t] = mfree(pressure_path[t]); + } + /* First we use the current cgroup path to chmod and chown the pressure path, then pass the + * path relative to the cgroup namespace to environment variables and mounts. If chown/chmod + * fails, we should not pass pressure path environment variable or read-write mount to the + * unit. This is why we check if pressure_path[t] != NULL in the conditional below. */ + if (pressure_path[t] && needs_sandboxing && exec_needs_cgroup_namespace(context)) { + pressure_path[t] = mfree(pressure_path[t]); + r = cg_get_path("/", pressure_file, &pressure_path[t]); + if (r < 0) { + *exit_status = EXIT_MEMORY; + return log_oom(); + } + } + } else if (cgroup_context->pressure[t].watch == CGROUP_PRESSURE_WATCH_NO) { + pressure_path[t] = strdup("/dev/null"); /* /dev/null is explicit indicator for turning off pressure watch */ + if (!pressure_path[t]) { + *exit_status = EXIT_MEMORY; + return log_oom(); + } } } } @@ -5829,7 +5864,7 @@ int exec_invoke( shell, journal_stream_dev, journal_stream_ino, - memory_pressure_path, + pressure_path, needs_sandboxing, &our_env); if (r < 0) { @@ -5991,10 +6026,10 @@ int exec_invoke( } /* Load a bunch of libraries we'll possibly need later, before we turn off dlopen() */ - (void) dlopen_bpf(); - (void) dlopen_cryptsetup(); - (void) dlopen_libmount(); - (void) dlopen_libseccomp(); + (void) dlopen_bpf(LOG_DEBUG); + (void) dlopen_cryptsetup(LOG_DEBUG); + (void) dlopen_libmount(LOG_DEBUG); + (void) dlopen_libseccomp(LOG_DEBUG); /* Let's now disable further dlopen()ing of libraries, since we are about to do namespace * shenanigans, and do not want to mix resources from host and namespace */ @@ -6047,7 +6082,7 @@ int exec_invoke( runtime, &rootfs, /* delegate= */ false, - memory_pressure_path, + pressure_path, uid, gid, command, @@ -6144,7 +6179,7 @@ int exec_invoke( runtime, &rootfs, /* delegate= */ true, - memory_pressure_path, + pressure_path, uid, gid, command, diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index 560df952874ea..143cfe6286b91 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -30,6 +30,7 @@ #include "string-util.h" #include "strv.h" #include "time-util.h" +#include "unit.h" static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) { _cleanup_free_ char *disable_controllers_str = NULL, *delegate_controllers_str = NULL, @@ -278,7 +279,15 @@ static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) { if (r < 0) return r; - r = serialize_item(f, "exec-cgroup-context-memory-pressure-watch", cgroup_pressure_watch_to_string(c->memory_pressure_watch)); + r = serialize_item(f, "exec-cgroup-context-memory-pressure-watch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_MEMORY].watch)); + if (r < 0) + return r; + + r = serialize_item(f, "exec-cgroup-context-cpu-pressure-watch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_CPU].watch)); + if (r < 0) + return r; + + r = serialize_item(f, "exec-cgroup-context-io-pressure-watch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_IO].watch)); if (r < 0) return r; @@ -286,8 +295,20 @@ static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) { if (r < 0) return r; - if (c->memory_pressure_threshold_usec != USEC_INFINITY) { - r = serialize_usec(f, "exec-cgroup-context-memory-pressure-threshold-usec", c->memory_pressure_threshold_usec); + if (c->pressure[PRESSURE_MEMORY].threshold_usec != USEC_INFINITY) { + r = serialize_usec(f, "exec-cgroup-context-memory-pressure-threshold-usec", c->pressure[PRESSURE_MEMORY].threshold_usec); + if (r < 0) + return r; + } + + if (c->pressure[PRESSURE_CPU].threshold_usec != USEC_INFINITY) { + r = serialize_usec(f, "exec-cgroup-context-cpu-pressure-threshold-usec", c->pressure[PRESSURE_CPU].threshold_usec); + if (r < 0) + return r; + } + + if (c->pressure[PRESSURE_IO].threshold_usec != USEC_INFINITY) { + r = serialize_usec(f, "exec-cgroup-context-io-pressure-threshold-usec", c->pressure[PRESSURE_IO].threshold_usec); if (r < 0) return r; } @@ -620,15 +641,31 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { if (r < 0) return r; } else if ((val = startswith(l, "exec-cgroup-context-memory-pressure-watch="))) { - c->memory_pressure_watch = cgroup_pressure_watch_from_string(val); - if (c->memory_pressure_watch < 0) + c->pressure[PRESSURE_MEMORY].watch = cgroup_pressure_watch_from_string(val); + if (c->pressure[PRESSURE_MEMORY].watch < 0) + return -EINVAL; + } else if ((val = startswith(l, "exec-cgroup-context-cpu-pressure-watch="))) { + c->pressure[PRESSURE_CPU].watch = cgroup_pressure_watch_from_string(val); + if (c->pressure[PRESSURE_CPU].watch < 0) + return -EINVAL; + } else if ((val = startswith(l, "exec-cgroup-context-io-pressure-watch="))) { + c->pressure[PRESSURE_IO].watch = cgroup_pressure_watch_from_string(val); + if (c->pressure[PRESSURE_IO].watch < 0) return -EINVAL; } else if ((val = startswith(l, "exec-cgroup-context-delegate-subgroup="))) { r = free_and_strdup(&c->delegate_subgroup, val); if (r < 0) return r; } else if ((val = startswith(l, "exec-cgroup-context-memory-pressure-threshold-usec="))) { - r = deserialize_usec(val, &c->memory_pressure_threshold_usec); + r = deserialize_usec(val, &c->pressure[PRESSURE_MEMORY].threshold_usec); + if (r < 0) + return r; + } else if ((val = startswith(l, "exec-cgroup-context-cpu-pressure-threshold-usec="))) { + r = deserialize_usec(val, &c->pressure[PRESSURE_CPU].threshold_usec); + if (r < 0) + return r; + } else if ((val = startswith(l, "exec-cgroup-context-io-pressure-threshold-usec="))) { + r = deserialize_usec(val, &c->pressure[PRESSURE_IO].threshold_usec); if (r < 0) return r; } else if ((val = startswith(l, "exec-cgroup-context-device-allow="))) { @@ -3104,12 +3141,19 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { if (r < 0) return r; } else if ((val = startswith(l, "exec-context-log-extra-fields="))) { + if (c->n_log_extra_fields >= LOG_EXTRA_FIELDS_MAX) { + log_warning("Too many extra log fields, ignoring."); + continue; + } + if (!GREEDY_REALLOC(c->log_extra_fields, c->n_log_extra_fields + 1)) return log_oom_debug(); - c->log_extra_fields[c->n_log_extra_fields++].iov_base = strdup(val); - if (!c->log_extra_fields[c->n_log_extra_fields-1].iov_base) + char *field = strdup(val); + if (!field) return log_oom_debug(); + + c->log_extra_fields[c->n_log_extra_fields++] = IOVEC_MAKE_STRING(field); } else if ((val = startswith(l, "exec-context-log-namespace="))) { r = free_and_strdup(&c->log_namespace, val); if (r < 0) diff --git a/src/core/execute.c b/src/core/execute.c index 80c22623be797..cfd63fe3038fa 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -113,7 +113,7 @@ int exec_context_apply_tty_size( if (rows == UINT_MAX && cols == UINT_MAX && exec_context_shall_ansi_seq_reset(context) && isatty_safe(input_fd)) { - r = terminal_get_size_by_dsr(input_fd, output_fd, &rows, &cols); + r = terminal_get_size(input_fd, output_fd, &rows, &cols, /* try_dsr= */ true, /* try_csi18= */ false); if (r < 0) log_debug_errno(r, "Failed to get terminal size by DSR, ignoring: %m"); } @@ -696,10 +696,10 @@ void exec_context_done(ExecContext *c) { bind_mount_free_many(c->bind_mounts, c->n_bind_mounts); c->bind_mounts = NULL; c->n_bind_mounts = 0; - mount_image_free_many(c->mount_images, c->n_mount_images); + mount_image_free_array(c->mount_images, c->n_mount_images); c->mount_images = NULL; c->n_mount_images = 0; - mount_image_free_many(c->extension_images, c->n_extension_images); + mount_image_free_array(c->extension_images, c->n_extension_images); c->extension_images = NULL; c->n_extension_images = 0; c->extension_directories = strv_free(c->extension_directories); @@ -1491,7 +1491,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { fputc('~', f); #if HAVE_SECCOMP - if (dlopen_libseccomp() >= 0) { + if (dlopen_libseccomp(LOG_DEBUG) >= 0) { void *id, *val; bool first = true; HASHMAP_FOREACH_KEY(val, id, c->syscall_filter) { @@ -1910,7 +1910,7 @@ char** exec_context_get_syscall_filter(const ExecContext *c) { assert(c); #if HAVE_SECCOMP - if (dlopen_libseccomp() < 0) + if (dlopen_libseccomp(LOG_DEBUG) < 0) return strv_new(NULL); void *id, *val; @@ -1930,14 +1930,12 @@ char** exec_context_get_syscall_filter(const ExecContext *c) { if (num >= 0) { e = seccomp_errno_or_action_to_string(num); - if (e) { + if (e) s = strjoin(name, ":", e); - if (!s) - return NULL; - } else { - if (asprintf(&s, "%s:%d", name, num) < 0) - return NULL; - } + else + s = asprintf_safe("%s:%d", name, num); + if (!s) + return NULL; } else s = TAKE_PTR(name); @@ -1981,7 +1979,7 @@ char** exec_context_get_syscall_log(const ExecContext *c) { assert(c); #if HAVE_SECCOMP - if (dlopen_libseccomp() < 0) + if (dlopen_libseccomp(LOG_DEBUG) < 0) return strv_new(NULL); void *id, *val; @@ -2374,6 +2372,8 @@ static int exec_shared_runtime_add( assert(m); assert(id); + assert(tmp_dir); + assert(var_tmp_dir); /* tmp_dir, var_tmp_dir, {net,ipc}ns_storage_socket fds are donated on success */ diff --git a/src/core/executor.c b/src/core/executor.c index 9ca15cf35155f..94bb481db43a4 100644 --- a/src/core/executor.c +++ b/src/core/executor.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-messages.h" @@ -17,10 +16,10 @@ #include "exit-status.h" #include "fd-util.h" #include "fdset.h" -#include "fileio.h" -#include "getopt-defs.h" +#include "format-table.h" #include "label-util.h" #include "log.h" +#include "options.h" #include "parse-util.h" #include "pretty-print.h" #include "selinux-util.h" @@ -32,142 +31,100 @@ STATIC_DESTRUCTOR_REGISTER(arg_serialization, fclosep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...]\n\n" - "%sSandbox and execute processes.%s\n\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - " --log-target=TARGET Set log target (console, journal,\n" - " journal-or-kmsg,\n" - " kmsg, null)\n" - " --log-level=LEVEL Set log level (debug, info, notice,\n" - " warning, err, crit,\n" - " alert, emerg)\n" - " --log-color=BOOL Highlight important messages\n" - " --log-location=BOOL Include code location in messages\n" - " --log-time=BOOL Prefix messages with current time\n" - " --deserialize=FD Deserialize process config from FD\n" - "\nSee the %s for details.\n", + "%sSandbox and execute processes.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - COMMON_GETOPT_ARGS, - ARG_VERSION, - ARG_DESERIALIZE, - }; - - static const struct option options[] = { - { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, - { "log-target", required_argument, NULL, ARG_LOG_TARGET }, - { "log-color", required_argument, NULL, ARG_LOG_COLOR }, - { "log-location", required_argument, NULL, ARG_LOG_LOCATION }, - { "log-time", required_argument, NULL, ARG_LOG_TIME }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "deserialize", required_argument, NULL, ARG_DESERIALIZE }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_LOG_LEVEL: - r = log_set_max_level_from_string(optarg); + OPTION_COMMON_LOG_LEVEL: + r = log_set_max_level_from_string(arg); if (r < 0) - return log_error_errno(r, "Failed to parse log level \"%s\": %m", optarg); - + return log_error_errno(r, "Failed to parse log level \"%s\": %m", arg); break; - case ARG_LOG_TARGET: - r = log_set_target_from_string(optarg); + OPTION_COMMON_LOG_TARGET: + r = log_set_target_from_string(arg); if (r < 0) - return log_error_errno(r, "Failed to parse log target \"%s\": %m", optarg); - + return log_error_errno(r, "Failed to parse log target \"%s\": %m", arg); break; - case ARG_LOG_COLOR: - r = log_show_color_from_string(optarg); + OPTION_COMMON_LOG_COLOR: + r = log_show_color_from_string(arg); if (r < 0) - return log_error_errno( - r, - "Failed to parse log color setting \"%s\": %m", - optarg); - + return log_error_errno(r, "Failed to parse log color setting \"%s\": %m", arg); break; - case ARG_LOG_LOCATION: - r = log_show_location_from_string(optarg); + OPTION_COMMON_LOG_LOCATION: + r = log_show_location_from_string(arg); if (r < 0) - return log_error_errno( - r, - "Failed to parse log location setting \"%s\": %m", - optarg); - + return log_error_errno(r, "Failed to parse log location setting \"%s\": %m", arg); break; - case ARG_LOG_TIME: - r = log_show_time_from_string(optarg); + OPTION_COMMON_LOG_TIME: + r = log_show_time_from_string(arg); if (r < 0) - return log_error_errno( - r, - "Failed to parse log time setting \"%s\": %m", - optarg); - + return log_error_errno(r, "Failed to parse log time setting \"%s\": %m", arg); break; - case ARG_DESERIALIZE: { - _cleanup_close_ int fd = -EBADF; - FILE *f; - - fd = parse_fd(optarg); + OPTION_LONG("deserialize", "FD", "Deserialize process config from FD"): { + int fd = parse_fd(arg); if (fd < 0) - return log_error_errno(fd, - "Failed to parse serialization fd \"%s\": %m", - optarg); + return log_error_errno(fd, "Failed to parse serialization fd \"%s\": %m", arg); + /* Set O_CLOEXEC and as a side effect, verify that the fd is valid. */ r = fd_cloexec(fd, /* cloexec= */ true); + if (r == -EBADF) + return log_error_errno(r, "Serialization fd %d is not valid.", fd); if (r < 0) - return log_error_errno(r, - "Failed to set serialization fd %d to close-on-exec: %m", + return log_error_errno(r, "Failed to set serialization fd %d to close-on-exec: %m", fd); - f = take_fdopen(&fd, "r"); + FILE *f = fdopen(fd, "r"); if (!f) return log_error_errno(errno, "Failed to open serialization fd %d: %m", fd); safe_fclose(arg_serialization); arg_serialization = f; - break; } - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_serialization) @@ -199,7 +156,6 @@ static int run(int argc, char *argv[]) { cgroup_context_init(&cgroup_context); /* We might be starting the journal itself, we'll be told by the caller what to do */ - log_set_always_reopen_console(true); log_set_prohibit_ipc(true); log_setup(); @@ -208,6 +164,7 @@ static int run(int argc, char *argv[]) { return r; /* Now that we know the intended log target, allow IPC and open the final log target. */ + log_set_always_reopen_console(true); log_set_prohibit_ipc(false); log_open(); diff --git a/src/core/job.c b/src/core/job.c index 1cac09bd06607..b4578a946c6a3 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -41,6 +41,8 @@ Job* job_new_raw(Unit *unit) { .manager = unit->manager, .unit = unit, .type = _JOB_TYPE_INVALID, + .state = JOB_WAITING, + .result = _JOB_RESULT_INVALID, }; return j; @@ -138,17 +140,18 @@ static void job_set_state(Job *j, JobState state) { if (j->state == state) return; + JobState old_state = j->state; j->state = state; if (!j->installed) return; if (j->state == JOB_RUNNING) + /* This job changed into running, count up */ j->manager->n_running_jobs++; - else { - assert(j->state == JOB_WAITING); + else if (old_state == JOB_RUNNING) { + /* This job changed away from running into another state, count down. */ assert(j->manager->n_running_jobs > 0); - j->manager->n_running_jobs--; if (j->manager->n_running_jobs <= 0) @@ -510,6 +513,8 @@ JobType job_type_collapse(JobType t, Unit *u) { int job_type_merge_and_collapse(JobType *a, JobType b, Unit *u) { JobType t; + assert(a); + t = job_type_lookup_merge(*a, b); if (t < 0) return -EEXIST; @@ -1014,6 +1019,8 @@ int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool alr j->result = result; + job_set_state(j, JOB_FINISHED); + log_unit_debug(u, "Job %" PRIu32 " %s/%s finished, result=%s", j->id, u->id, job_type_to_string(t), job_result_to_string(result)); @@ -1523,6 +1530,11 @@ void job_add_to_gc_queue(Job *j) { } static int job_compare_id(Job * const *a, Job * const *b) { + /* This is called from qsort()s inner loops. Correctly implemented qsort will never pass NULL so we + just suppress the check via POINTER_MAY_BE_NULL instead of assert() to avoid the runtime cost. */ + POINTER_MAY_BE_NULL(a); + POINTER_MAY_BE_NULL(b); + return CMP((*a)->id, (*b)->id); } @@ -1637,8 +1649,9 @@ int job_get_after(Job *j, Job*** ret) { } static const char* const job_state_table[_JOB_STATE_MAX] = { - [JOB_WAITING] = "waiting", - [JOB_RUNNING] = "running", + [JOB_WAITING] = "waiting", + [JOB_RUNNING] = "running", + [JOB_FINISHED] = "finished", }; DEFINE_STRING_TABLE_LOOKUP(job_state, JobState); diff --git a/src/core/job.h b/src/core/job.h index d8aa6ce17c53a..a8eca99505d51 100644 --- a/src/core/job.h +++ b/src/core/job.h @@ -52,6 +52,7 @@ enum JobType { typedef enum JobState { JOB_WAITING, JOB_RUNNING, + JOB_FINISHED, _JOB_STATE_MAX, _JOB_STATE_INVALID = -EINVAL, } JobState; @@ -106,7 +107,6 @@ typedef struct Job { JobType type; JobState state; - JobResult result; unsigned run_queue_idx; diff --git a/src/core/kmod-setup.c b/src/core/kmod-setup.c index 499e09443ff65..7d0d1d9b67373 100644 --- a/src/core/kmod-setup.c +++ b/src/core/kmod-setup.c @@ -94,6 +94,10 @@ static bool in_vmware(void) { static bool in_hyperv(void) { return detect_vm() == VIRTUALIZATION_MICROSOFT; } + +static bool may_have_vsock_loopback(void) { + return may_have_virtio() || in_vmware(); +} #endif int kmod_setup(void) { @@ -107,23 +111,25 @@ int kmod_setup(void) { } kmod_table[] = { /* This one we need to load explicitly, since auto-loading on use doesn't work * before udev created the ghost device nodes, and we need it earlier than that. */ - { "autofs4", "/sys/class/misc/autofs", true, false, NULL }, + { "autofs4", "/sys/class/misc/autofs", true, false, NULL }, /* This one we need to load explicitly, since auto-loading of IPv6 is not done when * we try to configure ::1 on the loopback device. */ - { "ipv6", "/sys/module/ipv6", false, true, NULL }, + { "ipv6", "/sys/module/ipv6", false, true, NULL }, /* virtio_rng would be loaded by udev later, but real entropy might be needed very early */ - { "virtio_rng", NULL, false, false, has_virtio_rng }, + { "virtio_rng", NULL, false, false, has_virtio_rng }, /* we want early logging to hvc consoles if possible, and make sure systemd-getty-generator * can rely on all consoles being probed already. */ - { "virtio_console", NULL, false, false, may_have_virtio }, + { "virtio_console", NULL, false, false, may_have_virtio }, /* Make sure we can send sd-notify messages over vsock as early as possible. */ - { "vmw_vsock_virtio_transport", NULL, false, false, may_have_virtio }, - { "vmw_vsock_vmci_transport", NULL, false, false, in_vmware }, - { "hv_sock", NULL, false, false, in_hyperv }, + { "vmw_vsock_virtio_transport", NULL, false, false, may_have_virtio }, + /* vsock_loopback provides VMADDR_CID_LOCAL and is not a hard dep of any transport module */ + { "vsock_loopback", "/sys/module/vsock_loopback", false, false, may_have_vsock_loopback }, + { "vmw_vsock_vmci_transport", NULL, false, false, in_vmware }, + { "hv_sock", NULL, false, false, in_hyperv }, /* We can't wait for specific virtiofs tags to show up as device nodes so we have to load the * virtiofs and virtio_pci modules early to make sure the virtiofs tags are found when @@ -131,18 +137,18 @@ int kmod_setup(void) { * * TODO: Remove these again once https://gitlab.com/virtio-fs/virtiofsd/-/issues/128 is * resolved and the kernel fix is widely available. */ - { "virtiofs", "/sys/module/virtiofs", false, false, may_have_virtio }, - { "virtio_pci", "/sys/module/virtio_pci", false, false, has_virtio_pci }, + { "virtiofs", "/sys/module/virtiofs", false, false, may_have_virtio }, + { "virtio_pci", "/sys/module/virtio_pci", false, false, has_virtio_pci }, /* qemu_fw_cfg would be loaded by udev later, but we want to import credentials from it super early */ - { "qemu_fw_cfg", "/sys/firmware/qemu_fw_cfg", false, false, in_qemu }, + { "qemu_fw_cfg", "/sys/firmware/qemu_fw_cfg", false, false, in_qemu }, /* dmi-sysfs is needed to import credentials from it super early */ - { "dmi-sysfs", "/sys/firmware/dmi/entries", false, false, NULL }, + { "dmi-sysfs", "/sys/firmware/dmi/entries", false, false, NULL }, #if HAVE_TPM2 /* Make sure the tpm subsystem is available which ConditionSecurity=tpm2 depends on. */ - { "tpm", "/sys/class/tpmrm", false, false, efi_has_tpm2 }, + { "tpm", "/sys/class/tpmrm", false, false, efi_has_tpm2 }, #endif }; diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index bf808d220bb73..17ac9c5138b26 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -276,8 +276,12 @@ {{type}}.SocketBindAllow, config_parse_cgroup_socket_bind, 0, offsetof({{type}}, cgroup_context.socket_bind_allow) {{type}}.SocketBindDeny, config_parse_cgroup_socket_bind, 0, offsetof({{type}}, cgroup_context.socket_bind_deny) {{type}}.RestrictNetworkInterfaces, config_parse_restrict_network_interfaces, 0, offsetof({{type}}, cgroup_context) -{{type}}.MemoryPressureThresholdSec, config_parse_sec, 0, offsetof({{type}}, cgroup_context.memory_pressure_threshold_usec) -{{type}}.MemoryPressureWatch, config_parse_memory_pressure_watch, 0, offsetof({{type}}, cgroup_context.memory_pressure_watch) +{{type}}.MemoryPressureThresholdSec, config_parse_sec, 0, offsetof({{type}}, cgroup_context.pressure[PRESSURE_MEMORY].threshold_usec) +{{type}}.MemoryPressureWatch, config_parse_pressure_watch, 0, offsetof({{type}}, cgroup_context.pressure[PRESSURE_MEMORY].watch) +{{type}}.CPUPressureThresholdSec, config_parse_sec, 0, offsetof({{type}}, cgroup_context.pressure[PRESSURE_CPU].threshold_usec) +{{type}}.CPUPressureWatch, config_parse_pressure_watch, 0, offsetof({{type}}, cgroup_context.pressure[PRESSURE_CPU].watch) +{{type}}.IOPressureThresholdSec, config_parse_sec, 0, offsetof({{type}}, cgroup_context.pressure[PRESSURE_IO].threshold_usec) +{{type}}.IOPressureWatch, config_parse_pressure_watch, 0, offsetof({{type}}, cgroup_context.pressure[PRESSURE_IO].watch) {{type}}.NFTSet, config_parse_cgroup_nft_set, NFT_SET_PARSE_CGROUP, offsetof({{type}}, cgroup_context) {{type}}.CoredumpReceive, config_parse_bool, 0, offsetof({{type}}, cgroup_context.coredump_receive) {{type}}.BindNetworkInterface, config_parse_bind_network_interface, 0, offsetof({{type}}, cgroup_context) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index d2bfd20fd43af..1fae521333f79 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -10,7 +10,6 @@ #include "sd-bus.h" #include "sd-messages.h" -#include "af-list.h" #include "all-units.h" #include "alloc-util.h" #include "bpf-program.h" @@ -57,7 +56,7 @@ #include "reboot-util.h" #include "seccomp-util.h" #include "securebits-util.h" -#include "selinux-util.h" +#include "selinux-util.h" /* IWYU pragma: keep */ #include "set.h" #include "show-status.h" #include "signal-util.h" @@ -88,6 +87,8 @@ static int parse_socket_protocol(const char *s) { int parse_crash_chvt(const char *value, int *data) { int b; + assert(data); + if (safe_atoi(value, data) >= 0) return 0; @@ -107,6 +108,8 @@ int parse_confirm_spawn(const char *value, char **console) { char *s; int r; + assert(console); + r = value ? parse_boolean(value) : 1; if (r == 0) { *console = NULL; @@ -150,7 +153,7 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_service_timeout_failure_mode, service_time DEFINE_CONFIG_PARSE_ENUM(config_parse_socket_bind, socket_address_bind_ipv6_only_or_bool, SocketAddressBindIPv6Only); DEFINE_CONFIG_PARSE_ENUM(config_parse_oom_policy, oom_policy, OOMPolicy); DEFINE_CONFIG_PARSE_ENUM(config_parse_managed_oom_preference, managed_oom_preference, ManagedOOMPreference); -DEFINE_CONFIG_PARSE_ENUM(config_parse_memory_pressure_watch, cgroup_pressure_watch, CGroupPressureWatch); +DEFINE_CONFIG_PARSE_ENUM(config_parse_pressure_watch, cgroup_pressure_watch, CGroupPressureWatch); DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_ip_tos, ip_tos, int, -1); DEFINE_CONFIG_PARSE_PTR(config_parse_cg_weight, cg_weight_parse, uint64_t); DEFINE_CONFIG_PARSE_PTR(config_parse_cg_cpu_weight, cg_cpu_weight_parse, uint64_t); @@ -565,6 +568,8 @@ static int patch_var_run( const char *e; char *z; + assert(path); + e = path_startswith(*path, "/var/run/"); if (!e) return 0; @@ -985,7 +990,7 @@ int config_parse_exec( ignore ? ", ignoring" : "", rvalue); return ignore ? 0 : -ENOEXEC; } - if (!string_is_safe(path)) { + if (!string_is_safe(path, /* flags= */ 0)) { log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0, "Executable path contains special characters%s: %s", ignore ? ", ignoring" : "", path); @@ -2941,6 +2946,11 @@ int config_parse_log_extra_fields( continue; } + if (c->n_log_extra_fields >= LOG_EXTRA_FIELDS_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Too many extra log fields, ignoring some."); + return 0; + } + if (!GREEDY_REALLOC(c->log_extra_fields, c->n_log_extra_fields + 1)) return log_oom(); @@ -3463,72 +3473,26 @@ int config_parse_address_families( void *userdata) { ExecContext *c = data; - bool invert = false; + bool is_allowlist = c->address_families_allow_list; int r; assert(filename); assert(lvalue); assert(rvalue); - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - c->address_families = set_free(c->address_families); - c->address_families_allow_list = false; - return 0; - } - - if (streq(rvalue, "none")) { - /* Forbid all address families. */ - c->address_families = set_free(c->address_families); - c->address_families_allow_list = true; + r = parse_address_families(rvalue, &c->address_families, &is_allowlist); + /* Copy back unconditionally: parse_address_families() may have partially populated + * c->address_families before failing, so keep is_allowlist in sync with that state. */ + c->address_families_allow_list = is_allowlist; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse address family, ignoring: %s", rvalue); return 0; } - if (rvalue[0] == '~') { - invert = true; - rvalue++; - } - - if (!c->address_families) { - c->address_families = set_new(NULL); - if (!c->address_families) - return log_oom(); - - c->address_families_allow_list = !invert; - } - - for (const char *p = rvalue;;) { - _cleanup_free_ char *word = NULL; - int af; - - r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid syntax, ignoring: %s", rvalue); - return 0; - } - if (r == 0) - return 0; - - af = af_from_name(word); - if (af < 0) { - log_syntax(unit, LOG_WARNING, filename, line, af, - "Failed to parse address family, ignoring: %s", word); - continue; - } - - /* If we previously wanted to forbid an address family and now - * we want to allow it, then just remove it from the list. - */ - if (!invert == c->address_families_allow_list) { - r = set_put(c->address_families, INT_TO_PTR(af)); - if (r < 0) - return log_oom(); - } else - set_remove(c->address_families, INT_TO_PTR(af)); - } + return 0; } #endif @@ -5224,7 +5188,7 @@ int config_parse_mount_images( if (isempty(rvalue)) { /* Empty assignment resets the list */ - mount_image_free_many(c->mount_images, c->n_mount_images); + mount_image_free_array(c->mount_images, c->n_mount_images); c->mount_images = NULL; c->n_mount_images = 0; return 0; @@ -5374,7 +5338,7 @@ int config_parse_extension_images( if (isempty(rvalue)) { /* Empty assignment resets the list */ - mount_image_free_many(c->extension_images, c->n_extension_images); + mount_image_free_array(c->extension_images, c->n_extension_images); c->extension_images = NULL; c->n_extension_images = 0; return 0; diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 4677564904c52..a5b7595dbfdb9 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -164,7 +164,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_watchdog_sec); CONFIG_PARSER_PROTOTYPE(config_parse_tty_size); CONFIG_PARSER_PROTOTYPE(config_parse_log_filter_patterns); CONFIG_PARSER_PROTOTYPE(config_parse_open_file); -CONFIG_PARSER_PROTOTYPE(config_parse_memory_pressure_watch); +CONFIG_PARSER_PROTOTYPE(config_parse_pressure_watch); CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set); CONFIG_PARSER_PROTOTYPE(config_parse_mount_node); CONFIG_PARSER_PROTOTYPE(config_parse_concurrency_max); diff --git a/src/core/main.c b/src/core/main.c index 3a6284b456bc3..3bdce441a85bb 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -163,6 +163,7 @@ static void *arg_random_seed; static size_t arg_random_seed_size; static usec_t arg_reload_limit_interval_sec; static unsigned arg_reload_limit_burst; +static usec_t arg_minimum_uptime_usec; /* A copy of the original environment block */ static char **saved_env = NULL; @@ -501,7 +502,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; } - if (!string_is_safe(value)) { + if (!string_is_safe(value, /* flags= */ 0)) { log_warning("Watchdog pretimeout governor '%s' is not valid, ignoring.", value); return 0; } @@ -554,6 +555,17 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; } + } else if (proc_cmdline_key_streq(key, "systemd.minimum_uptime_sec")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = parse_sec(value, &arg_minimum_uptime_usec); + if (r < 0) { + log_warning_errno(r, "Failed to parse systemd.minimum_uptime_sec= argument '%s', ignoring: %m", value); + return 0; + } + } else if (streq(key, "quiet") && !value) { if (arg_show_status == _SHOW_STATUS_INVALID) @@ -735,88 +747,93 @@ static int config_parse_crash_reboot( static int parse_config_file(void) { const ConfigTableItem items[] = { - { "Manager", "LogLevel", config_parse_level2, 0, NULL }, - { "Manager", "LogTarget", config_parse_target, 0, NULL }, - { "Manager", "LogColor", config_parse_color, 0, NULL }, - { "Manager", "LogLocation", config_parse_location, 0, NULL }, - { "Manager", "LogTime", config_parse_time, 0, NULL }, - { "Manager", "DumpCore", config_parse_bool, 0, &arg_dump_core }, - { "Manager", "CrashChVT", /* legacy */ config_parse_crash_chvt, 0, &arg_crash_chvt }, - { "Manager", "CrashChangeVT", config_parse_crash_chvt, 0, &arg_crash_chvt }, - { "Manager", "CrashShell", config_parse_bool, 0, &arg_crash_shell }, - { "Manager", "CrashReboot", config_parse_crash_reboot, 0, &arg_crash_action }, - { "Manager", "CrashAction", config_parse_crash_action, 0, &arg_crash_action }, - { "Manager", "ShowStatus", config_parse_show_status, 0, &arg_show_status }, - { "Manager", "StatusUnitFormat", config_parse_status_unit_format, 0, &arg_status_unit_format }, - { "Manager", "CPUAffinity", config_parse_cpu_set, 0, &arg_cpu_affinity }, - { "Manager", "NUMAPolicy", config_parse_numa_policy, 0, &arg_numa_policy.type }, - { "Manager", "NUMAMask", config_parse_numa_mask, 0, &arg_numa_policy.nodes }, - { "Manager", "JoinControllers", config_parse_warn_compat, DISABLED_LEGACY, NULL }, - { "Manager", "RuntimeWatchdogSec", config_parse_watchdog_sec, 0, &arg_runtime_watchdog }, - { "Manager", "RuntimeWatchdogPreSec", config_parse_watchdog_sec, 0, &arg_pretimeout_watchdog }, - { "Manager", "RebootWatchdogSec", config_parse_watchdog_sec, 0, &arg_reboot_watchdog }, - { "Manager", "ShutdownWatchdogSec", config_parse_watchdog_sec, 0, &arg_reboot_watchdog }, /* obsolete alias */ - { "Manager", "KExecWatchdogSec", config_parse_watchdog_sec, 0, &arg_kexec_watchdog }, - { "Manager", "WatchdogDevice", config_parse_path, 0, &arg_watchdog_device }, - { "Manager", "RuntimeWatchdogPreGovernor", config_parse_string, CONFIG_PARSE_STRING_SAFE, &arg_watchdog_pretimeout_governor }, - { "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set }, - { "Manager", "NoNewPrivileges", config_parse_bool, 0, &arg_no_new_privs }, - { "Manager", "ProtectSystem", config_parse_protect_system_pid1, 0, &arg_protect_system }, + { "Manager", "LogLevel", config_parse_level2, 0, NULL }, + { "Manager", "LogTarget", config_parse_target, 0, NULL }, + { "Manager", "LogColor", config_parse_color, 0, NULL }, + { "Manager", "LogLocation", config_parse_location, 0, NULL }, + { "Manager", "LogTime", config_parse_time, 0, NULL }, + { "Manager", "DumpCore", config_parse_bool, 0, &arg_dump_core }, + { "Manager", "CrashChVT", /* legacy */ config_parse_crash_chvt, 0, &arg_crash_chvt }, + { "Manager", "CrashChangeVT", config_parse_crash_chvt, 0, &arg_crash_chvt }, + { "Manager", "CrashShell", config_parse_bool, 0, &arg_crash_shell }, + { "Manager", "CrashReboot", config_parse_crash_reboot, 0, &arg_crash_action }, + { "Manager", "CrashAction", config_parse_crash_action, 0, &arg_crash_action }, + { "Manager", "ShowStatus", config_parse_show_status, 0, &arg_show_status }, + { "Manager", "StatusUnitFormat", config_parse_status_unit_format, 0, &arg_status_unit_format }, + { "Manager", "CPUAffinity", config_parse_cpu_set, 0, &arg_cpu_affinity }, + { "Manager", "NUMAPolicy", config_parse_numa_policy, 0, &arg_numa_policy.type }, + { "Manager", "NUMAMask", config_parse_numa_mask, 0, &arg_numa_policy.nodes }, + { "Manager", "JoinControllers", config_parse_warn_compat, DISABLED_LEGACY, NULL }, + { "Manager", "RuntimeWatchdogSec", config_parse_watchdog_sec, 0, &arg_runtime_watchdog }, + { "Manager", "RuntimeWatchdogPreSec", config_parse_watchdog_sec, 0, &arg_pretimeout_watchdog }, + { "Manager", "RebootWatchdogSec", config_parse_watchdog_sec, 0, &arg_reboot_watchdog }, + { "Manager", "ShutdownWatchdogSec", config_parse_watchdog_sec, 0, &arg_reboot_watchdog }, /* obsolete alias */ + { "Manager", "KExecWatchdogSec", config_parse_watchdog_sec, 0, &arg_kexec_watchdog }, + { "Manager", "WatchdogDevice", config_parse_path, 0, &arg_watchdog_device }, + { "Manager", "RuntimeWatchdogPreGovernor", config_parse_string, CONFIG_PARSE_STRING_SAFE, &arg_watchdog_pretimeout_governor }, + { "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set }, + { "Manager", "NoNewPrivileges", config_parse_bool, 0, &arg_no_new_privs }, + { "Manager", "ProtectSystem", config_parse_protect_system_pid1, 0, &arg_protect_system }, #if HAVE_SECCOMP - { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, + { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, #else - { "Manager", "SystemCallArchitectures", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL }, - + { "Manager", "SystemCallArchitectures", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL }, #endif - { "Manager", "TimerSlackNSec", config_parse_nsec, 0, &arg_timer_slack_nsec }, - { "Manager", "DefaultTimerAccuracySec", config_parse_sec, 0, &arg_defaults.timer_accuracy_usec }, - { "Manager", "DefaultStandardOutput", config_parse_output_restricted, 0, &arg_defaults.std_output }, - { "Manager", "DefaultStandardError", config_parse_output_restricted, 0, &arg_defaults.std_error }, - { "Manager", "DefaultTimeoutStartSec", config_parse_sec, 0, &arg_defaults.timeout_start_usec }, - { "Manager", "DefaultTimeoutStopSec", config_parse_sec, 0, &arg_defaults.timeout_stop_usec }, - { "Manager", "DefaultTimeoutAbortSec", config_parse_default_timeout_abort, 0, NULL }, - { "Manager", "DefaultDeviceTimeoutSec", config_parse_sec, 0, &arg_defaults.device_timeout_usec }, - { "Manager", "DefaultRestartSec", config_parse_sec, 0, &arg_defaults.restart_usec }, - { "Manager", "DefaultStartLimitInterval", config_parse_sec, 0, &arg_defaults.start_limit.interval}, /* obsolete alias */ - { "Manager", "DefaultStartLimitIntervalSec", config_parse_sec, 0, &arg_defaults.start_limit.interval}, - { "Manager", "DefaultStartLimitBurst", config_parse_unsigned, 0, &arg_defaults.start_limit.burst }, - { "Manager", "DefaultRestrictSUIDSGID", config_parse_bool, 0, &arg_defaults.restrict_suid_sgid }, - { "Manager", "DefaultEnvironment", config_parse_environ, arg_runtime_scope, &arg_default_environment }, - { "Manager", "ManagerEnvironment", config_parse_environ, arg_runtime_scope, &arg_manager_environment }, - { "Manager", "DefaultLimitCPU", config_parse_rlimit, RLIMIT_CPU, arg_defaults.rlimit }, - { "Manager", "DefaultLimitFSIZE", config_parse_rlimit, RLIMIT_FSIZE, arg_defaults.rlimit }, - { "Manager", "DefaultLimitDATA", config_parse_rlimit, RLIMIT_DATA, arg_defaults.rlimit }, - { "Manager", "DefaultLimitSTACK", config_parse_rlimit, RLIMIT_STACK, arg_defaults.rlimit }, - { "Manager", "DefaultLimitCORE", config_parse_rlimit, RLIMIT_CORE, arg_defaults.rlimit }, - { "Manager", "DefaultLimitRSS", config_parse_rlimit, RLIMIT_RSS, arg_defaults.rlimit }, - { "Manager", "DefaultLimitNOFILE", config_parse_rlimit, RLIMIT_NOFILE, arg_defaults.rlimit }, - { "Manager", "DefaultLimitAS", config_parse_rlimit, RLIMIT_AS, arg_defaults.rlimit }, - { "Manager", "DefaultLimitNPROC", config_parse_rlimit, RLIMIT_NPROC, arg_defaults.rlimit }, - { "Manager", "DefaultLimitMEMLOCK", config_parse_rlimit, RLIMIT_MEMLOCK, arg_defaults.rlimit }, - { "Manager", "DefaultLimitLOCKS", config_parse_rlimit, RLIMIT_LOCKS, arg_defaults.rlimit }, - { "Manager", "DefaultLimitSIGPENDING", config_parse_rlimit, RLIMIT_SIGPENDING, arg_defaults.rlimit }, - { "Manager", "DefaultLimitMSGQUEUE", config_parse_rlimit, RLIMIT_MSGQUEUE, arg_defaults.rlimit }, - { "Manager", "DefaultLimitNICE", config_parse_rlimit, RLIMIT_NICE, arg_defaults.rlimit }, - { "Manager", "DefaultLimitRTPRIO", config_parse_rlimit, RLIMIT_RTPRIO, arg_defaults.rlimit }, - { "Manager", "DefaultLimitRTTIME", config_parse_rlimit, RLIMIT_RTTIME, arg_defaults.rlimit }, - { "Manager", "DefaultCPUAccounting", config_parse_warn_compat, DISABLED_LEGACY, NULL }, - { "Manager", "DefaultIOAccounting", config_parse_bool, 0, &arg_defaults.io_accounting }, - { "Manager", "DefaultIPAccounting", config_parse_bool, 0, &arg_defaults.ip_accounting }, - { "Manager", "DefaultBlockIOAccounting", config_parse_warn_compat, DISABLED_LEGACY, NULL }, - { "Manager", "DefaultMemoryAccounting", config_parse_bool, 0, &arg_defaults.memory_accounting }, - { "Manager", "DefaultTasksAccounting", config_parse_bool, 0, &arg_defaults.tasks_accounting }, - { "Manager", "DefaultTasksMax", config_parse_tasks_max, 0, &arg_defaults.tasks_max }, - { "Manager", "DefaultMemoryPressureThresholdSec", config_parse_sec, 0, &arg_defaults.memory_pressure_threshold_usec }, - { "Manager", "DefaultMemoryPressureWatch", config_parse_memory_pressure_watch, 0, &arg_defaults.memory_pressure_watch }, - { "Manager", "CtrlAltDelBurstAction", config_parse_emergency_action, arg_runtime_scope, &arg_cad_burst_action }, - { "Manager", "DefaultOOMPolicy", config_parse_oom_policy, 0, &arg_defaults.oom_policy }, - { "Manager", "DefaultOOMScoreAdjust", config_parse_oom_score_adjust, 0, NULL }, - { "Manager", "ReloadLimitIntervalSec", config_parse_sec, 0, &arg_reload_limit_interval_sec }, - { "Manager", "ReloadLimitBurst", config_parse_unsigned, 0, &arg_reload_limit_burst }, + { "Manager", "TimerSlackNSec", config_parse_nsec, 0, &arg_timer_slack_nsec }, + { "Manager", "DefaultTimerAccuracySec", config_parse_sec, 0, &arg_defaults.timer_accuracy_usec }, + { "Manager", "DefaultStandardOutput", config_parse_output_restricted, 0, &arg_defaults.std_output }, + { "Manager", "DefaultStandardError", config_parse_output_restricted, 0, &arg_defaults.std_error }, + { "Manager", "DefaultTimeoutStartSec", config_parse_sec, 0, &arg_defaults.timeout_start_usec }, + { "Manager", "DefaultTimeoutStopSec", config_parse_sec, 0, &arg_defaults.timeout_stop_usec }, + { "Manager", "DefaultTimeoutAbortSec", config_parse_default_timeout_abort, 0, NULL }, + { "Manager", "DefaultDeviceTimeoutSec", config_parse_sec, 0, &arg_defaults.device_timeout_usec }, + { "Manager", "DefaultRestartSec", config_parse_sec, 0, &arg_defaults.restart_usec }, + { "Manager", "DefaultStartLimitInterval", config_parse_sec, 0, &arg_defaults.start_limit.interval }, /* obsolete alias */ + { "Manager", "DefaultStartLimitIntervalSec", config_parse_sec, 0, &arg_defaults.start_limit.interval }, + { "Manager", "DefaultStartLimitBurst", config_parse_unsigned, 0, &arg_defaults.start_limit.burst }, + { "Manager", "DefaultRestrictSUIDSGID", config_parse_bool, 0, &arg_defaults.restrict_suid_sgid }, + { "Manager", "DefaultEnvironment", config_parse_environ, arg_runtime_scope, &arg_default_environment }, + { "Manager", "ManagerEnvironment", config_parse_environ, arg_runtime_scope, &arg_manager_environment }, + { "Manager", "DefaultLimitCPU", config_parse_rlimit, RLIMIT_CPU, arg_defaults.rlimit }, + { "Manager", "DefaultLimitFSIZE", config_parse_rlimit, RLIMIT_FSIZE, arg_defaults.rlimit }, + { "Manager", "DefaultLimitDATA", config_parse_rlimit, RLIMIT_DATA, arg_defaults.rlimit }, + { "Manager", "DefaultLimitSTACK", config_parse_rlimit, RLIMIT_STACK, arg_defaults.rlimit }, + { "Manager", "DefaultLimitCORE", config_parse_rlimit, RLIMIT_CORE, arg_defaults.rlimit }, + { "Manager", "DefaultLimitRSS", config_parse_rlimit, RLIMIT_RSS, arg_defaults.rlimit }, + { "Manager", "DefaultLimitNOFILE", config_parse_rlimit, RLIMIT_NOFILE, arg_defaults.rlimit }, + { "Manager", "DefaultLimitAS", config_parse_rlimit, RLIMIT_AS, arg_defaults.rlimit }, + { "Manager", "DefaultLimitNPROC", config_parse_rlimit, RLIMIT_NPROC, arg_defaults.rlimit }, + { "Manager", "DefaultLimitMEMLOCK", config_parse_rlimit, RLIMIT_MEMLOCK, arg_defaults.rlimit }, + { "Manager", "DefaultLimitLOCKS", config_parse_rlimit, RLIMIT_LOCKS, arg_defaults.rlimit }, + { "Manager", "DefaultLimitSIGPENDING", config_parse_rlimit, RLIMIT_SIGPENDING, arg_defaults.rlimit }, + { "Manager", "DefaultLimitMSGQUEUE", config_parse_rlimit, RLIMIT_MSGQUEUE, arg_defaults.rlimit }, + { "Manager", "DefaultLimitNICE", config_parse_rlimit, RLIMIT_NICE, arg_defaults.rlimit }, + { "Manager", "DefaultLimitRTPRIO", config_parse_rlimit, RLIMIT_RTPRIO, arg_defaults.rlimit }, + { "Manager", "DefaultLimitRTTIME", config_parse_rlimit, RLIMIT_RTTIME, arg_defaults.rlimit }, + { "Manager", "DefaultCPUAccounting", config_parse_warn_compat, DISABLED_LEGACY, NULL }, + { "Manager", "DefaultIOAccounting", config_parse_bool, 0, &arg_defaults.io_accounting }, + { "Manager", "DefaultIPAccounting", config_parse_bool, 0, &arg_defaults.ip_accounting }, + { "Manager", "DefaultBlockIOAccounting", config_parse_warn_compat, DISABLED_LEGACY, NULL }, + { "Manager", "DefaultMemoryAccounting", config_parse_bool, 0, &arg_defaults.memory_accounting }, + { "Manager", "DefaultTasksAccounting", config_parse_bool, 0, &arg_defaults.tasks_accounting }, + { "Manager", "DefaultTasksMax", config_parse_tasks_max, 0, &arg_defaults.tasks_max }, + { "Manager", "DefaultMemoryPressureThresholdSec", config_parse_sec, 0, &arg_defaults.pressure[PRESSURE_MEMORY].threshold_usec }, + { "Manager", "DefaultMemoryPressureWatch", config_parse_pressure_watch, 0, &arg_defaults.pressure[PRESSURE_MEMORY].watch }, + { "Manager", "DefaultCPUPressureThresholdSec", config_parse_sec, 0, &arg_defaults.pressure[PRESSURE_CPU].threshold_usec }, + { "Manager", "DefaultCPUPressureWatch", config_parse_pressure_watch, 0, &arg_defaults.pressure[PRESSURE_CPU].watch }, + { "Manager", "DefaultIOPressureThresholdSec", config_parse_sec, 0, &arg_defaults.pressure[PRESSURE_IO].threshold_usec }, + { "Manager", "DefaultIOPressureWatch", config_parse_pressure_watch, 0, &arg_defaults.pressure[PRESSURE_IO].watch }, + { "Manager", "CtrlAltDelBurstAction", config_parse_emergency_action, arg_runtime_scope, &arg_cad_burst_action }, + { "Manager", "DefaultOOMPolicy", config_parse_oom_policy, 0, &arg_defaults.oom_policy }, + { "Manager", "DefaultOOMScoreAdjust", config_parse_oom_score_adjust, 0, NULL }, + { "Manager", "ReloadLimitIntervalSec", config_parse_sec, 0, &arg_reload_limit_interval_sec }, + { "Manager", "ReloadLimitBurst", config_parse_unsigned, 0, &arg_reload_limit_burst }, + { "Manager", "DefaultMemoryZSwapWriteback", config_parse_bool, 0, &arg_defaults.memory_zswap_writeback }, + { "Manager", "MinimumUptimeSec", config_parse_sec, 0, &arg_minimum_uptime_usec }, #if ENABLE_SMACK - { "Manager", "DefaultSmackProcessLabel", config_parse_string, 0, &arg_defaults.smack_process_label }, + { "Manager", "DefaultSmackProcessLabel", config_parse_string, 0, &arg_defaults.smack_process_label }, #else - { "Manager", "DefaultSmackProcessLabel", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL }, + { "Manager", "DefaultSmackProcessLabel", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL }, #endif {} }; @@ -1740,6 +1757,9 @@ static int become_shutdown(int objective, int retval) { if (arg_watchdog_device) (void) strv_extendf(&env_block, "WATCHDOG_DEVICE=%s", arg_watchdog_device); + if (arg_minimum_uptime_usec != USEC_INFINITY) + (void) strv_extendf(&env_block, "MINIMUM_UPTIME_USEC=" USEC_FMT, arg_minimum_uptime_usec); + (void) write_boot_or_shutdown_osc("shutdown"); execve(SYSTEMD_SHUTDOWN_BINARY_PATH, (char **) command_line, env_block); @@ -2632,6 +2652,8 @@ static int do_queue_default_job( Unit *target; int r; + assert(ret_error_message); + if (arg_default_unit) unit = arg_default_unit; else if (in_initrd()) @@ -2830,6 +2852,8 @@ static void reset_arguments(void) { arg_reload_limit_interval_sec = 0; arg_reload_limit_burst = 0; + + arg_minimum_uptime_usec = USEC_INFINITY; } static void determine_default_oom_score_adjust(void) { @@ -3354,7 +3378,7 @@ int main(int argc, char *argv[]) { } /* Building without libmount is allowed, but if it is compiled in, then we must be able to load it */ - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) { error_message = "Failed to load libmount.so"; goto finish; diff --git a/src/core/manager-serialize.c b/src/core/manager-serialize.c index 2357b9277c616..c794888164874 100644 --- a/src/core/manager-serialize.c +++ b/src/core/manager-serialize.c @@ -14,6 +14,7 @@ #include "manager-serialize.h" #include "parse-util.h" #include "serialize.h" +#include "set.h" #include "string-util.h" #include "strv.h" #include "syslog-util.h" @@ -197,7 +198,50 @@ int manager_serialize( return 0; } -static int manager_deserialize_one_unit(Manager *m, const char *name, FILE *f, FDSet *fds) { +static int manager_collect_serialized_unit_names(FILE *f, Set **ret) { + _cleanup_set_free_ Set *serialized_units = NULL; + off_t offset; + int r; + + assert(f); + assert(ret); + + offset = ftello(f); + if (offset < 0) + return log_error_errno(errno, "Failed to determine serialization offset: %m"); + + for (;;) { + _cleanup_free_ char *line = NULL; + + r = read_stripped_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return log_error_errno(r, "Failed to read serialization line: %m"); + if (r == 0) + break; + + r = set_ensure_consume(&serialized_units, &string_hash_ops_free, TAKE_PTR(line)); + if (r < 0) + return log_oom(); + + r = unit_deserialize_state_skip(f); + if (r < 0) + return r; + } + + if (fseeko(f, offset, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to reset serialization offset: %m"); + + *ret = TAKE_PTR(serialized_units); + return 0; +} + +static int manager_deserialize_one_unit( + Manager *m, + const char *name, + FILE *f, + FDSet *fds, + Set *serialized_units) { + Unit *u; int r; @@ -207,18 +251,75 @@ static int manager_deserialize_one_unit(Manager *m, const char *name, FILE *f, F if (r < 0) return log_notice_errno(r, "Failed to load unit \"%s\", skipping deserialization: %m", name); + if (!streq(u->id, name) && + set_contains(serialized_units, u->id)) { + /* + * The unit from the state file (name) resolved to a different canonical unit (u->id), and + * the canonical unit also has its own state entry. + * + * This means the state entry for the unit name is stale. That is, when the state was + * serialized, the name referred to an independent unit, but it now resolves as an alias to + * the canonical unit. Deserializing it would overwrite the canonical unit's own serialized + * state, and thus corrupt its live runtime state. + * + * It is very important to note that this only affects units that were independent when the + * state file was written, but are now aliases (either because a reload created the symlink, + * or the symlink existed but this is the first reload). Normal aliases that were already + * aliases during the most recent serialization are filtered out in manager_serialize(), so + * they never appear in the state file. + * + * If the canonical unit does not have its own state entry, then this is instead a rename or + * canonical ID change, and this state entry is the only state we have for the unit. In that + * case we must preserve it. After doing so, we insert the canonical unit's ID into the set + * so that any further aliases resolving to the same unit are skipped. + * + * The serialized data represents the old, independent unit. Deserializing this stale state + * would corrupt the canonical unit's live state, so we must discard it. + * + * Take as an example, a.service is running. Someone created symlink b.service -> a.service. + * On first reload, the state file still has b.service as an independent dead unit (from + * before the symlink existed), but b.service now resolves to a.service. We must discard + * b.service's stale dead state to preserve a.service's running state. + * + * Note: This log message is checked in TEST-07-PID1.alias-corruption.sh, so the test case + * may need adjustment if the message is changed. + */ + log_warning("Unit file for '%s' was overridden by a symlink to '%s', which also has serialized state. Skipping stale state of old unit. Any processes from the overridden unit are now abandoned!", + name, + u->id); + + return unit_deserialize_state_skip(f); + } + r = unit_deserialize_state(u, f, fds); if (r == -ENOMEM) return log_oom(); if (r < 0) return log_notice_errno(r, "Failed to deserialize unit \"%s\", skipping: %m", name); + /* If this unit was deserialized under an alias name (that is, it is a rename), record the canonical + * ID so that any further aliases pointing to the same unit are correctly skipped. */ + if (!streq(u->id, name)) { + r = set_put_strdup(&serialized_units, u->id); + if (r < 0) + return log_oom(); + } + return 0; } -static int manager_deserialize_units(Manager *m, FILE *f, FDSet *fds) { +static int manager_deserialize_units( + Manager *m, + FILE *f, + FDSet *fds) { + + _cleanup_set_free_ Set *serialized_units = NULL; int r; + r = manager_collect_serialized_unit_names(f, &serialized_units); + if (r < 0) + return r; + for (;;) { _cleanup_free_ char *line = NULL; @@ -229,7 +330,7 @@ static int manager_deserialize_units(Manager *m, FILE *f, FDSet *fds) { if (r == 0) break; - r = manager_deserialize_one_unit(m, line, f, fds); + r = manager_deserialize_one_unit(m, line, f, fds, serialized_units); if (r == -ENOMEM) return r; if (r < 0) { diff --git a/src/core/manager.c b/src/core/manager.c index 79fa19d976eb3..56ee66e5bd292 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -616,9 +616,13 @@ static char** sanitize_environment(char **l) { l, "CACHE_DIRECTORY", "CONFIGURATION_DIRECTORY", + "CPU_PRESSURE_WATCH", + "CPU_PRESSURE_WRITE", "CREDENTIALS_DIRECTORY", "EXIT_CODE", "EXIT_STATUS", + "IO_PRESSURE_WATCH", + "IO_PRESSURE_WRITE", "INVOCATION_ID", "JOURNAL_STREAM", "LISTEN_FDNAMES", @@ -796,26 +800,38 @@ static int manager_setup_sigchld_event_source(Manager *m) { return 0; } -int manager_setup_memory_pressure_event_source(Manager *m) { +typedef int (*pressure_add_t)(sd_event *, sd_event_source **, sd_event_handler_t, void *); +typedef int (*pressure_set_period_t)(sd_event_source *, usec_t, usec_t); + +static const struct { + pressure_add_t add; + pressure_set_period_t set_period; +} pressure_dispatch_table[_PRESSURE_RESOURCE_MAX] = { + [PRESSURE_MEMORY] = { sd_event_add_memory_pressure, sd_event_source_set_memory_pressure_period }, + [PRESSURE_CPU] = { sd_event_add_cpu_pressure, sd_event_source_set_cpu_pressure_period }, + [PRESSURE_IO] = { sd_event_add_io_pressure, sd_event_source_set_io_pressure_period }, +}; + +int manager_setup_pressure_event_source(Manager *m, PressureResource t) { int r; assert(m); + assert(t >= 0 && t < _PRESSURE_RESOURCE_MAX); - m->memory_pressure_event_source = sd_event_source_disable_unref(m->memory_pressure_event_source); + m->pressure_event_source[t] = sd_event_source_disable_unref(m->pressure_event_source[t]); - r = sd_event_add_memory_pressure(m->event, &m->memory_pressure_event_source, NULL, NULL); + r = pressure_dispatch_table[t].add(m->event, &m->pressure_event_source[t], NULL, NULL); if (r < 0) log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) || ERRNO_IS_PRIVILEGE(r) || (r == -EHOSTDOWN) ? LOG_DEBUG : LOG_NOTICE, r, - "Failed to establish memory pressure event source, ignoring: %m"); - else if (m->defaults.memory_pressure_threshold_usec != USEC_INFINITY) { - - /* If there's a default memory pressure threshold set, also apply it to the service manager itself */ - r = sd_event_source_set_memory_pressure_period( - m->memory_pressure_event_source, - m->defaults.memory_pressure_threshold_usec, - MEMORY_PRESSURE_DEFAULT_WINDOW_USEC); + "Failed to establish %s pressure event source, ignoring: %m", pressure_resource_to_string(t)); + else if (m->defaults.pressure[t].threshold_usec != USEC_INFINITY) { + + r = pressure_dispatch_table[t].set_period( + m->pressure_event_source[t], + m->defaults.pressure[t].threshold_usec, + PRESSURE_DEFAULT_WINDOW_USEC); if (r < 0) - log_warning_errno(r, "Failed to adjust memory pressure threshold, ignoring: %m"); + log_warning_errno(r, "Failed to adjust %s pressure threshold, ignoring: %m", pressure_resource_to_string(t)); } return 0; @@ -1001,9 +1017,11 @@ int manager_new(RuntimeScope runtime_scope, ManagerTestRunFlags test_run_flags, if (r < 0) return r; - r = manager_setup_memory_pressure_event_source(m); - if (r < 0) - return r; + for (PressureResource t = 0; t < _PRESSURE_RESOURCE_MAX; t++) { + r = manager_setup_pressure_event_source(m, t); + if (r < 0) + return r; + } #if HAVE_LIBBPF if (MANAGER_IS_SYSTEM(m) && bpf_restrict_fs_supported(/* initialize= */ true)) { @@ -1711,7 +1729,8 @@ Manager* manager_free(Manager *m) { sd_event_source_unref(m->user_lookup_event_source); sd_event_source_unref(m->handoff_timestamp_event_source); sd_event_source_unref(m->pidref_event_source); - sd_event_source_unref(m->memory_pressure_event_source); + FOREACH_ARRAY(pressure_event_source, m->pressure_event_source, _PRESSURE_RESOURCE_MAX) + sd_event_source_unref(*pressure_event_source); safe_close(m->signal_fd); safe_close(m->notify_fd); @@ -1982,6 +2001,8 @@ Manager* manager_reloading_start(Manager *m) { } void manager_reloading_stopp(Manager **m) { + assert(m); + if (*m) { assert((*m)->n_reloading > 0); (*m)->n_reloading--; @@ -2501,6 +2522,8 @@ int manager_load_startable_unit_or_warn( Unit *unit; int r; + assert(ret); + r = manager_load_unit(m, name, path, &error, &unit); if (r < 0) return log_error_errno(r, "Failed to load %s %s: %s", @@ -3553,12 +3576,14 @@ int manager_set_watchdog_pretimeout_governor(Manager *m, const char *governor) { if (MANAGER_IS_USER(m)) return 0; + governor = empty_to_null(governor); + if (streq_ptr(m->watchdog_pretimeout_governor, governor)) return 0; - p = strdup(governor); - if (!p) - return -ENOMEM; + r = strdup_to(&p, governor); + if (r < 0) + return r; r = watchdog_setup_pretimeout_governor(governor); if (r < 0) @@ -3576,12 +3601,14 @@ int manager_override_watchdog_pretimeout_governor(Manager *m, const char *govern if (MANAGER_IS_USER(m)) return 0; + governor = empty_to_null(governor); + if (streq_ptr(m->watchdog_pretimeout_governor_overridden, governor)) return 0; - p = strdup(governor); - if (!p) - return -ENOMEM; + r = strdup_to(&p, governor); + if (r < 0) + return r; r = watchdog_setup_pretimeout_governor(governor); if (r < 0) @@ -4298,8 +4325,9 @@ int manager_set_unit_defaults(Manager *m, const UnitDefaults *defaults) { m->defaults.oom_score_adjust = defaults->oom_score_adjust; m->defaults.oom_score_adjust_set = defaults->oom_score_adjust_set; - m->defaults.memory_pressure_watch = defaults->memory_pressure_watch; - m->defaults.memory_pressure_threshold_usec = defaults->memory_pressure_threshold_usec; + memcpy(m->defaults.pressure, defaults->pressure, sizeof(m->defaults.pressure)); + + m->defaults.memory_zswap_writeback = defaults->memory_zswap_writeback; free_and_replace(m->defaults.smack_process_label, label); rlimit_free_all(m->defaults.rlimit); @@ -4497,10 +4525,9 @@ const char* manager_get_confirm_spawn(Manager *m) { goto fail; } - if (!S_ISCHR(st.st_mode)) { - r = -ENOTTY; + r = stat_verify_char(&st); + if (r < 0) goto fail; - } last_errno = 0; return m->confirm_spawn; @@ -5191,11 +5218,16 @@ void unit_defaults_init(UnitDefaults *defaults, RuntimeScope scope) { .tasks_max = DEFAULT_TASKS_MAX, .timer_accuracy_usec = 1 * USEC_PER_MINUTE, - .memory_pressure_watch = CGROUP_PRESSURE_WATCH_AUTO, - .memory_pressure_threshold_usec = MEMORY_PRESSURE_DEFAULT_THRESHOLD_USEC, + .pressure = { + [PRESSURE_MEMORY] = { .watch = CGROUP_PRESSURE_WATCH_AUTO, .threshold_usec = PRESSURE_DEFAULT_THRESHOLD_USEC }, + [PRESSURE_CPU] = { .watch = CGROUP_PRESSURE_WATCH_AUTO, .threshold_usec = PRESSURE_DEFAULT_THRESHOLD_USEC }, + [PRESSURE_IO] = { .watch = CGROUP_PRESSURE_WATCH_AUTO, .threshold_usec = PRESSURE_DEFAULT_THRESHOLD_USEC }, + }, .oom_policy = OOM_STOP, .oom_score_adjust_set = false, + + .memory_zswap_writeback = true, }; } @@ -5220,9 +5252,13 @@ void manager_log_caller(Manager *manager, PidRef *caller, const char *method) { _cleanup_free_ char *comm = NULL; assert(manager); - assert(pidref_is_set(caller)); assert(method); + if (!pidref_is_set(caller)) { + log_notice("%s requested from unknown client PID...", method); + return; + } + (void) pidref_get_comm(caller, &comm); Unit *caller_unit = manager_get_unit_by_pidref(manager, caller); diff --git a/src/core/manager.h b/src/core/manager.h index 2df606005dbb1..7d58c330a1b82 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -147,8 +147,9 @@ typedef struct UnitDefaults { int oom_score_adjust; bool oom_score_adjust_set; - CGroupPressureWatch memory_pressure_watch; - usec_t memory_pressure_threshold_usec; + bool memory_zswap_writeback; + + CGroupPressure pressure[_PRESSURE_RESOURCE_MAX]; char *smack_process_label; @@ -479,7 +480,7 @@ typedef struct Manager { /* Dump*() are slow, so always rate limit them to 10 per 10 minutes */ RateLimit dump_ratelimit; - sd_event_source *memory_pressure_event_source; + sd_event_source *pressure_event_source[_PRESSURE_RESOURCE_MAX]; /* For NFTSet= */ sd_netlink *nfnl; @@ -560,7 +561,7 @@ void manager_unwatch_pidref(Manager *m, const PidRef *pid); unsigned manager_dispatch_load_queue(Manager *m); -int manager_setup_memory_pressure_event_source(Manager *m); +int manager_setup_pressure_event_source(Manager *m, PressureResource t); int manager_default_environment(Manager *m); int manager_transient_environment_add(Manager *m, char **plus); diff --git a/src/core/meson.build b/src/core/meson.build index e703cc3728970..d24fc3b6d3788 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -64,26 +64,22 @@ libcore_sources = files( 'unit-serialize.c', 'unit.c', 'varlink.c', + 'varlink-automount.c', 'varlink-cgroup.c', 'varlink-common.c', 'varlink-dynamic-user.c', 'varlink-execute.c', + 'varlink-kill.c', 'varlink-manager.c', 'varlink-metrics.c', + 'varlink-mount.c', 'varlink-unit.c', ) -subdir('bpf/socket-bind') -subdir('bpf/restrict-fs') -subdir('bpf/restrict-ifaces') -subdir('bpf/bind-iface') - if conf.get('BPF_FRAMEWORK') == 1 - libcore_sources += [ - socket_bind_skel_h, - restrict_fs_skel_h, - restrict_ifaces_skel_h, - bind_network_interface_skel_h] + foreach name : ['socket-bind', 'restrict-fs', 'restrict-ifaces', 'bind-iface'] + libcore_sources += bpf_programs_by_name[name] + endforeach endif sources += libcore_sources @@ -170,11 +166,14 @@ systemd_sources = files( 'apparmor-setup.c', 'ima-setup.c', 'ipe-setup.c', - 'selinux-setup.c', 'smack-setup.c', 'efi-random.c', ) +if conf.get('HAVE_SELINUX') == 1 + systemd_sources += files('selinux-setup.c') +endif + systemd_executor_sources = files( 'executor.c', 'exec-invoke.c', diff --git a/src/core/mount.c b/src/core/mount.c index 680e376febfc9..967274b950b67 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -2003,6 +2003,8 @@ static int mount_get_timeout(Unit *u, usec_t *timeout) { usec_t t; int r; + assert(timeout); + if (!m->timer_event_source) return 0; @@ -2406,7 +2408,7 @@ static int mount_test_startable(Unit *u) { } static bool mount_supported(void) { - return dlopen_libmount() >= 0; + return dlopen_libmount(LOG_DEBUG) >= 0; } static int mount_subsystem_ratelimited(Manager *m) { diff --git a/src/core/namespace.c b/src/core/namespace.c index 9727d725eb7df..b07b1082edef2 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -1049,7 +1049,7 @@ static bool verity_has_later_duplicates(MountList *ml, const MountEntry *needle) for (const MountEntry *m = needle + 1; m < ml->mounts + ml->n_mounts; m++) { if (m->mode != MOUNT_EXTENSION_IMAGE) continue; - if (iovec_memcmp(&m->verity.root_hash, &needle->verity.root_hash) == 0) + if (iovec_equal(&m->verity.root_hash, &needle->verity.root_hash)) return true; } @@ -3250,18 +3250,15 @@ int bind_mount_add(BindMount **b, size_t *n, const BindMount *item) { return 0; } -void mount_image_free_many(MountImage *m, size_t n) { - assert(m || n == 0); - - FOREACH_ARRAY(i, m, n) { - free(i->source); - free(i->destination); - mount_options_free_all(i->mount_options); - } - - free(m); +static void mount_image_done(MountImage *m) { + assert(m); + m->source = mfree(m->source); + m->destination = mfree(m->destination); + m->mount_options = mount_options_free_all(m->mount_options); } +DEFINE_ARRAY_FREE_FUNC(mount_image_free_array, MountImage, mount_image_done); + int mount_image_add(MountImage **m, size_t *n, const MountImage *item) { _cleanup_free_ char *s = NULL, *d = NULL; _cleanup_(mount_options_free_allp) MountOptions *o = NULL; @@ -3665,7 +3662,7 @@ static int unpeel_get_fd(const char *mount_path, int *ret_fd) { _exit(EXIT_FAILURE); } if (r > 0) { - log_debug_errno(r, "'%s' is still an overlay after opening mount tree: %m", mount_path); + log_debug("'%s' is still an overlay after opening mount tree", mount_path); _exit(EXIT_FAILURE); } @@ -3877,7 +3874,7 @@ static int handle_mount_from_grandchild( if (r < 0) return log_oom_debug(); - *fd_layers[(*n_fd_layers)++] = TAKE_FD(tree_fd); + (*fd_layers)[(*n_fd_layers)++] = TAKE_FD(tree_fd); } m->overlay_layers = strv_free(m->overlay_layers); m->overlay_layers = TAKE_PTR(new_layers); @@ -3987,7 +3984,7 @@ int refresh_extensions_in_namespace( if (r > 0) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Target namespace is not separate, cannot reload extensions"); - (void) dlopen_cryptsetup(); + (void) dlopen_cryptsetup(LOG_DEBUG); extension_dir = path_join(p->private_namespace_dir, "unit-extensions"); if (!extension_dir) diff --git a/src/core/namespace.h b/src/core/namespace.h index f5d792dd77144..d25859f17aa46 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -303,7 +303,7 @@ DECLARE_STRING_TABLE_LOOKUP(private_pids, PrivatePIDs); void bind_mount_free_many(BindMount *b, size_t n); int bind_mount_add(BindMount **b, size_t *n, const BindMount *item); -void mount_image_free_many(MountImage *m, size_t n); +void mount_image_free_array(MountImage *array, size_t n); int mount_image_add(MountImage **m, size_t *n, const MountImage *item); void temporary_filesystem_free_many(TemporaryFileSystem *t, size_t n); diff --git a/src/core/path.c b/src/core/path.c index 789ef9e25d6e9..18a5e140f1442 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -931,6 +931,7 @@ static int activation_details_path_deserialize(const char *key, const char *valu assert(key); assert(value); + POINTER_MAY_BE_NULL(details); if (!details || !*details) return -EINVAL; diff --git a/src/core/scope.c b/src/core/scope.c index d36b27c537dfc..21520d9d1943b 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -483,6 +483,8 @@ static int scope_get_timeout(Unit *u, usec_t *timeout) { usec_t t; int r; + assert(timeout); + if (!s->timer_event_source) return 0; diff --git a/src/core/selinux-setup.c b/src/core/selinux-setup.c index 6f78346036d7c..d06c9f0293167 100644 --- a/src/core/selinux-setup.c +++ b/src/core/selinux-setup.c @@ -13,16 +13,13 @@ #include "time-util.h" int mac_selinux_setup(bool *loaded_policy) { - assert(loaded_policy); - -#if HAVE_SELINUX int r; - r = dlopen_libselinux(); - if (r < 0) { - log_debug_errno(r, "No SELinux library available, skipping setup."); + assert(loaded_policy); + + r = dlopen_libselinux(LOG_DEBUG); + if (r < 0) return 0; - } mac_selinux_disable_logging(); @@ -92,7 +89,6 @@ int mac_selinux_setup(bool *loaded_policy) { } else log_debug("Unable to load SELinux policy. Ignoring."); } -#endif return 0; } diff --git a/src/core/selinux-setup.h b/src/core/selinux-setup.h index 3dad97bbf6977..dd961b03709d2 100644 --- a/src/core/selinux-setup.h +++ b/src/core/selinux-setup.h @@ -3,4 +3,10 @@ #include "core-forward.h" +#if HAVE_SELINUX int mac_selinux_setup(bool *loaded_policy); +#else +static inline int mac_selinux_setup(bool *loaded_policy) { + return 0; +} +#endif diff --git a/src/core/service.c b/src/core/service.c index 51bba291e8fd4..4e1d1d38d03bd 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -13,6 +13,7 @@ #include "bus-common-errors.h" #include "bus-error.h" #include "bus-util.h" +#include "cgroup.h" #include "chase.h" #include "dbus-service.h" #include "dbus-unit.h" @@ -60,6 +61,8 @@ #define service_spawn(...) service_spawn_internal(__func__, __VA_ARGS__) +#define SERVICE_FD_STORE_POPULATED(s) (!!(s)->fd_store) + static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = { [SERVICE_DEAD] = UNIT_INACTIVE, [SERVICE_CONDITION] = UNIT_ACTIVATING, @@ -138,6 +141,8 @@ static void service_enter_reload_by_notify(Service *s); static bool service_can_reload_extensions(Service *s, bool warn); +static void service_set_state(Service *s, ServiceState state); + static bool SERVICE_STATE_WITH_MAIN_PROCESS(ServiceState state) { return IN_SET(state, SERVICE_START, SERVICE_START_POST, @@ -479,12 +484,12 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(ServiceFDStore*, service_fd_store_unlink); static void service_release_fd_store(Service *s) { assert(s); - if (!s->fd_store) + if (!SERVICE_FD_STORE_POPULATED(s)) return; log_unit_debug(UNIT(s), "Releasing all stored fds."); - while (s->fd_store) + while (SERVICE_FD_STORE_POPULATED(s)) service_fd_store_unlink(s->fd_store); assert(s->n_fd_store == 0); @@ -571,15 +576,20 @@ static void service_done(Unit *u) { static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *userdata) { ServiceFDStore *fs = ASSERT_PTR(userdata); + Service *s = fs->service; assert(e); /* If we get either EPOLLHUP or EPOLLERR, it's time to remove this entry from the fd store */ - log_unit_debug(UNIT(fs->service), + log_unit_debug(UNIT(s), "Received %s on stored fd %d (%s), closing.", revents & EPOLLERR ? "EPOLLERR" : "EPOLLHUP", fs->fd, strna(fs->fdname)); service_fd_store_unlink(fs); + + if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !SERVICE_FD_STORE_POPULATED(s)) + service_set_state(s, SERVICE_DEAD); + return 0; } @@ -2126,7 +2136,7 @@ static bool service_will_restart(Unit *u) { static ServiceState service_determine_dead_state(Service *s) { assert(s); - return s->fd_store && s->fd_store_preserve_mode == EXEC_PRESERVE_YES ? SERVICE_DEAD_RESOURCES_PINNED : SERVICE_DEAD; + return SERVICE_FD_STORE_POPULATED(s) && s->fd_store_preserve_mode == EXEC_PRESERVE_YES ? SERVICE_DEAD_RESOURCES_PINNED : SERVICE_DEAD; } static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) { @@ -3165,8 +3175,10 @@ static int service_start(Unit *u) { exec_status_reset(&s->main_exec_status); CGroupRuntime *crt = unit_get_cgroup_runtime(u); - if (crt) + if (crt) { + unit_cgroup_disable_all_controllers(u); crt->reset_accounting = true; + } service_enter_condition(s); return 1; @@ -3950,8 +3962,10 @@ static bool service_may_gc(Unit *u) { return false; /* Only allow collection of actually dead services, i.e. not those that are in the transitionary - * SERVICE_DEAD_BEFORE_AUTO_RESTART/SERVICE_FAILED_BEFORE_AUTO_RESTART states. */ - if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_RESOURCES_PINNED)) + * SERVICE_DEAD_BEFORE_AUTO_RESTART/SERVICE_FAILED_BEFORE_AUTO_RESTART states, and not those + * that still have resources pinned (fd store with FileDescriptorStorePreserve=yes) in case they are + * started again later despite not having any reverse dependency. */ + if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED)) return false; return true; @@ -5196,7 +5210,7 @@ static void service_notify_message( e = empty_to_null(e); - if (e && !string_is_safe_ascii(e)) { + if (e && !string_is_safe(e, STRING_ASCII)) { _cleanup_free_ char *escaped = cescape(e); log_unit_warning(u, "Got invalid %s string, ignoring: %s", i->tag, strna(escaped)); } else if (free_and_strdup_warn(status_error, e) > 0) @@ -5612,7 +5626,7 @@ static int service_clean(Unit *u, ExecCleanMask mask) { /* If we are done, leave quickly */ if (strv_isempty(l)) { - if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !s->fd_store) + if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !SERVICE_FD_STORE_POPULATED(s)) service_set_state(s, SERVICE_DEAD); return 0; } @@ -5629,6 +5643,7 @@ static int service_clean(Unit *u, ExecCleanMask mask) { goto fail; } + unit_cgroup_disable_all_controllers(u); r = unit_fork_and_watch_rm_rf(u, l, &s->control_pid); if (r < 0) { log_unit_warning_errno(u, r, "Failed to spawn cleaning task: %m"); @@ -5865,7 +5880,7 @@ static void service_release_resources(Unit *u) { if (s->fd_store_preserve_mode != EXEC_PRESERVE_YES) service_release_fd_store(s); - if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !s->fd_store) + if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !SERVICE_FD_STORE_POPULATED(s)) service_set_state(s, SERVICE_DEAD); } @@ -5896,7 +5911,7 @@ int service_determine_exec_selinux_label(Service *s, char **ret) { _cleanup_free_ char *path = NULL; if (s->exec_context.root_directory_as_fd) - r = chaseat(s->root_directory_fd, c->path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &path, NULL); + r = chaseat(s->root_directory_fd, s->root_directory_fd, c->path, CHASE_TRIGGER_AUTOFS, &path, NULL); else r = chase(c->path, s->exec_context.root_directory, CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS, &path, NULL); if (r < 0) { diff --git a/src/core/smack-setup.c b/src/core/smack-setup.c index 1e8e2b54e53d2..46c7d0a6e88d2 100644 --- a/src/core/smack-setup.c +++ b/src/core/smack-setup.c @@ -26,6 +26,9 @@ static int fdopen_unlocked_at(int dfd, const char *dir, const char *name, int *s int fd, r; FILE *f; + assert(status); + assert(ret_file); + fd = openat(dfd, name, O_RDONLY|O_CLOEXEC); if (fd < 0) { if (*status == 0) diff --git a/src/core/socket.c b/src/core/socket.c index c18f28aad6843..fbf0dfd9332ae 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -882,16 +882,14 @@ static int instance_from_socket( a = be32toh(local.in.sin_addr.s_addr), b = be32toh(remote.in.sin_addr.s_addr); - if (asprintf(&s, - "%u-%" PRIu64 "-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", - nr, - cookie, - a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF, - be16toh(local.in.sin_port), - b >> 24, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF, - be16toh(remote.in.sin_port)) < 0) - return -ENOMEM; - + s = asprintf_safe( + "%u-%" PRIu64 "-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", + nr, + cookie, + a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF, + be16toh(local.in.sin_port), + b >> 24, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF, + be16toh(remote.in.sin_port)); break; } @@ -906,27 +904,23 @@ static int instance_from_socket( *a = local.in6.sin6_addr.s6_addr+12, *b = remote.in6.sin6_addr.s6_addr+12; - if (asprintf(&s, - "%u-%" PRIu64 "-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", - nr, - cookie, - a[0], a[1], a[2], a[3], - be16toh(local.in6.sin6_port), - b[0], b[1], b[2], b[3], - be16toh(remote.in6.sin6_port)) < 0) - return -ENOMEM; - } else { - if (asprintf(&s, - "%u-%" PRIu64 "-%s:%u-%s:%u", - nr, - cookie, - IN6_ADDR_TO_STRING(&local.in6.sin6_addr), - be16toh(local.in6.sin6_port), - IN6_ADDR_TO_STRING(&remote.in6.sin6_addr), - be16toh(remote.in6.sin6_port)) < 0) - return -ENOMEM; - } - + s = asprintf_safe( + "%u-%" PRIu64 "-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", + nr, + cookie, + a[0], a[1], a[2], a[3], + be16toh(local.in6.sin6_port), + b[0], b[1], b[2], b[3], + be16toh(remote.in6.sin6_port)); + } else + s = asprintf_safe( + "%u-%" PRIu64 "-%s:%u-%s:%u", + nr, + cookie, + IN6_ADDR_TO_STRING(&local.in6.sin6_addr), + be16toh(local.in6.sin6_port), + IN6_ADDR_TO_STRING(&remote.in6.sin6_addr), + be16toh(remote.in6.sin6_port)); break; } @@ -939,42 +933,38 @@ static int instance_from_socket( uint64_t pidfd_id; if (pidfd >= 0 && pidfd_get_inode_id(pidfd, &pidfd_id) >= 0) - r = asprintf(&s, "%u-%" PRIu64 "-" PID_FMT "_%" PRIu64 "-" UID_FMT, - nr, cookie, ucred.pid, pidfd_id, ucred.uid); + s = asprintf_safe( + "%u-%" PRIu64 "-" PID_FMT "_%" PRIu64 "-" UID_FMT, + nr, cookie, ucred.pid, pidfd_id, ucred.uid); else - r = asprintf(&s, "%u-%" PRIu64 "-" PID_FMT "-" UID_FMT, - nr, cookie, ucred.pid, ucred.uid); - if (r < 0) - return -ENOMEM; - } else if (r == -ENODATA) { + s = asprintf_safe( + "%u-%" PRIu64 "-" PID_FMT "-" UID_FMT, + nr, cookie, ucred.pid, ucred.uid); + } else if (r == -ENODATA) /* This handles the case where somebody is connecting from another pid/uid namespace * (e.g. from outside of our container). */ - if (asprintf(&s, - "%u-%" PRIu64 "-unknown", - nr, - cookie) < 0) - return -ENOMEM; - } else + s = asprintf_safe("%u-%" PRIu64 "-unknown", nr, cookie); + else return r; - break; } case AF_VSOCK: - if (asprintf(&s, - "%u-%" PRIu64 "-%u:%u-%u:%u", - nr, - cookie, - local.vm.svm_cid, local.vm.svm_port, - remote.vm.svm_cid, remote.vm.svm_port) < 0) - return -ENOMEM; - + s = asprintf_safe( + "%u-%" PRIu64 "-%u:%u-%u:%u", + nr, + cookie, + local.vm.svm_cid, local.vm.svm_port, + remote.vm.svm_cid, remote.vm.svm_port); break; default: assert_not_reached(); } + if (!s) + return -ENOMEM; + *ret = s; return 0; } @@ -2036,6 +2026,7 @@ static int socket_chown(Socket *s, PidRef *ret_pid) { int r; assert(s); + assert(ret_pid); r = socket_arm_timer(s, /* relative= */ true, s->timeout_usec); if (r < 0) @@ -3535,6 +3526,8 @@ static int socket_get_timeout(Unit *u, usec_t *timeout) { usec_t t; int r; + assert(timeout); + if (!s->timer_event_source) return 0; diff --git a/src/core/swap.c b/src/core/swap.c index 5de1dccf42779..cedabc430dc0c 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -223,6 +223,7 @@ static int swap_add_device_dependencies(Swap *s) { } static int swap_add_default_dependencies(Swap *s) { + SwapParameters *p; int r; assert(s); @@ -236,13 +237,46 @@ static int swap_add_default_dependencies(Swap *s) { if (detect_container() > 0) return 0; - /* swap units generated for the swap dev links are missing the - * ordering dep against the swap target. */ - r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SWAP_TARGET, true, UNIT_DEPENDENCY_DEFAULT); - if (r < 0) - return r; + p = swap_get_parameters(s); - return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, true, UNIT_DEPENDENCY_DEFAULT); + if (p && fstab_test_option(p->options, "_netdev\0")) { + /* Network swap devices (those with _netdev in options) are routed through + * remote-fs.target instead of swap.target, mirroring how network mounts use + * remote-fs.target instead of local-fs.target. This avoids an ordering cycle: + * swap.target is pulled in at sysinit.target time, but network-online.target + * only comes after basic.target which is after sysinit.target. */ + r = unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_REMOTE_FS_PRE_TARGET, + /* add_reference= */ true, UNIT_DEPENDENCY_DEFAULT); + if (r < 0) + return r; + + r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_REMOTE_FS_TARGET, + /* add_reference= */ true, UNIT_DEPENDENCY_DEFAULT); + if (r < 0) + return r; + + /* Pull in and order after network-online.target, analogous to + * mount_add_default_network_dependencies() for network mounts. */ + r = unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_NETWORK_TARGET, + /* add_reference= */ true, UNIT_DEPENDENCY_DEFAULT); + if (r < 0) + return r; + + r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_WANTS, UNIT_AFTER, SPECIAL_NETWORK_ONLINE_TARGET, + /* add_reference= */ true, UNIT_DEPENDENCY_DEFAULT); + if (r < 0) + return r; + } else { + /* swap units generated for the swap dev links are missing the + * ordering dep against the swap target. */ + r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SWAP_TARGET, + /* add_reference= */ true, UNIT_DEPENDENCY_DEFAULT); + if (r < 0) + return r; + } + + return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, + /* add_reference= */ true, UNIT_DEPENDENCY_DEFAULT); } static int swap_verify(Swap *s) { @@ -1427,6 +1461,8 @@ static int swap_get_timeout(Unit *u, usec_t *timeout) { usec_t t; int r; + assert(timeout); + if (!s->timer_event_source) return 0; diff --git a/src/core/system.conf.in b/src/core/system.conf.in index 54196e84894df..63d28059305fe 100644 --- a/src/core/system.conf.in +++ b/src/core/system.conf.in @@ -37,6 +37,7 @@ #RebootWatchdogSec=10min #KExecWatchdogSec=off #WatchdogDevice= +#MinimumUptimeSec=15s #CapabilityBoundingSet= #NoNewPrivileges=no #ProtectSystem=auto @@ -77,8 +78,13 @@ #DefaultLimitRTTIME= #DefaultMemoryPressureThresholdSec=200ms #DefaultMemoryPressureWatch=auto +#DefaultCPUPressureThresholdSec=200ms +#DefaultCPUPressureWatch=auto +#DefaultIOPressureThresholdSec=200ms +#DefaultIOPressureWatch=auto #DefaultOOMPolicy=stop #DefaultSmackProcessLabel= #DefaultRestrictSUIDSGID= #ReloadLimitIntervalSec= #ReloadLimitBurst= +#DefaultMemoryZSwapWriteback=yes diff --git a/src/core/timer.c b/src/core/timer.c index c591fcd469c7a..510d8e1995774 100644 --- a/src/core/timer.c +++ b/src/core/timer.c @@ -940,6 +940,7 @@ static int activation_details_timer_deserialize(const char *key, const char *val assert(key); assert(value); + POINTER_MAY_BE_NULL(details); if (!details || !*details) return -EINVAL; diff --git a/src/core/unit-printf.c b/src/core/unit-printf.c index 473f7c7d20d2c..8c168b0bb0e7d 100644 --- a/src/core/unit-printf.c +++ b/src/core/unit-printf.c @@ -50,6 +50,8 @@ static int specifier_last_component(char specifier, const void *data, const char char *dash; int r; + assert(ret); + r = unit_name_to_prefix(u->id, &prefix); if (r < 0) return r; diff --git a/src/core/unit.c b/src/core/unit.c index bb3430186cab0..3404d7f8d3a76 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -132,6 +132,8 @@ int unit_new_for_name(Manager *m, size_t size, const char *name, Unit **ret) { _cleanup_(unit_freep) Unit *u = NULL; int r; + assert(ret); + u = unit_new(m, size); if (!u) return -ENOMEM; @@ -178,8 +180,9 @@ static void unit_init(Unit *u) { if (u->type != UNIT_SLICE) cc->tasks_max = u->manager->defaults.tasks_max; - cc->memory_pressure_watch = u->manager->defaults.memory_pressure_watch; - cc->memory_pressure_threshold_usec = u->manager->defaults.memory_pressure_threshold_usec; + cc->memory_zswap_writeback = u->manager->defaults.memory_zswap_writeback; + + memcpy(cc->pressure, u->manager->defaults.pressure, sizeof(cc->pressure)); } ec = unit_get_exec_context(u); @@ -677,6 +680,8 @@ static void unit_remove_transient(Unit *u) { if (!u->transient) return; + const char *dropin_directory = strjoina(u->id, ".d"); + STRV_FOREACH(i, u->dropin_paths) { _cleanup_free_ char *p = NULL, *pp = NULL; @@ -690,6 +695,10 @@ static void unit_remove_transient(Unit *u) { if (!path_equal(u->manager->lookup_paths.transient, pp)) continue; + /* Drop the transient drop-in directory also from unit path cache. */ + if (path_equal(last_path_component(p), dropin_directory)) + free(set_remove(u->manager->unit_path_cache, p)); + (void) unlink(*i); (void) rmdir(p); } @@ -4294,6 +4303,9 @@ static int user_from_unit_name(Unit *u, char **ret) { _cleanup_free_ char *n = NULL; int r; + assert(u); + assert(ret); + r = unit_name_to_prefix(u->id, &n); if (r < 0) return r; @@ -5823,12 +5835,6 @@ static int unit_export_log_level_max(Unit *u, int log_level_max, bool overwrite) } static int unit_export_log_extra_fields(Unit *u, const ExecContext *c) { - _cleanup_close_ int fd = -EBADF; - struct iovec *iovec; - const char *p; - char *pattern; - le64_t *sizes; - ssize_t n; int r; if (u->exported_log_extra_fields) @@ -5837,8 +5843,10 @@ static int unit_export_log_extra_fields(Unit *u, const ExecContext *c) { if (c->n_log_extra_fields <= 0) return 0; - sizes = newa(le64_t, c->n_log_extra_fields); - iovec = newa(struct iovec, c->n_log_extra_fields * 2); + assert(c->n_log_extra_fields <= LOG_EXTRA_FIELDS_MAX); + + le64_t *sizes = newa(le64_t, c->n_log_extra_fields); + struct iovec *iovec = newa(struct iovec, c->n_log_extra_fields * 2); for (size_t i = 0; i < c->n_log_extra_fields; i++) { sizes[i] = htole64(c->log_extra_fields[i].iov_len); @@ -5847,15 +5855,14 @@ static int unit_export_log_extra_fields(Unit *u, const ExecContext *c) { iovec[i*2+1] = c->log_extra_fields[i]; } - p = strjoina("/run/systemd/units/log-extra-fields:", u->id); - pattern = strjoina(p, ".XXXXXX"); + const char *p = strjoina("/run/systemd/units/log-extra-fields:", u->id); + char *pattern = strjoina(p, ".XXXXXX"); - fd = mkostemp_safe(pattern); + _cleanup_close_ int fd = mkostemp_safe(pattern); if (fd < 0) return log_unit_debug_errno(u, fd, "Failed to create extra fields file %s: %m", p); - n = writev(fd, iovec, c->n_log_extra_fields*2); - if (n < 0) { + if (writev(fd, iovec, c->n_log_extra_fields * 2) < 0) { r = log_unit_debug_errno(u, errno, "Failed to write extra fields: %m"); goto fail; } @@ -5876,8 +5883,6 @@ static int unit_export_log_extra_fields(Unit *u, const ExecContext *c) { } static int unit_export_log_ratelimit_interval(Unit *u, const ExecContext *c) { - _cleanup_free_ char *buf = NULL; - const char *p; int r; assert(u); @@ -5889,10 +5894,10 @@ static int unit_export_log_ratelimit_interval(Unit *u, const ExecContext *c) { if (c->log_ratelimit.interval == 0) return 0; - p = strjoina("/run/systemd/units/log-rate-limit-interval:", u->id); + const char *p = strjoina("/run/systemd/units/log-rate-limit-interval:", u->id); - if (asprintf(&buf, "%" PRIu64, c->log_ratelimit.interval) < 0) - return log_oom(); + char buf[DECIMAL_STR_MAX(c->log_ratelimit.interval)]; + xsprintf(buf, "%" PRIu64, c->log_ratelimit.interval); r = symlink_atomic(buf, p); if (r < 0) @@ -5903,8 +5908,6 @@ static int unit_export_log_ratelimit_interval(Unit *u, const ExecContext *c) { } static int unit_export_log_ratelimit_burst(Unit *u, const ExecContext *c) { - _cleanup_free_ char *buf = NULL; - const char *p; int r; assert(u); @@ -5916,10 +5919,10 @@ static int unit_export_log_ratelimit_burst(Unit *u, const ExecContext *c) { if (c->log_ratelimit.burst == 0) return 0; - p = strjoina("/run/systemd/units/log-rate-limit-burst:", u->id); + const char *p = strjoina("/run/systemd/units/log-rate-limit-burst:", u->id); - if (asprintf(&buf, "%u", c->log_ratelimit.burst) < 0) - return log_oom(); + char buf[DECIMAL_STR_MAX(c->log_ratelimit.burst)]; + xsprintf(buf, "%u", c->log_ratelimit.burst); r = symlink_atomic(buf, p); if (r < 0) @@ -6380,6 +6383,7 @@ int unit_clean(Unit *u, ExecCleanMask mask) { int unit_can_clean(Unit *u, ExecCleanMask *ret) { assert(u); + assert(ret); if (!UNIT_VTABLE(u)->clean || u->load_state != UNIT_LOADED) { diff --git a/src/core/unit.h b/src/core/unit.h index 9c94113239ee0..e503c2e344af5 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -10,6 +10,7 @@ #include "install.h" #include "iterator.h" #include "job.h" +#include "journal-def.h" #include "list.h" #include "log.h" #include "log-context.h" @@ -1097,6 +1098,11 @@ int unit_queue_job_check_and_mangle_type(Unit *u, JobType *type, bool reload_if_ int parse_unit_marker(const char *marker, unsigned *settings, unsigned *mask); unsigned unit_normalize_markers(unsigned existing_markers, unsigned new_markers); +/* Trying to log with too many fields is going to fail. We need at least also MESSAGE=, + * but we generally log a few extra in most cases. So let's reserve 10. Anything + * above a few would be very unusual, but let's not be overly strict. */ +#define LOG_EXTRA_FIELDS_MAX (ENTRY_FIELD_COUNT_MAX - 10) + /* Macros which append UNIT= or USER_UNIT= to the message */ #define log_unit_full_errno_zerook(unit, level, error, ...) \ diff --git a/src/core/user.conf.in b/src/core/user.conf.in index 9c37f4b54e9bd..33c6733268c08 100644 --- a/src/core/user.conf.in +++ b/src/core/user.conf.in @@ -54,6 +54,10 @@ #DefaultLimitRTTIME= #DefaultMemoryPressureThresholdSec=200ms #DefaultMemoryPressureWatch=auto +#DefaultCPUPressureThresholdSec=200ms +#DefaultCPUPressureWatch=auto +#DefaultIOPressureThresholdSec=200ms +#DefaultIOPressureWatch=auto #DefaultSmackProcessLabel= #DefaultRestrictSUIDSGID= #ReloadLimitIntervalSec= diff --git a/src/core/varlink-automount.c b/src/core/varlink-automount.c new file mode 100644 index 0000000000000..2bb5fa397d043 --- /dev/null +++ b/src/core/varlink-automount.c @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "automount.h" +#include "json-util.h" +#include "varlink-automount.h" + +int automount_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Automount *a = ASSERT_PTR(AUTOMOUNT(userdata)); + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Where", a->where), + JSON_BUILD_PAIR_STRING_NON_EMPTY("ExtraOptions", a->extra_options), + SD_JSON_BUILD_PAIR_UNSIGNED("DirectoryMode", a->directory_mode), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutIdleUSec", a->timeout_idle_usec)); +} + +int automount_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Automount *a = ASSERT_PTR(AUTOMOUNT(userdata)); + return sd_json_buildo(ASSERT_PTR(ret), JSON_BUILD_PAIR_ENUM("Result", automount_result_to_string(a->result))); +} diff --git a/src/core/varlink-automount.h b/src/core/varlink-automount.h new file mode 100644 index 0000000000000..892f8ba65b9f6 --- /dev/null +++ b/src/core/varlink-automount.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int automount_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int automount_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata); diff --git a/src/core/varlink-cgroup.c b/src/core/varlink-cgroup.c index 65c07ecfad477..3793975e8cc2e 100644 --- a/src/core/varlink-cgroup.c +++ b/src/core/varlink-cgroup.c @@ -194,6 +194,8 @@ static int device_allow_build_json(sd_json_variant **ret, const char *name, void CGroupDeviceAllow *allow = userdata; int r; + assert(ret); + LIST_FOREACH(device_allow, a, allow) { r = sd_json_variant_append_arraybo( &v, @@ -219,7 +221,7 @@ static int controllers_build_json(sd_json_variant **ret, const char *name, void if (!FLAGS_SET(*mask, CGROUP_CONTROLLER_TO_MASK(ctrl))) continue; - r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_STRING(cgroup_controller_to_string(ctrl))); + r = sd_json_variant_append_arrayb(&v, JSON_BUILD_STRING_UNDERSCORIFY(cgroup_controller_to_string(ctrl))); if (r < 0) return r; } @@ -309,7 +311,7 @@ int unit_cgroup_context_build_json(sd_json_variant **ret, const char *name, void /* Device Access */ JSON_BUILD_PAIR_CALLBACK_NON_NULL("DeviceAllow", device_allow_build_json, c->device_allow), - SD_JSON_BUILD_PAIR_STRING("DevicePolicy", cgroup_device_policy_to_string(c->device_policy)), + JSON_BUILD_PAIR_ENUM("DevicePolicy", cgroup_device_policy_to_string(c->device_policy)), /* Control Group Management */ SD_JSON_BUILD_PAIR_BOOLEAN("Delegate", c->delegate), @@ -318,13 +320,17 @@ int unit_cgroup_context_build_json(sd_json_variant **ret, const char *name, void JSON_BUILD_PAIR_CALLBACK_NON_NULL("DisableControllers", controllers_build_json, &c->disable_controllers), /* Memory Pressure Control */ - SD_JSON_BUILD_PAIR_STRING("ManagedOOMSwap", managed_oom_mode_to_string(c->moom_swap)), - SD_JSON_BUILD_PAIR_STRING("ManagedOOMMemoryPressure", managed_oom_mode_to_string(c->moom_mem_pressure)), + JSON_BUILD_PAIR_ENUM("ManagedOOMSwap", managed_oom_mode_to_string(c->moom_swap)), + JSON_BUILD_PAIR_ENUM("ManagedOOMMemoryPressure", managed_oom_mode_to_string(c->moom_mem_pressure)), JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("ManagedOOMMemoryPressureLimit", c->moom_mem_pressure_limit), JSON_BUILD_PAIR_FINITE_USEC("ManagedOOMMemoryPressureDurationUSec", c->moom_mem_pressure_duration_usec), - SD_JSON_BUILD_PAIR_STRING("ManagedOOMPreference", managed_oom_preference_to_string(c->moom_preference)), - SD_JSON_BUILD_PAIR_STRING("MemoryPressureWatch", cgroup_pressure_watch_to_string(c->memory_pressure_watch)), - JSON_BUILD_PAIR_FINITE_USEC("MemoryPressureThresholdUSec", c->memory_pressure_threshold_usec), + JSON_BUILD_PAIR_ENUM("ManagedOOMPreference", managed_oom_preference_to_string(c->moom_preference)), + JSON_BUILD_PAIR_ENUM("MemoryPressureWatch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_MEMORY].watch)), + JSON_BUILD_PAIR_FINITE_USEC("MemoryPressureThresholdUSec", c->pressure[PRESSURE_MEMORY].threshold_usec), + JSON_BUILD_PAIR_ENUM("CPUPressureWatch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_CPU].watch)), + JSON_BUILD_PAIR_FINITE_USEC("CPUPressureThresholdUSec", c->pressure[PRESSURE_CPU].threshold_usec), + JSON_BUILD_PAIR_ENUM("IOPressureWatch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_IO].watch)), + JSON_BUILD_PAIR_FINITE_USEC("IOPressureThresholdUSec", c->pressure[PRESSURE_IO].threshold_usec), /* Others */ SD_JSON_BUILD_PAIR_BOOLEAN("CoredumpReceive", c->coredump_receive)); diff --git a/src/core/varlink-common.c b/src/core/varlink-common.c index e388b1633929d..bdef4d5a9471d 100644 --- a/src/core/varlink-common.c +++ b/src/core/varlink-common.c @@ -4,6 +4,7 @@ #include "bus-common-errors.h" #include "cpu-set-util.h" +#include "execute.h" #include "json-util.h" #include "rlimit-util.h" #include "varlink-common.h" @@ -104,3 +105,24 @@ int cpuset_build_json(sd_json_variant **ret, const char *name, void *userdata) { *ret = NULL; return 0; } + +int exec_command_build_json(sd_json_variant **ret, const char *name, void *userdata) { + ExecCommand *cmd = ASSERT_PTR(userdata); + + assert(ret); + + if (isempty(cmd->path)) { + *ret = NULL; + return 0; + } + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("path", cmd->path), + JSON_BUILD_PAIR_STRV_NON_EMPTY("arguments", cmd->argv), + SD_JSON_BUILD_PAIR_BOOLEAN("ignoreFailure", FLAGS_SET(cmd->flags, EXEC_COMMAND_IGNORE_FAILURE)), + SD_JSON_BUILD_PAIR_BOOLEAN("privileged", FLAGS_SET(cmd->flags, EXEC_COMMAND_FULLY_PRIVILEGED)), + SD_JSON_BUILD_PAIR_BOOLEAN("noSetuid", FLAGS_SET(cmd->flags, EXEC_COMMAND_NO_SETUID)), + SD_JSON_BUILD_PAIR_BOOLEAN("noEnvExpand", FLAGS_SET(cmd->flags, EXEC_COMMAND_NO_ENV_EXPAND)), + SD_JSON_BUILD_PAIR_BOOLEAN("viaShell", FLAGS_SET(cmd->flags, EXEC_COMMAND_VIA_SHELL))); +} diff --git a/src/core/varlink-common.h b/src/core/varlink-common.h index 82d1458dd9609..fc919dcf36c74 100644 --- a/src/core/varlink-common.h +++ b/src/core/varlink-common.h @@ -6,5 +6,5 @@ int rlimit_build_json(sd_json_variant **ret, const char *name, void *userdata); int rlimit_table_build_json(sd_json_variant **ret, const char *name, void *userdata); int cpuset_build_json(sd_json_variant **ret, const char *name, void *userdata); - const char* varlink_error_id_from_bus_error(const sd_bus_error *e); +int exec_command_build_json(sd_json_variant **ret, const char *name, void *userdata); diff --git a/src/core/varlink-dynamic-user.c b/src/core/varlink-dynamic-user.c index 3f27a1f89140f..c7e851d005242 100644 --- a/src/core/varlink-dynamic-user.c +++ b/src/core/varlink-dynamic-user.c @@ -10,7 +10,6 @@ #include "uid-classification.h" #include "user-util.h" #include "varlink-dynamic-user.h" -#include "varlink-util.h" typedef struct LookupParameters { const char *user_name; @@ -78,7 +77,7 @@ int vl_method_get_user_record(sd_varlink *link, sd_json_variant *parameters, sd_ if (!streq_ptr(p.service, "io.systemd.DynamicUser")) return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; @@ -182,7 +181,7 @@ int vl_method_get_group_record(sd_varlink *link, sd_json_variant *parameters, sd if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; diff --git a/src/core/varlink-execute.c b/src/core/varlink-execute.c index e6efd5989597b..21d735183fc4a 100644 --- a/src/core/varlink-execute.c +++ b/src/core/varlink-execute.c @@ -27,6 +27,9 @@ #include "varlink-common.h" #include "varlink-execute.h" +#define JSON_BUILD_PAIR_MOUNT_PROPAGATION_FLAG(name, s) \ + SD_JSON_BUILD_PAIR_CONDITION(!isempty(s), name, JSON_BUILD_STRING_UNDERSCORIFY(s)) + static int working_directory_build_json(sd_json_variant **ret, const char *name, void *userdata) { ExecContext *c = ASSERT_PTR(userdata); @@ -262,7 +265,7 @@ static int cpu_sched_class_build_json(sd_json_variant **ret, const char *name, v if (r < 0) return log_debug_errno(r, "Failed to convert sched policy to string: %m"); - return sd_json_variant_new_string(ret, s); + return sd_json_variant_new_string(ret, json_underscorify(s)); } static int cpu_affinity_build_json(sd_json_variant **ret, const char *name, void *userdata) { @@ -343,7 +346,7 @@ static int ioprio_class_build_json(sd_json_variant **ret, const char *name, void if (r < 0) return log_debug_errno(r, "Failed to convert IO priority class to string: %m"); - return sd_json_variant_new_string(ret, s); + return sd_json_variant_new_string(ret, json_underscorify(s)); } static int exec_dir_build_json(sd_json_variant **ret, const char *name, void *userdata) { @@ -470,48 +473,56 @@ static int private_bpf_delegate_commands_build_json(sd_json_variant **ret, const ExecContext *c = ASSERT_PTR(userdata); _cleanup_free_ char *v = bpf_delegate_commands_to_string(c->bpf_delegate_commands); + assert(ret); + if (!v) { *ret = NULL; return 0; } - return sd_json_variant_new_string(ASSERT_PTR(ret), v); + return sd_json_variant_new_string(ret, v); } static int private_bpf_delegate_maps_build_json(sd_json_variant **ret, const char *name, void *userdata) { ExecContext *c = ASSERT_PTR(userdata); _cleanup_free_ char *v = bpf_delegate_maps_to_string(c->bpf_delegate_maps); + assert(ret); + if (!v) { *ret = NULL; return 0; } - return sd_json_variant_new_string(ASSERT_PTR(ret), v); + return sd_json_variant_new_string(ret, v); } static int private_bpf_delegate_programs_build_json(sd_json_variant **ret, const char *name, void *userdata) { ExecContext *c = ASSERT_PTR(userdata); _cleanup_free_ char *v = bpf_delegate_programs_to_string(c->bpf_delegate_programs); + assert(ret); + if (!v) { *ret = NULL; return 0; } - return sd_json_variant_new_string(ASSERT_PTR(ret), v); + return sd_json_variant_new_string(ret, v); } static int private_bpf_delegate_attachments_build_json(sd_json_variant **ret, const char *name, void *userdata) { ExecContext *c = ASSERT_PTR(userdata); _cleanup_free_ char *v = bpf_delegate_attachments_to_string(c->bpf_delegate_attachments); + assert(ret); + if (!v) { *ret = NULL; return 0; } - return sd_json_variant_new_string(ASSERT_PTR(ret), v); + return sd_json_variant_new_string(ret, v); } static int syscall_filter_build_json(sd_json_variant **ret, const char *name, void *userdata) { @@ -781,6 +792,9 @@ static int set_credential_build_json(sd_json_variant **ret, const char *name, vo int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { Unit *u = ASSERT_PTR(userdata); ExecContext *c = unit_get_exec_context(u); + + assert(ret); + if (!c) { *ret = NULL; return 0; @@ -807,8 +821,8 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void * SD_JSON_BUILD_PAIR_CALLBACK("ExtensionImagePolicy", image_policy_build_json, c->extension_image_policy), JSON_BUILD_PAIR_YES_NO("MountAPIVFS", exec_context_get_effective_mount_apivfs(c)), SD_JSON_BUILD_PAIR_BOOLEAN("BindLogSockets", exec_context_get_effective_bind_log_sockets(c)), - SD_JSON_BUILD_PAIR_STRING("ProtectProc", protect_proc_to_string(c->protect_proc)), - SD_JSON_BUILD_PAIR_STRING("ProcSubset", proc_subset_to_string(c->proc_subset)), + JSON_BUILD_PAIR_ENUM("ProtectProc", protect_proc_to_string(c->protect_proc)), + JSON_BUILD_PAIR_ENUM("ProcSubset", proc_subset_to_string(c->proc_subset)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("BindPaths", bind_paths_build_json, c), JSON_BUILD_PAIR_CALLBACK_NON_NULL("BindReadOnlyPaths", bind_paths_build_json, c), JSON_BUILD_PAIR_CALLBACK_NON_NULL("MountImages", mount_images_build_json, c), @@ -849,7 +863,7 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void * SD_JSON_BUILD_PAIR_CALLBACK("Limits", rlimit_table_with_defaults_build_json, u), JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("UMask", c->umask), JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("CoredumpFilter", exec_context_get_coredump_filter(c)), - SD_JSON_BUILD_PAIR_STRING("KeyringMode", exec_keyring_mode_to_string(c->keyring_mode)), + JSON_BUILD_PAIR_ENUM("KeyringMode", exec_keyring_mode_to_string(c->keyring_mode)), JSON_BUILD_PAIR_INTEGER_NON_ZERO("OOMScoreAdjust", exec_context_get_oom_score_adjust(c)), JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("TimerSlackNSec", exec_context_get_timer_slack_nsec(c), NSEC_INFINITY), JSON_BUILD_PAIR_STRING_NON_EMPTY("Personality", personality_to_string(c->personality)), @@ -867,17 +881,17 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void * SD_JSON_BUILD_PAIR_INTEGER("IOSchedulingPriority", ioprio_prio_data(exec_context_get_effective_ioprio(c))), JSON_BUILD_PAIR_TRISTATE_NON_NULL("MemoryKSM", c->memory_ksm), - SD_JSON_BUILD_PAIR_STRING("MemoryTHP", memory_thp_to_string(c->memory_thp)), + JSON_BUILD_PAIR_ENUM("MemoryTHP", memory_thp_to_string(c->memory_thp)), /* Sandboxing */ - SD_JSON_BUILD_PAIR_STRING("ProtectSystem", protect_system_to_string(c->protect_system)), - SD_JSON_BUILD_PAIR_STRING("ProtectHome", protect_home_to_string(c->protect_home)), + JSON_BUILD_PAIR_ENUM("ProtectSystem", protect_system_to_string(c->protect_system)), + JSON_BUILD_PAIR_ENUM("ProtectHome", protect_home_to_string(c->protect_home)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("RuntimeDirectory", exec_dir_build_json, &c->directories[EXEC_DIRECTORY_RUNTIME]), JSON_BUILD_PAIR_CALLBACK_NON_NULL("StateDirectory", exec_dir_build_json, &c->directories[EXEC_DIRECTORY_STATE]), JSON_BUILD_PAIR_CALLBACK_NON_NULL("CacheDirectory", exec_dir_build_json, &c->directories[EXEC_DIRECTORY_CACHE]), JSON_BUILD_PAIR_CALLBACK_NON_NULL("LogsDirectory", exec_dir_build_json, &c->directories[EXEC_DIRECTORY_LOGS]), JSON_BUILD_PAIR_CALLBACK_NON_NULL("ConfigurationDirectory", exec_dir_build_json, &c->directories[EXEC_DIRECTORY_CONFIGURATION]), - SD_JSON_BUILD_PAIR_STRING("RuntimeDirectoryPreserve", exec_preserve_mode_to_string(c->runtime_directory_preserve_mode)), + JSON_BUILD_PAIR_ENUM("RuntimeDirectoryPreserve", exec_preserve_mode_to_string(c->runtime_directory_preserve_mode)), JSON_BUILD_PAIR_FINITE_USEC("TimeoutCleanUSec", c->timeout_clean_usec), JSON_BUILD_PAIR_STRV_NON_EMPTY("ReadWritePaths", c->read_write_paths), JSON_BUILD_PAIR_STRV_NON_EMPTY("ReadOnlyPaths", c->read_only_paths), @@ -886,26 +900,26 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void * JSON_BUILD_PAIR_STRV_NON_EMPTY("NoExecPaths", c->no_exec_paths), JSON_BUILD_PAIR_CALLBACK_NON_NULL("TemporaryFileSystem", temporary_filesystems_build_json, c), /* XXX should we make all these Private/Protect strings??? */ - SD_JSON_BUILD_PAIR_STRING("PrivateTmp", private_tmp_to_string(c->private_tmp)), + JSON_BUILD_PAIR_ENUM("PrivateTmp", private_tmp_to_string(c->private_tmp)), JSON_BUILD_PAIR_YES_NO("PrivateDevices", c->private_devices), JSON_BUILD_PAIR_YES_NO("PrivateNetwork", c->private_network), JSON_BUILD_PAIR_STRING_NON_EMPTY("NetworkNamespacePath", c->network_namespace_path), JSON_BUILD_PAIR_YES_NO("PrivateIPC", c->private_ipc), JSON_BUILD_PAIR_STRING_NON_EMPTY("IPCNamespacePath", c->ipc_namespace_path), - SD_JSON_BUILD_PAIR_STRING("PrivatePIDs", private_pids_to_string(c->private_pids)), - SD_JSON_BUILD_PAIR_STRING("PrivateUsers", private_users_to_string(c->private_users)), + JSON_BUILD_PAIR_ENUM("PrivatePIDs", private_pids_to_string(c->private_pids)), + JSON_BUILD_PAIR_ENUM("PrivateUsers", private_users_to_string(c->private_users)), JSON_BUILD_PAIR_STRING_NON_EMPTY("UserNamespacePath", c->user_namespace_path), - SD_JSON_BUILD_PAIR_STRING("ProtectHostname", protect_hostname_to_string(c->protect_hostname)), + JSON_BUILD_PAIR_ENUM("ProtectHostname", protect_hostname_to_string(c->protect_hostname)), JSON_BUILD_PAIR_YES_NO("ProtectClock", c->protect_clock), JSON_BUILD_PAIR_YES_NO("ProtectKernelTunables", c->protect_kernel_tunables), JSON_BUILD_PAIR_YES_NO("ProtectKernelModules", c->protect_kernel_modules), JSON_BUILD_PAIR_YES_NO("ProtectKernelLogs", c->protect_kernel_logs), - SD_JSON_BUILD_PAIR_STRING("ProtectControlGroups", protect_control_groups_to_string(c->protect_control_groups)), + JSON_BUILD_PAIR_ENUM("ProtectControlGroups", protect_control_groups_to_string(c->protect_control_groups)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("RestrictAddressFamilies", address_families_build_json, c), JSON_BUILD_PAIR_CALLBACK_NON_NULL("RestrictFileSystems", restrict_filesystems_build_json, c), JSON_BUILD_PAIR_CALLBACK_NON_NULL("RestrictNamespaces", namespace_flags_build_json, ULONG_TO_PTR(c->restrict_namespaces)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("DelegateNamespaces", namespace_flags_build_json, ULONG_TO_PTR(c->delegate_namespaces)), - SD_JSON_BUILD_PAIR_STRING("PrivatePBF", private_bpf_to_string(c->private_bpf)), + JSON_BUILD_PAIR_ENUM("PrivatePBF", private_bpf_to_string(c->private_bpf)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("BPFDelegateCommands", private_bpf_delegate_commands_build_json, c), JSON_BUILD_PAIR_CALLBACK_NON_NULL("BPFDelegateMaps", private_bpf_delegate_maps_build_json, c), JSON_BUILD_PAIR_CALLBACK_NON_NULL("BPFDelegatePrograms", private_bpf_delegate_programs_build_json, c), @@ -916,7 +930,7 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void * SD_JSON_BUILD_PAIR_BOOLEAN("RestrictSUIDSGID", c->restrict_suid_sgid), SD_JSON_BUILD_PAIR_BOOLEAN("RemoveIPC", c->remove_ipc), JSON_BUILD_PAIR_TRISTATE_NON_NULL("PrivateMounts", c->private_mounts), - JSON_BUILD_PAIR_STRING_NON_EMPTY("MountFlags", mount_propagation_flag_to_string(c->mount_propagation_flag)), + JSON_BUILD_PAIR_MOUNT_PROPAGATION_FLAG("MountFlags", mount_propagation_flag_to_string(c->mount_propagation_flag)), /* System Call Filtering */ JSON_BUILD_PAIR_CALLBACK_NON_NULL("SystemCallFilter", syscall_filter_build_json, c), @@ -931,9 +945,9 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void * JSON_BUILD_PAIR_STRV_NON_EMPTY("UnsetEnvironment", c->unset_environment), /* Logging and Standard Input/Output */ - SD_JSON_BUILD_PAIR_STRING("StandardInput", exec_input_to_string(c->std_input)), - SD_JSON_BUILD_PAIR_STRING("StandardOutput", exec_output_to_string(c->std_output)), - SD_JSON_BUILD_PAIR_STRING("StandardError", exec_output_to_string(c->std_error)), + JSON_BUILD_PAIR_ENUM("StandardInput", exec_input_to_string(c->std_input)), + JSON_BUILD_PAIR_ENUM("StandardOutput", exec_output_to_string(c->std_output)), + JSON_BUILD_PAIR_ENUM("StandardError", exec_output_to_string(c->std_error)), JSON_BUILD_PAIR_STRING_NON_EMPTY("StandardInputFileDescriptorName", exec_context_fdname(c, STDIN_FILENO)), JSON_BUILD_PAIR_STRING_NON_EMPTY("StandardOutputFileDescriptorName", exec_context_fdname(c, STDOUT_FILENO)), JSON_BUILD_PAIR_STRING_NON_EMPTY("StandardErrorFileDescriptorName", exec_context_fdname(c, STDERR_FILENO)), @@ -963,5 +977,5 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void * /* System V Compatibility */ JSON_BUILD_PAIR_STRING_NON_EMPTY("UtmpIdentifier", c->utmp_id), - SD_JSON_BUILD_PAIR_STRING("UtmpMode", exec_utmp_mode_to_string(c->utmp_mode))); + JSON_BUILD_PAIR_ENUM("UtmpMode", exec_utmp_mode_to_string(c->utmp_mode))); } diff --git a/src/core/varlink-kill.c b/src/core/varlink-kill.c new file mode 100644 index 0000000000000..8b48da098a7eb --- /dev/null +++ b/src/core/varlink-kill.c @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "json-util.h" +#include "kill.h" +#include "signal-util.h" +#include "varlink-kill.h" + +int unit_kill_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + KillContext *c = userdata; + + assert(ret); + + if (!c) { + *ret = NULL; + return 0; + } + + return sd_json_buildo( + ret, + JSON_BUILD_PAIR_ENUM("KillMode", kill_mode_to_string(c->kill_mode)), + SD_JSON_BUILD_PAIR_STRING("KillSignal", signal_to_string(c->kill_signal)), + SD_JSON_BUILD_PAIR_STRING("RestartKillSignal", signal_to_string(restart_kill_signal(c))), + SD_JSON_BUILD_PAIR_BOOLEAN("SendSIGHUP", c->send_sighup), + SD_JSON_BUILD_PAIR_BOOLEAN("SendSIGKILL", c->send_sigkill), + SD_JSON_BUILD_PAIR_STRING("FinalKillSignal", signal_to_string(c->final_kill_signal)), + SD_JSON_BUILD_PAIR_STRING("WatchdogSignal", signal_to_string(c->watchdog_signal))); +} diff --git a/src/core/varlink-kill.h b/src/core/varlink-kill.h new file mode 100644 index 0000000000000..a894e89ad68d3 --- /dev/null +++ b/src/core/varlink-kill.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int unit_kill_context_build_json(sd_json_variant **ret, const char *name, void *userdata); diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index d00f7e5a248a7..96287dc32f05e 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -16,6 +16,7 @@ #include "glyph-util.h" #include "json-util.h" #include "manager.h" +#include "path-util.h" #include "pidref.h" #include "selinux-access.h" #include "set.h" @@ -87,10 +88,10 @@ static int manager_context_build_json(sd_json_variant **ret, const char *name, v ASSERT_PTR(ret), SD_JSON_BUILD_PAIR_BOOLEAN("ShowStatus", manager_get_show_status_on(m)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("LogLevel", log_level_build_json, m), - SD_JSON_BUILD_PAIR_STRING("LogTarget", log_target_to_string(log_get_target())), + JSON_BUILD_PAIR_ENUM("LogTarget", log_target_to_string(log_get_target())), JSON_BUILD_PAIR_CALLBACK_NON_NULL("Environment", manager_environment_build_json, m), - SD_JSON_BUILD_PAIR_STRING("DefaultStandardOutput", exec_output_to_string(m->defaults.std_output)), - SD_JSON_BUILD_PAIR_STRING("DefaultStandardError", exec_output_to_string(m->defaults.std_error)), + JSON_BUILD_PAIR_ENUM("DefaultStandardOutput", exec_output_to_string(m->defaults.std_output)), + JSON_BUILD_PAIR_ENUM("DefaultStandardError", exec_output_to_string(m->defaults.std_error)), SD_JSON_BUILD_PAIR_BOOLEAN("ServiceWatchdogs", m->service_watchdogs), JSON_BUILD_PAIR_FINITE_USEC("DefaultTimerAccuracyUSec", m->defaults.timer_accuracy_usec), JSON_BUILD_PAIR_FINITE_USEC("DefaultTimeoutStartUSec", m->defaults.timeout_start_usec), @@ -105,8 +106,12 @@ static int manager_context_build_json(sd_json_variant **ret, const char *name, v SD_JSON_BUILD_PAIR_BOOLEAN("DefaultTasksAccounting", m->defaults.tasks_accounting), SD_JSON_BUILD_PAIR_CALLBACK("DefaultLimits", rlimit_table_build_json, m->defaults.rlimit), SD_JSON_BUILD_PAIR_UNSIGNED("DefaultTasksMax", cgroup_tasks_max_resolve(&m->defaults.tasks_max)), - JSON_BUILD_PAIR_FINITE_USEC("DefaultMemoryPressureThresholdUSec", m->defaults.memory_pressure_threshold_usec), - SD_JSON_BUILD_PAIR_STRING("DefaultMemoryPressureWatch", cgroup_pressure_watch_to_string(m->defaults.memory_pressure_watch)), + JSON_BUILD_PAIR_FINITE_USEC("DefaultMemoryPressureThresholdUSec", m->defaults.pressure[PRESSURE_MEMORY].threshold_usec), + JSON_BUILD_PAIR_ENUM("DefaultMemoryPressureWatch", cgroup_pressure_watch_to_string(m->defaults.pressure[PRESSURE_MEMORY].watch)), + JSON_BUILD_PAIR_FINITE_USEC("DefaultCPUPressureThresholdUSec", m->defaults.pressure[PRESSURE_CPU].threshold_usec), + JSON_BUILD_PAIR_ENUM("DefaultCPUPressureWatch", cgroup_pressure_watch_to_string(m->defaults.pressure[PRESSURE_CPU].watch)), + JSON_BUILD_PAIR_FINITE_USEC("DefaultIOPressureThresholdUSec", m->defaults.pressure[PRESSURE_IO].threshold_usec), + JSON_BUILD_PAIR_ENUM("DefaultIOPressureWatch", cgroup_pressure_watch_to_string(m->defaults.pressure[PRESSURE_IO].watch)), JSON_BUILD_PAIR_FINITE_USEC("RuntimeWatchdogUSec", manager_get_watchdog(m, WATCHDOG_RUNTIME)), JSON_BUILD_PAIR_FINITE_USEC("RebootWatchdogUSec", manager_get_watchdog(m, WATCHDOG_REBOOT)), JSON_BUILD_PAIR_FINITE_USEC("KExecWatchdogUSec", manager_get_watchdog(m, WATCHDOG_KEXEC)), @@ -114,10 +119,11 @@ static int manager_context_build_json(sd_json_variant **ret, const char *name, v JSON_BUILD_PAIR_STRING_NON_EMPTY("RuntimeWatchdogPreGovernor", m->watchdog_pretimeout_governor), JSON_BUILD_PAIR_STRING_NON_EMPTY("WatchdogDevice", watchdog_get_device()), JSON_BUILD_PAIR_FINITE_USEC("TimerSlackNSec", (uint64_t) prctl(PR_GET_TIMERSLACK)), - SD_JSON_BUILD_PAIR_STRING("DefaultOOMPolicy", oom_policy_to_string(m->defaults.oom_policy)), + JSON_BUILD_PAIR_ENUM("DefaultOOMPolicy", oom_policy_to_string(m->defaults.oom_policy)), SD_JSON_BUILD_PAIR_INTEGER("DefaultOOMScoreAdjust", m->defaults.oom_score_adjust), SD_JSON_BUILD_PAIR_BOOLEAN("DefaultRestrictSUIDSGID", m->defaults.restrict_suid_sgid), - SD_JSON_BUILD_PAIR_STRING("CtrlAltDelBurstAction", emergency_action_to_string(m->cad_burst_action)), + JSON_BUILD_PAIR_ENUM("CtrlAltDelBurstAction", emergency_action_to_string(m->cad_burst_action)), + SD_JSON_BUILD_PAIR_BOOLEAN("DefaultMemoryZSwapWriteback", m->defaults.memory_zswap_writeback), JSON_BUILD_PAIR_STRING_NON_EMPTY("ConfirmSpawn", manager_get_confirm_spawn(m)), JSON_BUILD_PAIR_STRING_NON_EMPTY("ControlGroup", m->cgroup_root)); } @@ -201,6 +207,10 @@ int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd if (r != 0) return r; + r = mac_selinux_access_check_varlink(link, "status"); + if (r < 0) + return r; + r = sd_json_buildo( &v, SD_JSON_BUILD_PAIR_CALLBACK("context", manager_context_build_json, manager), @@ -211,8 +221,22 @@ int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd return sd_varlink_reply(link, v); } -int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { +static void varlink_log_caller(sd_varlink *link, Manager *manager, const char *method) { _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r; + + assert(link); + assert(manager); + assert(method); + + r = varlink_get_peer_pidref(link, &pidref); + if (r < 0) + log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); + + manager_log_caller(manager, &pidref, method); +} + +int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { Manager *manager = ASSERT_PTR(userdata); int r; @@ -236,12 +260,7 @@ int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_v if (r <= 0) return r; - /* We need at least the pidref, otherwise there's nothing to log about. */ - r = varlink_get_peer_pidref(link, &pidref); - if (r < 0) - log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); - else - manager_log_caller(manager, &pidref, "Reload"); + varlink_log_caller(link, manager, "Reload"); /* Check the rate limit after the authorization succeeds, to avoid denial-of-service issues. */ if (!ratelimit_below(&manager->reload_reexec_ratelimit)) { @@ -262,7 +281,6 @@ int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_v } int vl_method_reexecute_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; Manager *manager = ASSERT_PTR(userdata); int r; @@ -286,12 +304,7 @@ int vl_method_reexecute_manager(sd_varlink *link, sd_json_variant *parameters, s if (r <= 0) return r; - /* We need at least the pidref, otherwise there's nothing to log about. */ - r = varlink_get_peer_pidref(link, &pidref); - if (r < 0) - log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); - else - manager_log_caller(manager, &pidref, "Reexecute"); + varlink_log_caller(link, manager, "Reexecute"); /* Check the rate limit after the authorization succeeds, to avoid denial-of-service issues. */ if (!ratelimit_below(&manager->reload_reexec_ratelimit)) { @@ -330,7 +343,7 @@ int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *par if (r <= 0) return r; - r = varlink_set_sentinel(link, NULL); + r = sd_varlink_set_sentinel(link, NULL); if (r < 0) return r; @@ -398,3 +411,69 @@ int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *par return ret; } + +static int manager_do_set_objective(sd_varlink *link, sd_json_variant *parameters, ManagerObjective objective, const char *selinux_permission, bool can_do_root) { + Manager *m = ASSERT_PTR(sd_varlink_get_userdata(link)); + _cleanup_free_ char *root = NULL; + int r; + + assert(link); + assert(parameters); + assert(selinux_permission); + + if (!MANAGER_IS_SYSTEM(m)) + return sd_varlink_error(link, SD_VARLINK_ERROR_METHOD_NOT_IMPLEMENTED, NULL); + + if (can_do_root) { + static const sd_json_dispatch_field dispatch_table[] = { + { "root", SD_JSON_VARIANT_STRING, json_dispatch_path, 0, 0 }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &root); + } else + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + r = mac_selinux_access_check_varlink(link, selinux_permission); + if (r < 0) + return r; + + r = varlink_check_privileged_peer(link); + if (r < 0) + return r; + + if (root) { + assert(can_do_root); + path_simplify(root); + } + + varlink_log_caller(link, m, manager_objective_to_string(objective)); + + if (can_do_root) + free_and_replace(m->switch_root, root); + m->objective = objective; + + return sd_varlink_reply(link, NULL); +} + +int vl_method_poweroff(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_set_objective(link, parameters, MANAGER_POWEROFF, "halt", /* can_do_root= */ false); +} + +int vl_method_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_set_objective(link, parameters, MANAGER_REBOOT, "reboot", /* can_do_root= */ false); +} + +int vl_method_halt(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_set_objective(link, parameters, MANAGER_HALT, "halt", /* can_do_root= */ false); +} + +int vl_method_kexec(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_set_objective(link, parameters, MANAGER_KEXEC, "reboot", /* can_do_root= */ false); +} + +int vl_method_soft_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_set_objective(link, parameters, MANAGER_SOFT_REBOOT, "reboot", /* can_do_root= */ true); +} diff --git a/src/core/varlink-manager.h b/src/core/varlink-manager.h index e5111eb58dc7a..46c737f9a94c6 100644 --- a/src/core/varlink-manager.h +++ b/src/core/varlink-manager.h @@ -9,3 +9,9 @@ int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd int vl_method_reexecute_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); + +int vl_method_poweroff(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_halt(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_kexec(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_soft_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/core/varlink-metrics.c b/src/core/varlink-metrics.c index c492b0c04d315..82bc3cf4cba15 100644 --- a/src/core/varlink-metrics.c +++ b/src/core/varlink-metrics.c @@ -11,6 +11,118 @@ #include "unit.h" #include "varlink-metrics.h" +static int active_timestamp_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Unit *unit; + char *key; + int r; + + assert(context); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *enter_fields = NULL; + r = sd_json_buildo(&enter_fields, SD_JSON_BUILD_PAIR_STRING("event", "enter")); + if (r < 0) + return r; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *exit_fields = NULL; + r = sd_json_buildo(&exit_fields, SD_JSON_BUILD_PAIR_STRING("event", "exit")); + if (r < 0) + return r; + + HASHMAP_FOREACH_KEY(unit, key, manager->units) { + /* ignore aliases */ + if (key != unit->id) + continue; + + r = metric_build_send_unsigned( + context, + unit->id, + unit->active_enter_timestamp.realtime, + enter_fields); + if (r < 0) + return r; + + r = metric_build_send_unsigned( + context, + unit->id, + unit->active_exit_timestamp.realtime, + exit_fields); + if (r < 0) + return r; + } + + return 0; +} + +static int inactive_exit_timestamp_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Unit *unit; + char *key; + int r; + + assert(context); + + HASHMAP_FOREACH_KEY(unit, key, manager->units) { + /* ignore aliases */ + if (key != unit->id) + continue; + + r = metric_build_send_unsigned( + context, + unit->id, + unit->inactive_exit_timestamp.realtime, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int state_change_timestamp_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Unit *unit; + char *key; + int r; + + assert(context); + + HASHMAP_FOREACH_KEY(unit, key, manager->units) { + /* ignore aliases */ + if (key != unit->id) + continue; + + r = metric_build_send_unsigned( + context, + unit->id, + unit->state_change_timestamp.realtime, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int status_errno_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + int r; + + assert(context); + + LIST_FOREACH(units_by_type, unit, manager->units_by_type[UNIT_SERVICE]) { + r = metric_build_send_unsigned( + context, + unit->id, + (uint64_t) SERVICE(unit)->status_errno, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + static int unit_active_state_build_json(MetricFamilyContext *context, void *userdata) { Manager *manager = ASSERT_PTR(userdata); Unit *unit; @@ -108,7 +220,7 @@ static int units_by_type_total_build_json(MetricFamilyContext *context, void *us static int units_by_state_total_build_json(MetricFamilyContext *context, void *userdata) { Manager *manager = ASSERT_PTR(userdata); - UnitActiveState counters[_UNIT_ACTIVE_STATE_MAX] = {}; + uint64_t counters[_UNIT_ACTIVE_STATE_MAX] = {}; Unit *unit; char *key; int r; @@ -143,37 +255,168 @@ static int units_by_state_total_build_json(MetricFamilyContext *context, void *u return 0; } -const MetricFamily metric_family_table[] = { +static int jobs_queued_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + + assert(context); + + return metric_build_send_unsigned( + context, + /* object= */ NULL, + hashmap_size(manager->jobs), + /* fields= */ NULL); +} + +static int system_state_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + + assert(context); + + return metric_build_send_string( + context, + /* object= */ NULL, + manager_state_to_string(manager_state(manager)), + /* fields= */ NULL); +} + +static int units_by_load_state_total_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + uint64_t counters[_UNIT_LOAD_STATE_MAX] = {}; + Unit *unit; + char *key; + int r; + + assert(context); + + HASHMAP_FOREACH_KEY(unit, key, manager->units) { + /* ignore aliases */ + if (key != unit->id) + continue; + + counters[unit->load_state]++; + } + + for (UnitLoadState state = 0; state < _UNIT_LOAD_STATE_MAX; state++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; + + r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("load_state", unit_load_state_to_string(state))); + if (r < 0) + return r; + + r = metric_build_send_unsigned( + context, + /* object= */ NULL, + counters[state], + fields); + if (r < 0) + return r; + } + + return 0; +} + +static int units_total_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + uint64_t count = 0; + Unit *unit; + char *key; + + assert(context); + + HASHMAP_FOREACH_KEY(unit, key, manager->units) { + /* ignore aliases */ + if (key != unit->id) + continue; + + count++; + } + + return metric_build_send_unsigned( + context, + /* object= */ NULL, + count, + /* fields= */ NULL); +} + +static const MetricFamily metric_family_table[] = { /* Keep metrics ordered alphabetically */ { - .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "NRestarts", - .description = "Per unit metric: number of restarts", - .type = METRIC_FAMILY_TYPE_COUNTER, - .generate = nrestarts_build_json, + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "ActiveTimestamp", + .description = "Per unit metric: timestamp of active state transitions in microseconds; 0 indicates the transition has not occurred", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = active_timestamp_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "InactiveExitTimestamp", + .description = "Per unit metric: timestamp when the unit last exited the inactive state in microseconds; 0 indicates the transition has not occurred", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = inactive_exit_timestamp_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "JobsQueued", + .description = "Number of jobs currently queued", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = jobs_queued_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "NRestarts", + .description = "Per unit metric: number of restarts", + .type = METRIC_FAMILY_TYPE_COUNTER, + .generate = nrestarts_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "StateChangeTimestamp", + .description = "Per unit metric: timestamp of the last state change in microseconds; 0 indicates no state change has occurred", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = state_change_timestamp_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "StatusErrno", + .description = "Per service metric: errno status of the service", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = status_errno_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "SystemState", + .description = "Overall system state", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = system_state_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitActiveState", + .description = "Per unit metric: active state", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = unit_active_state_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitLoadState", + .description = "Per unit metric: load state", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = unit_load_state_build_json, }, { - .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitActiveState", - .description = "Per unit metric: active state", - .type = METRIC_FAMILY_TYPE_STRING, - .generate = unit_active_state_build_json, + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitsByLoadStateTotal", + .description = "Total number of units by load state", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = units_by_load_state_total_build_json, }, { - .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitLoadState", - .description = "Per unit metric: load state", - .type = METRIC_FAMILY_TYPE_STRING, - .generate = unit_load_state_build_json, + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitsByStateTotal", + .description = "Total number of units of different state", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = units_by_state_total_build_json, }, { - .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitsByStateTotal", - .description = "Total number of units of different state", - .type = METRIC_FAMILY_TYPE_GAUGE, - .generate = units_by_state_total_build_json, + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitsByTypeTotal", + .description = "Total number of units of different types", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = units_by_type_total_build_json, }, { - .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitsByTypeTotal", - .description = "Total number of units of different types", - .type = METRIC_FAMILY_TYPE_GAUGE, - .generate = units_by_type_total_build_json, + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitsTotal", + .description = "Total number of units", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = units_total_build_json, }, {} }; diff --git a/src/core/varlink-mount.c b/src/core/varlink-mount.c new file mode 100644 index 0000000000000..f4148cafb38e1 --- /dev/null +++ b/src/core/varlink-mount.c @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "json-util.h" +#include "mount.h" +#include "user-util.h" +#include "varlink-common.h" +#include "varlink-mount.h" + +int mount_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Mount *m = ASSERT_PTR(MOUNT(userdata)); + _cleanup_free_ char *what = NULL, *where = NULL, *options = NULL; + + what = mount_get_what_escaped(m); + if (!what) + return -ENOMEM; + + where = mount_get_where_escaped(m); + if (!where) + return -ENOMEM; + + options = mount_get_options_escaped(m); + if (!options) + return -ENOMEM; + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_STRING_NON_EMPTY("What", what), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Where", where), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Type", mount_get_fstype(m)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Options", options), + SD_JSON_BUILD_PAIR_BOOLEAN("SloppyOptions", m->sloppy_options), + SD_JSON_BUILD_PAIR_BOOLEAN("LazyUnmount", m->lazy_unmount), + SD_JSON_BUILD_PAIR_BOOLEAN("ReadWriteOnly", m->read_write_only), + SD_JSON_BUILD_PAIR_BOOLEAN("ForceUnmount", m->force_unmount), + SD_JSON_BUILD_PAIR_UNSIGNED("DirectoryMode", m->directory_mode), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutUSec", m->timeout_usec), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecMount", exec_command_build_json, &m->exec_command[MOUNT_EXEC_MOUNT]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecUnmount", exec_command_build_json, &m->exec_command[MOUNT_EXEC_UNMOUNT]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecRemount", exec_command_build_json, &m->exec_command[MOUNT_EXEC_REMOUNT])); +} + +int mount_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Unit *u = ASSERT_PTR(userdata); + Mount *m = ASSERT_PTR(MOUNT(u)); + return sd_json_buildo( + ASSERT_PTR(ret), + SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(&m->control_pid), "ControlPID", JSON_BUILD_PIDREF(&m->control_pid)), + JSON_BUILD_PAIR_ENUM("Result", mount_result_to_string(m->result)), + JSON_BUILD_PAIR_ENUM("ReloadResult", mount_result_to_string(m->reload_result)), + JSON_BUILD_PAIR_ENUM("CleanResult", mount_result_to_string(m->clean_result)), + SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(u->ref_uid), "UID", SD_JSON_BUILD_UNSIGNED(u->ref_uid)), + SD_JSON_BUILD_PAIR_CONDITION(gid_is_valid(u->ref_gid), "GID", SD_JSON_BUILD_UNSIGNED(u->ref_gid))); +} diff --git a/src/core/varlink-mount.h b/src/core/varlink-mount.h new file mode 100644 index 0000000000000..30fd92781a5e2 --- /dev/null +++ b/src/core/varlink-mount.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int mount_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int mount_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata); diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 2b01aa12b4b17..789b21947c83b 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -18,13 +18,16 @@ #include "set.h" #include "strv.h" #include "unit.h" +#include "varlink-automount.h" #include "varlink-cgroup.h" #include "varlink-execute.h" +#include "varlink-kill.h" +#include "varlink-mount.h" #include "varlink-unit.h" #include "varlink-util.h" #define JSON_BUILD_EMERGENCY_ACTION_NON_EMPTY(name, value) \ - SD_JSON_BUILD_PAIR_CONDITION(value > EMERGENCY_ACTION_NONE, name, SD_JSON_BUILD_STRING(emergency_action_to_string(value))) + SD_JSON_BUILD_PAIR_CONDITION(value > EMERGENCY_ACTION_NONE, name, JSON_BUILD_STRING_UNDERSCORIFY(emergency_action_to_string(value))) static int unit_dependencies_build_json(sd_json_variant **ret, const char *name, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; @@ -112,6 +115,12 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void * If it make sense to place a property into a config/unit file it belongs to Context. * Otherwise it's a 'Runtime'. */ + /* TODO missing callbacks */ + static const sd_json_build_callback_t unit_type_callbacks[_UNIT_TYPE_MAX] = { + [UNIT_AUTOMOUNT] = automount_context_build_json, + [UNIT_MOUNT] = mount_context_build_json, + }; + return sd_json_buildo( ASSERT_PTR(ret), SD_JSON_BUILD_PAIR_STRING("Type", unit_type_to_string(u->type)), @@ -149,8 +158,8 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void JSON_BUILD_PAIR_CALLBACK_NON_NULL("JoinsNamespaceOf", unit_dependencies_build_json, u), JSON_BUILD_PAIR_CALLBACK_NON_NULL("RequiresMountsFor", unit_mounts_for_build_json, &u->mounts_for), JSON_BUILD_PAIR_CALLBACK_NON_NULL("WantsMountsFor", unit_mounts_for_build_json, &u->mounts_for), - SD_JSON_BUILD_PAIR_STRING("OnSuccessJobMode", job_mode_to_string(u->on_success_job_mode)), - SD_JSON_BUILD_PAIR_STRING("OnFailureJobMode", job_mode_to_string(u->on_failure_job_mode)), + JSON_BUILD_PAIR_ENUM("OnSuccessJobMode", job_mode_to_string(u->on_success_job_mode)), + JSON_BUILD_PAIR_ENUM("OnFailureJobMode", job_mode_to_string(u->on_failure_job_mode)), SD_JSON_BUILD_PAIR_BOOLEAN("IgnoreOnIsolate", u->ignore_on_isolate), SD_JSON_BUILD_PAIR_BOOLEAN("StopWhenUnneeded", u->stop_when_unneeded), SD_JSON_BUILD_PAIR_BOOLEAN("RefuseManualStart", u->refuse_manual_start), @@ -158,7 +167,7 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void SD_JSON_BUILD_PAIR_BOOLEAN("AllowIsolate", u->allow_isolate), SD_JSON_BUILD_PAIR_BOOLEAN("DefaultDependencies", u->default_dependencies), SD_JSON_BUILD_PAIR_BOOLEAN("SurviveFinalKillSignal", u->survive_final_kill_signal), - SD_JSON_BUILD_PAIR_STRING("CollectMode", collect_mode_to_string(u->collect_mode)), + JSON_BUILD_PAIR_ENUM("CollectMode", collect_mode_to_string(u->collect_mode)), JSON_BUILD_EMERGENCY_ACTION_NON_EMPTY("FailureAction", u->failure_action), JSON_BUILD_EMERGENCY_ACTION_NON_EMPTY("SuccessAction", u->success_action), JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("FailureActionExitStatus", u->failure_action_exit_status), @@ -189,18 +198,9 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void SD_JSON_BUILD_PAIR_BOOLEAN("DebugInvocation", u->debug_invocation), JSON_BUILD_PAIR_CALLBACK_NON_NULL("CGroup", unit_cgroup_context_build_json, u), - JSON_BUILD_PAIR_CALLBACK_NON_NULL("Exec", unit_exec_context_build_json, u)); - - // TODO follow up PRs: - // JSON_BUILD_PAIR_CALLBACK_NON_NULL("Exec", exec_context_build_json, u) - // JSON_BUILD_PAIR_CALLBACK_NON_NULL("Kill", kill_context_build_json, u) - // Mount/Automount context - // Path context - // Scope context - // Swap context - // Timer context - // Service context - // Socket context + JSON_BUILD_PAIR_CALLBACK_NON_NULL("Exec", unit_exec_context_build_json, u), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("Kill", unit_kill_context_build_json, unit_get_kill_context(u)), + JSON_BUILD_PAIR_CALLBACK_NON_NULL(unit_type_to_capitalized_string(u->type), unit_type_callbacks[u->type], u)); } static int can_clean_build_json(sd_json_variant **ret, const char *name, void *userdata) { @@ -225,7 +225,7 @@ static int can_clean_build_json(sd_json_variant **ret, const char *name, void *u } if (FLAGS_SET(mask, EXEC_CLEAN_FDSTORE)) { - r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_STRING("fdstore")); + r = sd_json_variant_append_arrayb(&v, JSON_BUILD_CONST_STRING("fdstore")); if (r < 0) return r; } @@ -280,6 +280,12 @@ static int unit_runtime_build_json(sd_json_variant **ret, const char *name, void Unit *u = ASSERT_PTR(userdata); Unit *f = unit_following(u); + /* TODO missing callbacks */ + static const sd_json_build_callback_t unit_type_callbacks[_UNIT_TYPE_MAX] = { + [UNIT_AUTOMOUNT] = automount_runtime_build_json, + [UNIT_MOUNT] = mount_runtime_build_json, + }; + return sd_json_buildo( ASSERT_PTR(ret), JSON_BUILD_PAIR_STRING_NON_EMPTY("Following", f ? f->id : NULL), @@ -309,7 +315,8 @@ static int unit_runtime_build_json(sd_json_variant **ret, const char *name, void SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(u->invocation_id), "InvocationID", SD_JSON_BUILD_UUID(u->invocation_id)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("Markers", markers_build_json, &u->markers), JSON_BUILD_PAIR_CALLBACK_NON_NULL("ActivationDetails", activation_details_build_json, u->activation_details), - JSON_BUILD_PAIR_CALLBACK_NON_NULL("CGroup", unit_cgroup_runtime_build_json, u)); + JSON_BUILD_PAIR_CALLBACK_NON_NULL("CGroup", unit_cgroup_runtime_build_json, u), + JSON_BUILD_PAIR_CALLBACK_NON_NULL(unit_type_to_capitalized_string(u->type), unit_type_callbacks[u->type], u)); } static int list_unit_one(sd_varlink *link, Unit *unit) { @@ -408,13 +415,11 @@ static void unit_lookup_parameters_done(UnitLookupParameters *p) { } static int varlink_error_conflict_lookup_parameters(sd_varlink *v, const UnitLookupParameters *p) { - log_debug_errno( - ESRCH, - "Searching unit by lookup parameters name='%s' pid="PID_FMT" cgroup='%s' invocationID='%s' resulted in multiple different units", - p->name, - p->pidref.pid, - p->cgroup, - sd_id128_is_null(p->invocation_id) ? "" : SD_ID128_TO_UUID_STRING(p->invocation_id)); + log_debug("Unit lookup by parameters name='%s' pid='"PID_FMT"' cgroup='%s' invocationID='%s' resulted in multiple different units.", + strnull(p->name), + pidref_is_automatic(&p->pidref) ? 0 : pidref_is_set(&p->pidref) ? p->pidref.pid : (pid_t) -1, + strnull(p->cgroup), + sd_id128_is_null(p->invocation_id) ? "" : SD_ID128_TO_UUID_STRING(p->invocation_id)); return varlink_error_no_such_unit(v, /* name= */ NULL); } @@ -516,7 +521,7 @@ int vl_method_list_units(sd_varlink *link, sd_json_variant *parameters, sd_varli if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); - r = varlink_set_sentinel(link, "io.systemd.Unit.NoSuchUnit"); + r = sd_varlink_set_sentinel(link, VARLINK_ERROR_UNIT_NO_SUCH_UNIT); if (r < 0) return r; @@ -525,6 +530,10 @@ int vl_method_list_units(sd_varlink *link, sd_json_variant *parameters, sd_varli if (k != unit->id) continue; + r = mac_selinux_unit_access_check_varlink(unit, link, "status"); + if (r < 0) + continue; /* silently skip units the caller is not allowed to see */ + r = list_unit_one(link, unit); if (r < 0) return r; diff --git a/src/core/varlink.c b/src/core/varlink.c index ec4f8abad95ae..7bfc3789a39ba 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -4,6 +4,7 @@ #include "constants.h" #include "errno-util.h" +#include "json-util.h" #include "manager.h" #include "metrics.h" #include "path-util.h" @@ -62,7 +63,7 @@ static int build_managed_oom_json_array_element(Unit *u, const char *property, s return -EINVAL; return sd_json_buildo(ret_v, - SD_JSON_BUILD_PAIR_STRING("mode", mode), + JSON_BUILD_PAIR_ENUM("mode", mode), SD_JSON_BUILD_PAIR_STRING("path", crt->cgroup_path), SD_JSON_BUILD_PAIR_STRING("property", property), SD_JSON_BUILD_PAIR_CONDITION(use_limit, "limit", SD_JSON_BUILD_UNSIGNED(c->moom_mem_pressure_limit)), @@ -390,6 +391,11 @@ int manager_setup_varlink_server(Manager *m) { "io.systemd.Manager.Reexecute", vl_method_reexecute_manager, "io.systemd.Manager.Reload", vl_method_reload_manager, "io.systemd.Manager.EnqueueMarkedJobs", vl_method_enqueue_marked_jobs_manager, + "io.systemd.Manager.PowerOff", vl_method_poweroff, + "io.systemd.Manager.Reboot", vl_method_reboot, + "io.systemd.Manager.Halt", vl_method_halt, + "io.systemd.Manager.KExec", vl_method_kexec, + "io.systemd.Manager.SoftReboot", vl_method_soft_reboot, "io.systemd.Unit.List", vl_method_list_units, "io.systemd.Unit.SetProperties", vl_method_set_unit_properties, "io.systemd.service.Ping", varlink_method_ping, diff --git a/src/coredump/coredump-backtrace.c b/src/coredump/coredump-backtrace.c index 0a2dec2313632..9af7b5adb9613 100644 --- a/src/coredump/coredump-backtrace.c +++ b/src/coredump/coredump-backtrace.c @@ -50,7 +50,7 @@ int coredump_backtrace(int argc, char *argv[]) { } else { /* The imported iovecs are not supposed to be freed by us so let's copy and merge them at the * end of the array. */ - r = iovw_append(&context.iovw, &importer.iovw); + r = iovw_extend_iovw(&context.iovw, &importer.iovw); if (r < 0) return r; } diff --git a/src/coredump/coredump-config.c b/src/coredump/coredump-config.c index de2bb68bf3151..cb7bb1adcc485 100644 --- a/src/coredump/coredump-config.c +++ b/src/coredump/coredump-config.c @@ -3,7 +3,7 @@ #include "conf-parser.h" #include "coredump-config.h" #include "format-util.h" -#include "journal-importer.h" +#include "journal-def.h" #include "log.h" #include "string-table.h" #include "string-util.h" diff --git a/src/coredump/coredump-context.c b/src/coredump/coredump-context.c index 921cfe5de7650..6cacae4eff1ae 100644 --- a/src/coredump/coredump-context.c +++ b/src/coredump/coredump-context.c @@ -32,10 +32,12 @@ static const char * const metadata_field_table[_META_MAX] = { [META_ARGV_HOSTNAME] = "COREDUMP_HOSTNAME=", [META_ARGV_DUMPABLE] = "COREDUMP_DUMPABLE=", [META_ARGV_PIDFD] = "COREDUMP_BY_PIDFD=", + [META_ARGV_TID] = "COREDUMP_TID=", [META_COMM] = "COREDUMP_COMM=", [META_EXE] = "COREDUMP_EXE=", [META_UNIT] = "COREDUMP_UNIT=", [META_PROC_AUXV] = "COREDUMP_PROC_AUXV=", + [META_THREAD_NAME] = "COREDUMP_THREAD_NAME=", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(metadata_field, MetadataField); @@ -49,6 +51,7 @@ void coredump_context_done(CoredumpContext *context) { free(context->exe); free(context->unit); free(context->auxv); + free(context->thread_name); safe_close(context->mount_tree_fd); iovw_done_free(&context->iovw); safe_close(context->input_fd); @@ -150,6 +153,7 @@ static int get_process_container_parent_cmdline(PidRef *pid, char** ret_cmdline) assert(pidref_is_set(pid)); assert(!pidref_is_remote(pid)); + assert(ret_cmdline); r = pidref_from_same_root_fs(pid, &PIDREF_MAKE_FROM_PID(1)); if (r < 0) @@ -228,6 +232,12 @@ int coredump_context_build_iovw(CoredumpContext *context) { if (r < 0) return log_error_errno(r, "Failed to add COREDUMP_COMM= field: %m"); + if (context->tid > 0) + (void) iovw_put_string_fieldf(&context->iovw, "COREDUMP_TID=", PID_FMT, context->tid); + + if (context->thread_name) + (void) iovw_put_string_field(&context->iovw, "COREDUMP_THREAD_NAME=", context->thread_name); + if (context->exe) (void) iovw_put_string_field(&context->iovw, "COREDUMP_EXE=", context->exe); @@ -339,6 +349,12 @@ static int coredump_context_parse_from_procfs(CoredumpContext *context) { if (r < 0) return log_error_errno(r, "Failed to get COMM: %m"); + if (context->tid > 0) { + r = pid_get_comm(context->tid, &context->thread_name); + if (r < 0) + log_warning_errno(r, "Failed to get comm for thread "PID_FMT", ignoring: %m", context->tid); + } + r = get_process_exe(pid, &context->exe); if (r < 0) log_warning_errno(r, "Failed to get EXE, ignoring: %m"); @@ -465,6 +481,12 @@ static int context_parse_one(CoredumpContext *context, MetadataField meta, bool context->got_pidfd = 1; return 0; } + case META_ARGV_TID: + r = parse_pid(s, &context->tid); + if (r < 0) + log_warning_errno(r, "Failed to parse TID \"%s\", ignoring: %m", s); + return 0; + case META_COMM: return free_and_strdup_warn(&context->comm, s); @@ -474,6 +496,9 @@ static int context_parse_one(CoredumpContext *context, MetadataField meta, bool case META_UNIT: return free_and_strdup_warn(&context->unit, s); + case META_THREAD_NAME: + return free_and_strdup_warn(&context->thread_name, s); + case META_PROC_AUXV: { char *t = memdup_suffix0(s, size); if (!t) diff --git a/src/coredump/coredump-context.h b/src/coredump/coredump-context.h index f2d44c141c623..7fedfde2adbf7 100644 --- a/src/coredump/coredump-context.h +++ b/src/coredump/coredump-context.h @@ -23,6 +23,7 @@ typedef enum MetadataField { META_ARGV_HOSTNAME = _META_ARGV_REQUIRED, /* %h: hostname */ META_ARGV_DUMPABLE, /* %d: as set by the kernel */ META_ARGV_PIDFD, /* %F: pidfd of the process, since v6.16 */ + META_ARGV_TID, /* %I: TID of the crashing thread, as seen in the initial pid namespace */ /* If new fields are added, they should be added here, to maintain compatibility * with callers which don't know about the new fields. */ _META_ARGV_MAX, @@ -40,6 +41,7 @@ typedef enum MetadataField { META_EXE, META_UNIT, META_PROC_AUXV, + META_THREAD_NAME, _META_MAX, _META_INVALID = -EINVAL, } MetadataField; @@ -53,11 +55,13 @@ struct CoredumpContext { uint64_t rlimit; /* META_ARGV_RLIMIT */ char *hostname; /* META_ARGV_HOSTNAME */ unsigned dumpable; /* META_ARGV_DUMPABLE */ + pid_t tid; /* META_ARGV_TID */ char *comm; /* META_COMM */ char *exe; /* META_EXE */ char *unit; /* META_UNIT */ char *auxv; /* META_PROC_AUXV */ size_t auxv_size; /* META_PROC_AUXV */ + char *thread_name; /* META_THREAD_NAME */ bool got_pidfd; /* META_ARGV_PIDFD */ bool same_pidns; bool forwarded; diff --git a/src/coredump/coredump-submit.c b/src/coredump/coredump-submit.c index 9134697d5b8b7..42d92a32e9c6d 100644 --- a/src/coredump/coredump-submit.c +++ b/src/coredump/coredump-submit.c @@ -201,6 +201,10 @@ static int fix_xattr(int fd, const CoredumpContext *context) { RET_GATHER(r, fix_xattr_one(fd, "user.coredump.hostname", context->hostname)); RET_GATHER(r, fix_xattr_one(fd, "user.coredump.comm", context->comm)); RET_GATHER(r, fix_xattr_one(fd, "user.coredump.exe", context->exe)); + if (context->tid > 0) { + RET_GATHER(r, fix_xattr_format(fd, "user.coredump.tid", PID_FMT, context->tid)); + RET_GATHER(r, fix_xattr_one(fd, "user.coredump.thread_name", context->thread_name)); + } return r; } @@ -301,7 +305,6 @@ static int save_external_coredump( if (storage_on_tmpfs && config->compress) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; uint64_t cgroup_limit = UINT64_MAX; - struct statvfs sv; /* If we can't get the cgroup limit, just ignore it, but don't fail, * try anyway with the config settings. */ @@ -331,8 +334,9 @@ static int save_external_coredump( /* tmpfs might get full quickly, so check the available space too. But don't worry about * errors here, failing to access the storage location will be better logged when writing to * it. */ - if (fstatvfs(fd, &sv) >= 0) - max_size = MIN((uint64_t)sv.f_frsize * (uint64_t)sv.f_bfree, max_size); + uint64_t free_bytes; + if (vfs_free_bytes(fd, &free_bytes) >= 0) + max_size = MIN(free_bytes, max_size); /* Impose a lower minimum, otherwise we will miss the basic headers. */ max_size = MAX(PROCESS_SIZE_MIN, max_size); /* Ensure we can always switch to compressing on the fly in case we are running out of space @@ -369,7 +373,7 @@ static int save_external_coredump( if (fd_compressed < 0) return log_error_errno(fd_compressed, "Failed to create temporary file for coredump %s: %m", fn_compressed); - r = compress_stream(fd, fd_compressed, max_size, &uncompressed_size); + r = compress_stream(DEFAULT_COMPRESSION, fd, fd_compressed, max_size, &uncompressed_size); if (r < 0) return log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed)); @@ -382,7 +386,7 @@ static int save_external_coredump( tmp = unlink_and_free(tmp); fd = safe_close(fd); - r = compress_stream(context->input_fd, fd_compressed, max_size, &partial_uncompressed_size); + r = compress_stream(DEFAULT_COMPRESSION, context->input_fd, fd_compressed, max_size, &partial_uncompressed_size); if (r < 0) return log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed)); uncompressed_size += partial_uncompressed_size; diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 64f7ae99a10f0..b6ca0f8b7dd23 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -30,6 +30,7 @@ #include "fs-util.h" #include "glob-util.h" #include "image-policy.h" +#include "io-util.h" #include "journal-internal.h" #include "journal-util.h" #include "json-util.h" @@ -175,7 +176,7 @@ static int acquire_journal(sd_journal **ret, char **matches) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -223,6 +224,10 @@ static int verb_help(int argc, char **argv, void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, @@ -268,7 +273,7 @@ static int parse_argv(int argc, char *argv[]) { while ((c = getopt_long(argc, argv, "hA:o:F:1D:rS:U:qn:", options, NULL)) >= 0) switch (c) { case 'h': - return verb_help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -411,6 +416,8 @@ static int retrieve(const void *data, size_t ident; char *v; + assert(var); + ident = strlen(name) + 1; /* name + "=" */ if (len < ident) @@ -525,6 +532,8 @@ static int resolve_filename(const char *root, char **p) { char *resolved = NULL; int r; + assert(p); + if (!*p) return 0; @@ -544,14 +553,14 @@ static int print_list(FILE* file, sd_journal *j, Table *t) { _cleanup_free_ char *mid = NULL, *pid = NULL, *uid = NULL, *gid = NULL, *sgnl = NULL, *exe = NULL, *comm = NULL, - *filename = NULL, *truncated = NULL, *coredump = NULL; + *filename = NULL, *truncated = NULL; const void *d; size_t l; usec_t ts; int r, signal_as_int = 0; const char *present = NULL, *color = NULL; uint64_t size = UINT64_MAX; - bool normal_coredump; + bool normal_coredump, has_inline_coredump; uid_t uid_as_int = UID_INVALID; gid_t gid_as_int = GID_INVALID; pid_t pid_as_int = 0; @@ -570,9 +579,11 @@ static int print_list(FILE* file, sd_journal *j, Table *t) { RETRIEVE(d, l, "COREDUMP_COMM", comm); RETRIEVE(d, l, "COREDUMP_FILENAME", filename); RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated); - RETRIEVE(d, l, "COREDUMP", coredump); } + /* Check for an inline coredump without copying the (potentially large) payload to heap. */ + has_inline_coredump = sd_journal_get_data(j, "COREDUMP", NULL, NULL) >= 0; + if (!pid || !uid || !gid || !sgnl || !comm) { log_warning("Found a coredump entry without mandatory fields (PID=%s, UID=%s, GID=%s, SIGNAL=%s, COMM=%s), ignoring.", strna(pid), strna(uid), strna(gid), strna(sgnl), strna(comm)); @@ -596,7 +607,7 @@ static int print_list(FILE* file, sd_journal *j, Table *t) { return r; analyze_coredump_file(filename, &present, &color, &size); - } else if (coredump) + } else if (has_inline_coredump) present = "journal"; else if (normal_coredump) { present = "none"; @@ -632,11 +643,12 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { *boot_id = NULL, *machine_id = NULL, *hostname = NULL, *slice = NULL, *cgroup = NULL, *owner_uid = NULL, *message = NULL, *timestamp = NULL, *filename = NULL, - *truncated = NULL, *coredump = NULL, - *pkgmeta_name = NULL, *pkgmeta_version = NULL, *pkgmeta_json = NULL; + *truncated = NULL, + *pkgmeta_name = NULL, *pkgmeta_version = NULL, *pkgmeta_json = NULL, + *tid = NULL, *thread_name = NULL; const void *d; size_t l; - bool normal_coredump; + bool normal_coredump, has_inline_coredump; int r; assert(file); @@ -663,15 +675,19 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { RETRIEVE(d, l, "COREDUMP_TIMESTAMP", timestamp); RETRIEVE(d, l, "COREDUMP_FILENAME", filename); RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated); - RETRIEVE(d, l, "COREDUMP", coredump); RETRIEVE(d, l, "COREDUMP_PACKAGE_NAME", pkgmeta_name); RETRIEVE(d, l, "COREDUMP_PACKAGE_VERSION", pkgmeta_version); RETRIEVE(d, l, "COREDUMP_PACKAGE_JSON", pkgmeta_json); + RETRIEVE(d, l, "COREDUMP_TID", tid); + RETRIEVE(d, l, "COREDUMP_THREAD_NAME", thread_name); RETRIEVE(d, l, "_BOOT_ID", boot_id); RETRIEVE(d, l, "_MACHINE_ID", machine_id); RETRIEVE(d, l, "MESSAGE", message); } + /* Check for an inline coredump without copying the (potentially large) payload to heap. */ + has_inline_coredump = sd_journal_get_data(j, "COREDUMP", NULL, NULL) >= 0; + if (need_space) fputs("\n", file); @@ -686,6 +702,13 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { " PID: %s%s%s\n", ansi_highlight(), strna(pid), ansi_normal()); + if (tid) { + if (thread_name) + fprintf(file, " TID: %s (%s)\n", tid, thread_name); + else + fprintf(file, " TID: %s\n", tid); + } + if (uid) { uid_t n; @@ -802,7 +825,7 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { if (size != UINT64_MAX) fprintf(file, " Size on Disk: %s\n", FORMAT_BYTES(size)); - } else if (coredump) + } else if (has_inline_coredump) fprintf(file, " Storage: journal\n"); else fprintf(file, " Storage: none\n"); @@ -815,7 +838,7 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { if (exe && pkgmeta_json) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - r = sd_json_parse(pkgmeta_json, 0, &v, NULL, NULL); + r = sd_json_parse(pkgmeta_json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) { _cleanup_free_ char *esc = cescape(pkgmeta_json); log_warning_errno(r, "json_parse on \"%s\" failed, ignoring: %m", strnull(esc)); @@ -879,7 +902,7 @@ static int print_entry( return print_info(stdout, j, n_found > 0); } -static int dump_list(int argc, char **argv, void *userdata) { +static int verb_dump_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; _cleanup_(table_unrefp) Table *t = NULL; size_t n_found = 0; @@ -1080,7 +1103,7 @@ static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp) goto error; } - r = decompress_stream(filename, fdf, fd, -1); + r = decompress_stream_by_filename(filename, fdf, fd, -1); if (r < 0) { log_error_errno(r, "Failed to decompress %s: %m", filename); goto error; @@ -1091,8 +1114,6 @@ static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp) goto error; #endif } else { - ssize_t sz; - /* We want full data, nothing truncated. */ sd_journal_set_data_threshold(j, 0); @@ -1104,14 +1125,9 @@ static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp) data += 9; len -= 9; - sz = write(fd, data, len); - if (sz < 0) { - r = log_error_errno(errno, "Failed to write output: %m"); - goto error; - } - if (sz != (ssize_t) len) { - log_error("Short write to output."); - r = -EIO; + r = loop_write(fd, data, len); + if (r < 0) { + log_error_errno(r, "Failed to write output: %m"); goto error; } } @@ -1130,7 +1146,7 @@ static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp) return r; } -static int dump_core(int argc, char **argv, void *userdata) { +static int verb_dump_core(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; _cleanup_fclose_ FILE *f = NULL; int r; @@ -1166,7 +1182,7 @@ static int dump_core(int argc, char **argv, void *userdata) { return 0; } -static int run_debug(int argc, char **argv, void *userdata) { +static int verb_run_debug(int argc, char *argv[], uintptr_t _data, void *userdata) { static const struct sigaction sa = { .sa_sigaction = sigterm_process_group_handler, .sa_flags = SA_SIGINFO, @@ -1359,12 +1375,12 @@ static int check_units_active(void) { static int coredumpctl_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT, dump_list }, - { "info", VERB_ANY, VERB_ANY, 0, dump_list }, - { "dump", VERB_ANY, VERB_ANY, 0, dump_core }, - { "debug", VERB_ANY, VERB_ANY, 0, run_debug }, - { "gdb", VERB_ANY, VERB_ANY, 0, run_debug }, - { "help", VERB_ANY, 1, 0, verb_help }, + { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT, verb_dump_list }, + { "info", VERB_ANY, VERB_ANY, 0, verb_dump_list }, + { "dump", VERB_ANY, VERB_ANY, 0, verb_dump_core }, + { "debug", VERB_ANY, VERB_ANY, 0, verb_run_debug }, + { "gdb", VERB_ANY, VERB_ANY, 0, verb_run_debug }, + { "help", VERB_ANY, 1, 0, verb_help }, {} }; diff --git a/src/creds/creds.c b/src/creds/creds.c index bcdf3d30384b0..a133a27dd2b07 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -24,6 +23,7 @@ #include "log.h" #include "main-func.h" #include "memory-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -188,7 +188,7 @@ static int is_tmpfs_with_noswap(dev_t devno) { _cleanup_(mnt_free_tablep) struct libmnt_table *table = NULL; int r; - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0) return r; @@ -305,7 +305,9 @@ static int add_credentials_to_table(Table *t, bool encrypted) { return 1; /* Creds dir set */ } -static int verb_list(int argc, char **argv, void *userdata) { +VERB(verb_list, "list", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Show list of passed credentials"); +static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *t = NULL; int r, q; @@ -398,8 +400,6 @@ static int transcode( } static int print_newline(FILE *f, const char *data, size_t l) { - int fd; - assert(f); assert(data || l == 0); @@ -411,12 +411,14 @@ static int print_newline(FILE *f, const char *data, size_t l) { if (l > 0 && data[l-1] == '\n') return 0; - /* Don't bother unless this is a tty */ - fd = fileno(f); - if (fd >= 0 && !isatty_safe(fd)) - return 0; + /* If not explicitly requested, don't bother if the output is not a tty */ + if (arg_newline < 0) { + int fd = fileno(f); + if (fd >= 0 && !isatty_safe(fd)) + return 0; + } - if (fputc('\n', f) != '\n') + if (fputc('\n', f) == EOF) return log_error_errno(errno, "Failed to write trailing newline: %m"); return 1; @@ -465,14 +467,16 @@ static int write_blob(FILE *f, const void *data, size_t size) { return 0; } -static int verb_cat(int argc, char **argv, void *userdata) { +VERB(verb_cat, "cat", "CREDENTIAL...", 2, VERB_ANY, 0, + "Show contents of specified credentials"); +static int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata) { usec_t timestamp; int r, ret = 0; timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME); STRV_FOREACH(cn, strv_skip(argv, 1)) { - _cleanup_(erase_and_freep) void *data = NULL; + _cleanup_(erase_and_freep) void *content = NULL; size_t size = 0; int encrypted; @@ -500,7 +504,7 @@ static int verb_cat(int argc, char **argv, void *userdata) { UINT64_MAX, SIZE_MAX, flags, NULL, - (char**) &data, &size); + (char**) &content, &size); if (r == -ENOENT) /* Not found */ continue; if (r >= 0) /* Found */ @@ -522,7 +526,7 @@ static int verb_cat(int argc, char **argv, void *userdata) { *cn, timestamp, uid_is_valid(arg_uid) ? arg_uid : getuid(), - &IOVEC_MAKE(data, size), + &IOVEC_MAKE(content, size), arg_credential_flags | CREDENTIAL_ANY_SCOPE, &plaintext); else @@ -532,18 +536,18 @@ static int verb_cat(int argc, char **argv, void *userdata) { arg_tpm2_device, arg_tpm2_signature, uid_is_valid(arg_uid) ? arg_uid : getuid(), - &IOVEC_MAKE(data, size), + &IOVEC_MAKE(content, size), arg_credential_flags | CREDENTIAL_ANY_SCOPE, &plaintext); if (r < 0) return r; - erase_and_free(data); - data = TAKE_PTR(plaintext.iov_base); + erase_and_free(content); + content = TAKE_PTR(plaintext.iov_base); size = plaintext.iov_len; } - r = write_blob(stdout, data, size); + r = write_blob(stdout, content, size); if (r < 0) return r; } @@ -551,7 +555,9 @@ static int verb_cat(int argc, char **argv, void *userdata) { return ret; } -static int verb_encrypt(int argc, char **argv, void *userdata) { +VERB(verb_encrypt, "encrypt", "INPUT OUTPUT", 3, 3, 0, + "Encrypt plaintext credential file and write to ciphertext credential file"); +static int verb_encrypt(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(iovec_done_erase) struct iovec plaintext = {}, output = {}; _cleanup_free_ char *base64_buf = NULL, *fname = NULL; const char *input_path, *output_path, *name; @@ -659,7 +665,9 @@ static int verb_encrypt(int argc, char **argv, void *userdata) { return EXIT_SUCCESS; } -static int verb_decrypt(int argc, char **argv, void *userdata) { +VERB(verb_decrypt, "decrypt", "INPUT [OUTPUT]", 2, 3, 0, + "Decrypt ciphertext credential file and write to plaintext credential file"); +static int verb_decrypt(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(iovec_done_erase) struct iovec input = {}, plaintext = {}; _cleanup_free_ char *fname = NULL; _cleanup_fclose_ FILE *output_file = NULL; @@ -741,7 +749,9 @@ static int verb_decrypt(int argc, char **argv, void *userdata) { return EXIT_SUCCESS; } -static int verb_setup(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_setup, "setup", + "Generate credentials host key, if not existing yet"); +static int verb_setup(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(iovec_done_erase) struct iovec host_key = {}; int r; @@ -754,214 +764,169 @@ static int verb_setup(int argc, char **argv, void *userdata) { return EXIT_SUCCESS; } -static int verb_has_tpm2(int argc, char **argv, void *userdata) { +/* For backward compatibility. Hidden from help. */ +VERB(verb_has_tpm2, "has-tpm2", NULL, VERB_ANY, 1, 0, /* help= */ NULL); +static int verb_has_tpm2(int argc, char *argv[], uintptr_t _data, void *userdata) { if (!arg_quiet) - log_notice("The 'systemd-creds %1$s' command has been replaced by 'systemd-analyze %1$s'. Redirecting invocation.", argv[optind]); + log_notice("The 'systemd-creds %1$s' command has been replaced by 'systemd-analyze %1$s'. Redirecting invocation.", argv[0]); return verb_has_tpm2_generic(arg_quiet); } -static int verb_help(int argc, char **argv, void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-creds", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sDisplay and Process Credentials.%6$s\n" - "\n%3$sCommands:%4$s\n" - " list Show list of passed credentials\n" - " cat CREDENTIAL... Show contents of specified credentials\n" - " setup Generate credentials host key, if not existing yet\n" - " encrypt INPUT OUTPUT Encrypt plaintext credential file and write to\n" - " ciphertext credential file\n" - " decrypt INPUT [OUTPUT] Decrypt ciphertext credential file and write to\n" - " plaintext credential file\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " --system Show credentials passed to system\n" - " --transcode=base64|unbase64|hex|unhex\n" - " Transcode credential data\n" - " --newline=auto|yes|no\n" - " Suffix output with newline\n" - " -p --pretty Output as SetCredentialEncrypted= line\n" - " --name=NAME Override filename included in encrypted credential\n" - " --timestamp=TIME Include specified timestamp in encrypted credential\n" - " --not-after=TIME Include specified invalidation time in encrypted\n" - " credential\n" - " --with-key=host|tpm2|host+tpm2|null|auto|auto-initrd\n" - " Which keys to encrypt with\n" - " -H Shortcut for --with-key=host\n" - " -T Shortcut for --with-key=tpm2\n" - " --tpm2-device=PATH\n" - " Pick TPM2 device\n" - " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n" - " Specify TPM2 PCRs to seal against (fixed hash)\n" - " --tpm2-public-key=PATH\n" - " Specify PEM certificate to seal against\n" - " --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n" - " Specify TPM2 PCRs to seal against (public key)\n" - " --tpm2-signature=PATH\n" - " Specify signature for public key PCR policy\n" - " --user Select user-scoped credential encryption\n" - " --uid=UID Select user for scoped credentials\n" - " --allow-null Allow decrypting credentials with null key\n" - " --refuse-null Refuse decrypting credentials with null key\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sDisplay and Process Credentials.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); - return 0; -} + r = table_print_or_warn(verbs); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_JSON, - ARG_SYSTEM, - ARG_TRANSCODE, - ARG_NEWLINE, - ARG_WITH_KEY, - ARG_TPM2_DEVICE, - ARG_TPM2_PCRS, - ARG_TPM2_PUBLIC_KEY, - ARG_TPM2_PUBLIC_KEY_PCRS, - ARG_TPM2_SIGNATURE, - ARG_NAME, - ARG_TIMESTAMP, - ARG_NOT_AFTER, - ARG_USER, - ARG_UID, - ARG_ALLOW_NULL, - ARG_REFUSE_NULL, - ARG_NO_ASK_PASSWORD, - }; + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "json", required_argument, NULL, ARG_JSON }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "transcode", required_argument, NULL, ARG_TRANSCODE }, - { "newline", required_argument, NULL, ARG_NEWLINE }, - { "pretty", no_argument, NULL, 'p' }, - { "with-key", required_argument, NULL, ARG_WITH_KEY }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, - { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY }, - { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS }, - { "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE }, - { "name", required_argument, NULL, ARG_NAME }, - { "timestamp", required_argument, NULL, ARG_TIMESTAMP }, - { "not-after", required_argument, NULL, ARG_NOT_AFTER }, - { "quiet", no_argument, NULL, 'q' }, - { "user", no_argument, NULL, ARG_USER }, - { "uid", required_argument, NULL, ARG_UID }, - { "allow-null", no_argument, NULL, ARG_ALLOW_NULL }, - { "refuse-null", no_argument, NULL, ARG_REFUSE_NULL }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - {} - }; + r = table_print_or_warn(options); + if (r < 0) + return r; - int c, r; + printf("\nSee the %s for details.\n", link); + return 0; +} + +VERB_COMMON_HELP(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hHTpq", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return verb_help(0, NULL, NULL); + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Show credentials passed to system"): arg_system = true; break; - case ARG_TRANSCODE: - if (streq(optarg, "help")) { + OPTION_LONG("transcode", "METHOD", + "Transcode credential data (base64, unbase64, hex, unhex)"): + if (streq(arg, "help")) { if (arg_legend) puts("Supported transcode types:"); return DUMP_STRING_TABLE(transcode_mode, TranscodeMode, _TRANSCODE_MAX); } - if (parse_boolean(optarg) == 0) /* If specified as "false", turn transcoding off */ + if (parse_boolean(arg) == 0) /* If specified as "false", turn transcoding off */ arg_transcode = TRANSCODE_OFF; else { TranscodeMode m; - m = transcode_mode_from_string(optarg); + m = transcode_mode_from_string(arg); if (m < 0) return log_error_errno(m, "Failed to parse transcode mode: %m"); arg_transcode = m; } + break; + OPTION_LONG("newline", "auto|yes|no", "Suffix output with newline"): + r = parse_tristate_argument_with_auto("--newline=", arg, &arg_newline); + if (r < 0) + return r; break; - case ARG_NEWLINE: - if (isempty(optarg) || streq(optarg, "auto")) - arg_newline = -1; - else { - r = parse_boolean_argument("--newline=", optarg, NULL); - if (r < 0) - return r; + OPTION('p', "pretty", NULL, "Output as SetCredentialEncrypted= line"): + arg_pretty = true; + break; - arg_newline = r; + OPTION_LONG("name", "NAME", + "Override filename included in encrypted credential"): + if (isempty(arg)) { + arg_name = NULL; + arg_name_any = true; + break; } + + if (!credential_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", arg); + + arg_name = arg; + arg_name_any = false; break; - case 'p': - arg_pretty = true; + OPTION_LONG("timestamp", "TIME", + "Include specified timestamp in encrypted credential"): + r = parse_timestamp(arg, &arg_timestamp); + if (r < 0) + return log_error_errno(r, "Failed to parse timestamp: %s", arg); break; - case ARG_WITH_KEY: - if (streq(optarg, "help")) { + OPTION_LONG("not-after", "TIME", + "Include specified invalidation time in encrypted credential"): + r = parse_timestamp(arg, &arg_not_after); + if (r < 0) + return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", arg); + break; + + OPTION_LONG("with-key", "KEY", + "Which keys to encrypt with (host, tpm2, host+tpm2, null, auto, auto-initrd)"): + if (streq(arg, "help")) { if (arg_legend) puts("Supported key types:"); return DUMP_STRING_TABLE(cred_key_type, CredKeyType, _CRED_KEY_TYPE_MAX); } - if (isempty(optarg)) + if (isempty(arg)) arg_with_key = _CRED_AUTO; else { - CredKeyType t = cred_key_type_from_string(optarg); + CredKeyType t = cred_key_type_from_string(arg); if (t < 0) return log_error_errno(t, "Failed to parse key type: %m"); @@ -969,90 +934,63 @@ static int parse_argv(int argc, char *argv[]) { } break; - case 'H': + OPTION_SHORT('H', NULL, "Shortcut for --with-key=host"): arg_with_key = CRED_AES256_GCM_BY_HOST; break; - case 'T': + OPTION_SHORT('T', NULL, "Shortcut for --with-key=tpm2"): arg_with_key = _CRED_AUTO_TPM2; break; - case ARG_TPM2_DEVICE: - if (streq(optarg, "list")) + OPTION_LONG("tpm2-device", "PATH", "Pick TPM2 device"): + if (streq(arg, "list")) return tpm2_list_devices(arg_legend, arg_quiet); - arg_tpm2_device = streq(optarg, "auto") ? NULL : optarg; + arg_tpm2_device = streq(arg, "auto") ? NULL : arg; break; - case ARG_TPM2_PCRS: /* For fixed hash PCR policies only */ - r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_pcr_mask); + OPTION_LONG("tpm2-pcrs", "PCR1+PCR2+PCR3+…", + "Specify TPM2 PCRs to seal against (fixed hash)"): + /* For fixed hash PCR policies only */ + r = tpm2_parse_pcr_argument_to_mask(arg, &arg_tpm2_pcr_mask); if (r < 0) return r; - break; - case ARG_TPM2_PUBLIC_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key); + OPTION_LONG("tpm2-public-key", "PATH", + "Specify PEM certificate to seal against"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_public_key); if (r < 0) return r; - break; - case ARG_TPM2_PUBLIC_KEY_PCRS: /* For public key PCR policies only */ - r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); + OPTION_LONG("tpm2-public-key-pcrs", "PCR1+PCR2+…", + "Specify TPM2 PCRs to seal against (public key)"): + /* For public key PCR policies only */ + r = tpm2_parse_pcr_argument_to_mask(arg, &arg_tpm2_public_key_pcr_mask); if (r < 0) return r; - break; - case ARG_TPM2_SIGNATURE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_signature); + OPTION_LONG("tpm2-signature", "PATH", + "Specify signature for public key PCR policy"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_signature); if (r < 0) return r; - - break; - - case ARG_NAME: - if (isempty(optarg)) { - arg_name = NULL; - arg_name_any = true; - break; - } - - if (!credential_name_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", optarg); - - arg_name = optarg; - arg_name_any = false; - break; - - case ARG_TIMESTAMP: - r = parse_timestamp(optarg, &arg_timestamp); - if (r < 0) - return log_error_errno(r, "Failed to parse timestamp: %s", optarg); - break; - case ARG_NOT_AFTER: - r = parse_timestamp(optarg, &arg_not_after); - if (r < 0) - return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", optarg); - - break; - - case ARG_USER: + OPTION_LONG("user", NULL, "Select user-scoped credential encryption"): if (!uid_is_valid(arg_uid)) arg_uid = getuid(); - break; - case ARG_UID: - if (isempty(optarg)) + OPTION_LONG("uid", "UID", "Select user for scoped credentials"): + if (isempty(arg)) arg_uid = UID_INVALID; - else if (streq(optarg, "self")) + else if (streq(arg, "self")) arg_uid = getuid(); else { - const char *name = optarg; + const char *name = arg; r = get_user_creds( &name, @@ -1063,35 +1001,30 @@ static int parse_argv(int argc, char *argv[]) { /* flags= */ 0); if (r < 0) return log_error_errno(r, "Failed to resolve user '%s': %s", - optarg, STRERROR_USER(r)); + arg, STRERROR_USER(r)); } break; - case ARG_ALLOW_NULL: + OPTION_LONG("allow-null", NULL, + "Allow decrypting credentials with null key"): arg_credential_flags &= ~CREDENTIAL_REFUSE_NULL; arg_credential_flags |= CREDENTIAL_ALLOW_NULL; break; - case ARG_REFUSE_NULL: + OPTION_LONG("refuse-null", NULL, + "Refuse decrypting credentials with null key"): arg_credential_flags |= CREDENTIAL_REFUSE_NULL; arg_credential_flags &= ~CREDENTIAL_ALLOW_NULL; break; - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress informational messages"): arg_quiet = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } SET_FLAG(arg_credential_flags, CREDENTIAL_IPC_ALLOW_INTERACTIVE, arg_ask_password); @@ -1120,25 +1053,10 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); arg_varlink = r; + *ret_args = option_parser_get_args(&state); return 1; } -static int creds_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list }, - { "cat", 2, VERB_ANY, 0, verb_cat }, - { "encrypt", 3, 3, 0, verb_encrypt }, - { "decrypt", 2, 3, 0, verb_decrypt }, - { "setup", VERB_ANY, 1, 0, verb_setup }, - { "help", VERB_ANY, 1, 0, verb_help }, - { "has-tpm2", VERB_ANY, 1, 0, verb_has_tpm2 }, /* for backward compatibility */ - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - #define TIMESTAMP_FRESH_MAX (30*USEC_PER_SEC) static bool timestamp_is_fresh(usec_t x) { @@ -1554,14 +1472,15 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; if (arg_varlink) return vl_server(); - return creds_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/creds/meson.build b/src/creds/meson.build index a6e66495b6059..dc4a5a28ae316 100644 --- a/src/creds/meson.build +++ b/src/creds/meson.build @@ -11,7 +11,7 @@ executables += [ 'sources' : files('creds.c'), 'dependencies' : [ libmount_cflags, - libopenssl, + libopenssl_cflags, threads, ], }, diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c index 822cf26c896a3..600207e947b83 100644 --- a/src/cryptenroll/cryptenroll-fido2.c +++ b/src/cryptenroll/cryptenroll-fido2.c @@ -54,7 +54,7 @@ int load_volume_key_fido2( if (passphrase_size < 0) return log_oom(); - r = crypt_volume_key_get( + r = sym_crypt_volume_key_get( cd, CRYPT_ANY_SLOT, ret_vk, @@ -95,9 +95,9 @@ int enroll_fido2( assert_se(iovec_is_set(volume_key)); assert_se(device); - assert_se(node = crypt_get_device_name(cd)); + assert_se(node = sym_crypt_get_device_name(cd)); - un = strempty(crypt_get_uuid(cd)); + un = strempty(sym_crypt_get_uuid(cd)); if (salt_file) r = fido2_read_salt_file( @@ -140,7 +140,7 @@ int enroll_fido2( if (r < 0) return log_error_errno(r, "Failed to set minimal PBKDF: %m"); - keyslot = crypt_keyslot_add_by_volume_key( + keyslot = sym_crypt_keyslot_add_by_volume_key( cd, CRYPT_ANY_SLOT, volume_key->iov_base, diff --git a/src/cryptenroll/cryptenroll-list.c b/src/cryptenroll/cryptenroll-list.c index 32e8c9cf2a32b..ab7637e61634f 100644 --- a/src/cryptenroll/cryptenroll-list.c +++ b/src/cryptenroll/cryptenroll-list.c @@ -26,11 +26,11 @@ int list_enrolled(struct crypt_device *cd) { assert(cd); /* First step, find out all currently used slots */ - assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0); + assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0); for (int slot = 0; slot < slot_max; slot++) { crypt_keyslot_info status; - status = crypt_keyslot_status(cd, slot); + status = sym_crypt_keyslot_status(cd, slot); if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST)) continue; @@ -125,9 +125,5 @@ int list_enrolled(struct crypt_device *cd) { return 0; } - r = table_print(t, stdout); - if (r < 0) - return log_error_errno(r, "Failed to show slot table: %m"); - - return 0; + return table_print_or_warn(t); } diff --git a/src/cryptenroll/cryptenroll-password.c b/src/cryptenroll/cryptenroll-password.c index 26503187133b4..e189cce23aba0 100644 --- a/src/cryptenroll/cryptenroll-password.c +++ b/src/cryptenroll/cryptenroll-password.c @@ -31,7 +31,7 @@ int load_volume_key_password( if (r < 0) return log_error_errno(r, "Failed to acquire password from environment: %m"); if (r > 0) { - r = crypt_volume_key_get( + r = sym_crypt_volume_key_get( cd, CRYPT_ANY_SLOT, ret_vk, @@ -81,7 +81,7 @@ int load_volume_key_password( r = -EPERM; STRV_FOREACH(p, passwords) { - r = crypt_volume_key_get( + r = sym_crypt_volume_key_get( cd, CRYPT_ANY_SLOT, ret_vk, @@ -114,7 +114,7 @@ int enroll_password( assert(cd); assert(iovec_is_set(volume_key)); - assert_se(node = crypt_get_device_name(cd)); + assert_se(node = sym_crypt_get_device_name(cd)); r = getenv_steal_erase("NEWPASSWORD", &new_password); if (r < 0) @@ -123,7 +123,7 @@ int enroll_password( _cleanup_free_ char *disk_path = NULL, *id = NULL; unsigned i = 5; - assert_se(node = crypt_get_device_name(cd)); + assert_se(node = sym_crypt_get_device_name(cd)); (void) suggest_passwords(); @@ -196,7 +196,7 @@ int enroll_password( else if (r == 0) log_warning("Specified password does not pass quality checks (%s), proceeding anyway.", error); - keyslot = crypt_keyslot_add_by_volume_key( + keyslot = sym_crypt_keyslot_add_by_volume_key( cd, CRYPT_ANY_SLOT, volume_key->iov_base, diff --git a/src/cryptenroll/cryptenroll-pkcs11.c b/src/cryptenroll/cryptenroll-pkcs11.c index 18ffb2edc3e8b..ae678f96e477d 100644 --- a/src/cryptenroll/cryptenroll-pkcs11.c +++ b/src/cryptenroll/cryptenroll-pkcs11.c @@ -2,10 +2,10 @@ #include "alloc-util.h" #include "cryptenroll-pkcs11.h" +#include "crypto-util.h" #include "cryptsetup-util.h" #include "hexdecoct.h" #include "json-util.h" -#include "openssl-util.h" #include "pkcs11-util.h" #if HAVE_P11KIT && HAVE_OPENSSL @@ -14,6 +14,8 @@ static int uri_set_private_class(const char *uri, char **ret_uri) { _cleanup_free_ char *private_uri = NULL; int r; + assert(ret_uri); + r = uri_from_string(uri, &p11kit_uri); if (r < 0) return log_error_errno(r, "Failed to parse PKCS#11 URI '%s': %m", uri); @@ -51,7 +53,7 @@ int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key,const assert_se(iovec_is_set(volume_key)); assert_se(uri); - assert_se(node = crypt_get_device_name(cd)); + assert_se(node = sym_crypt_get_device_name(cd)); r = pkcs11_acquire_public_key( uri, @@ -78,7 +80,7 @@ int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key,const if (r < 0) return log_error_errno(r, "Failed to set minimal PBKDF: %m"); - int keyslot = crypt_keyslot_add_by_volume_key( + int keyslot = sym_crypt_keyslot_add_by_volume_key( cd, CRYPT_ANY_SLOT, volume_key->iov_base, diff --git a/src/cryptenroll/cryptenroll-recovery.c b/src/cryptenroll/cryptenroll-recovery.c index f9a588da26a3e..85ec13f128290 100644 --- a/src/cryptenroll/cryptenroll-recovery.c +++ b/src/cryptenroll/cryptenroll-recovery.c @@ -24,7 +24,7 @@ int enroll_recovery( assert_se(cd); assert_se(iovec_is_set(volume_key)); - assert_se(node = crypt_get_device_name(cd)); + assert_se(node = sym_crypt_get_device_name(cd)); r = make_recovery_key(&password); if (r < 0) @@ -34,7 +34,7 @@ int enroll_recovery( if (r < 0) return log_error_errno(r, "Failed to set minimal PBKDF: %m"); - keyslot = crypt_keyslot_add_by_volume_key( + keyslot = sym_crypt_keyslot_add_by_volume_key( cd, CRYPT_ANY_SLOT, volume_key->iov_base, @@ -93,7 +93,7 @@ int enroll_recovery( return keyslot; rollback: - q = crypt_keyslot_destroy(cd, keyslot); + q = sym_crypt_keyslot_destroy(cd, keyslot); if (q < 0) log_debug_errno(q, "Unable to remove key slot we just added again, can't rollback, sorry: %m"); diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index 50abca43639b8..2f6c7ecd7284b 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -72,7 +72,7 @@ static int search_policy_hash( if (r < 0) return log_error_errno(r, "Invalid hex data in 'tpm2-policy-hash' field item : %m"); - if (iovec_memcmp(policy_hash + j, &thash) != 0) { + if (!iovec_equal(policy_hash + j, &thash)) { match = false; break; } @@ -91,7 +91,7 @@ static int search_policy_hash( if (r < 0) return log_error_errno(r, "Invalid hex data in 'tpm2-policy-hash' field: %m"); - if (iovec_memcmp(policy_hash + 0, &thash) == 0) + if (iovec_equal(policy_hash + 0, &thash)) return keyslot; /* Found entry with same hash. */ } } @@ -272,7 +272,7 @@ int load_volume_key_tpm2( if (passphrase_size < 0) return log_oom(); - r = crypt_volume_key_get( + r = sym_crypt_volume_key_get( cd, CRYPT_ANY_SLOT, ret_vk, @@ -329,7 +329,7 @@ int enroll_tpm2(struct crypt_device *cd, assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask)); assert(ret_slot_to_wipe); - assert_se(node = crypt_get_device_name(cd)); + assert_se(node = sym_crypt_get_device_name(cd)); if (use_pin) { r = get_pin(&pin_str, &flags); @@ -566,7 +566,7 @@ int enroll_tpm2(struct crypt_device *cd, if (r < 0) return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); - if (iovec_memcmp(&secret, &secret2) != 0) + if (!iovec_equal(&secret, &secret2)) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed."); } @@ -579,7 +579,7 @@ int enroll_tpm2(struct crypt_device *cd, if (r < 0) return log_error_errno(r, "Failed to set minimal PBKDF: %m"); - keyslot = crypt_keyslot_add_by_volume_key( + keyslot = sym_crypt_keyslot_add_by_volume_key( cd, CRYPT_ANY_SLOT, volume_key->iov_base, diff --git a/src/cryptenroll/cryptenroll-wipe.c b/src/cryptenroll/cryptenroll-wipe.c index 1ae92bf91b8fb..a8638d2b3a777 100644 --- a/src/cryptenroll/cryptenroll-wipe.c +++ b/src/cryptenroll/cryptenroll-wipe.c @@ -15,7 +15,7 @@ static int find_all_slots(struct crypt_device *cd, Set *wipe_slots, Set *keep_sl assert(cd); assert(wipe_slots); - assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0); + assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0); /* Finds all currently assigned slots, and adds them to 'wipe_slots', except if listed already in 'keep_slots' */ @@ -27,7 +27,7 @@ static int find_all_slots(struct crypt_device *cd, Set *wipe_slots, Set *keep_sl set_contains(wipe_slots, INT_TO_PTR(slot))) continue; - status = crypt_keyslot_status(cd, slot); + status = sym_crypt_keyslot_status(cd, slot); if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST)) continue; @@ -44,12 +44,12 @@ static int find_empty_passphrase_slots(struct crypt_device *cd, Set *wipe_slots, assert(cd); assert(wipe_slots); - assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0); + assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0); /* Finds all slots with an empty passphrase assigned (i.e. "") and adds them to 'wipe_slots', except * if listed already in 'keep_slots' */ - r = crypt_get_volume_key_size(cd); + r = sym_crypt_get_volume_key_size(cd); if (r <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size"); vks = (size_t) r; @@ -63,7 +63,7 @@ static int find_empty_passphrase_slots(struct crypt_device *cd, Set *wipe_slots, set_contains(wipe_slots, INT_TO_PTR(slot))) continue; - status = crypt_keyslot_status(cd, slot); + status = sym_crypt_keyslot_status(cd, slot); if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST)) continue; @@ -71,7 +71,7 @@ static int find_empty_passphrase_slots(struct crypt_device *cd, Set *wipe_slots, if (!vk) return log_oom(); - r = crypt_volume_key_get(cd, slot, vk, &vks, "", 0); + r = sym_crypt_volume_key_get(cd, slot, vk, &vks, "", 0); if (r < 0) { log_debug_errno(r, "Failed to acquire volume key from slot %i with empty password, ignoring: %m", slot); continue; @@ -164,7 +164,7 @@ static int find_slots_by_mask( if ((by_mask & (1U << ENROLL_PASSWORD)) != 0) { int slot_max; - assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0); + assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0); for (int slot = 0; slot < slot_max; slot++) { crypt_keyslot_info status; @@ -177,7 +177,7 @@ static int find_slots_by_mask( if (set_contains(listed_slots, INT_TO_PTR(slot))) /* This has a token, hence is not a password. */ continue; - status = crypt_keyslot_status(cd, slot); + status = sym_crypt_keyslot_status(cd, slot); if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST)) /* Not actually assigned? */ continue; @@ -273,7 +273,7 @@ static bool slots_remain(struct crypt_device *cd, Set *wipe_slots, Set *keep_slo int slot_max; assert(cd); - assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0); + assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0); /* Checks if any slots remaining in the LUKS2 header if we remove all slots listed in 'wipe_slots' * (keeping those listed in 'keep_slots') */ @@ -281,7 +281,7 @@ static bool slots_remain(struct crypt_device *cd, Set *wipe_slots, Set *keep_slo for (int slot = 0; slot < slot_max; slot++) { crypt_keyslot_info status; - status = crypt_keyslot_status(cd, slot); + status = sym_crypt_keyslot_status(cd, slot); if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST)) continue; @@ -338,7 +338,7 @@ int wipe_slots(struct crypt_device *cd, if (set_put(keep_slots, INT_TO_PTR(except_slot)) < 0) return log_oom(); - assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0); + assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0); /* Maintain another set of the slots we intend to wipe */ for (size_t i = 0; i < n_explicit_slots; i++) { @@ -425,7 +425,7 @@ int wipe_slots(struct crypt_device *cd, * first.) */ ret = 0; for (size_t i = n_ordered_slots; i > 0; i--) { - r = crypt_keyslot_destroy(cd, ordered_slots[i - 1]); + r = sym_crypt_keyslot_destroy(cd, ordered_slots[i - 1]); if (r < 0) { if (r == -ENOENT) log_warning_errno(r, "Failed to wipe non-existent slot %i, continuing.", ordered_slots[i - 1]); @@ -438,7 +438,7 @@ int wipe_slots(struct crypt_device *cd, } for (size_t i = n_ordered_tokens; i > 0; i--) { - r = crypt_token_json_set(cd, ordered_tokens[i - 1], NULL); + r = sym_crypt_token_json_set(cd, ordered_tokens[i - 1], NULL); if (r < 0) { log_warning_errno(r, "Failed to wipe token %i, continuing: %m", ordered_tokens[i - 1]); if (ret == 0) diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 2a914a9b49918..46b3a546c9b9b 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-device.h" @@ -19,9 +18,11 @@ #include "cryptsetup-util.h" #include "extract-word.h" #include "fileio.h" +#include "format-table.h" #include "libfido2-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -30,6 +31,7 @@ #include "process-util.h" #include "string-table.h" #include "string-util.h" +#include "strv.h" #include "tpm2-pcr.h" #include "tpm2-util.h" @@ -54,7 +56,7 @@ static uint32_t arg_tpm2_public_key_pcr_mask = 0; static char *arg_tpm2_signature = NULL; static char *arg_tpm2_pcrlock = NULL; static char *arg_node = NULL; -PagerFlags arg_pager_flags = 0; +static PagerFlags arg_pager_flags = 0; static int *arg_wipe_slots = NULL; static size_t arg_n_wipe_slots = 0; static WipeScope arg_wipe_slots_scope = WIPE_EXPLICIT; @@ -231,207 +233,96 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [BLOCK-DEVICE]\n\n" - "%5$sEnroll a security token or authentication credential to a LUKS volume.%6$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not spawn a pager\n" - " --list-devices List candidate block devices to operate on\n" - " --wipe-slot=SLOT1,SLOT2,…\n" - " Wipe specified slots\n" - "\n%3$sUnlocking:%4$s\n" - " --unlock-key-file=PATH\n" - " Use a file to unlock the volume\n" - " --unlock-fido2-device=PATH\n" - " Use a FIDO2 device to unlock the volume\n" - " --unlock-tpm2-device=PATH\n" - " Use a TPM2 device to unlock the volume\n" - "\n%3$sSimple Enrollment:%4$s\n" - " --password Enroll a user-supplied password\n" - " --recovery-key Enroll a recovery key\n" - "\n%3$sPKCS#11 Enrollment:%4$s\n" - " --pkcs11-token-uri=URI|auto|list\n" - " Enroll a PKCS#11 security token or list them\n" - "\n%3$sFIDO2 Enrollment:%4$s\n" - " --fido2-device=PATH|auto|list\n" - " Enroll a FIDO2-HMAC security token or list them\n" - " --fido2-salt-file=PATH\n" - " Use salt from a file instead of generating one\n" - " --fido2-parameters-in-header=BOOL\n" - " Whether to store FIDO2 parameters in the LUKS2 header\n" - " --fido2-credential-algorithm=STRING\n" - " Specify COSE algorithm for FIDO2 credential\n" - " --fido2-with-client-pin=BOOL\n" - " Whether to require entering a PIN to unlock the volume\n" - " --fido2-with-user-presence=BOOL\n" - " Whether to require user presence to unlock the volume\n" - " --fido2-with-user-verification=BOOL\n" - " Whether to require user verification to unlock the volume\n" - "\n%3$sTPM2 Enrollment:%4$s\n" - " --tpm2-device=PATH|auto|list\n" - " Enroll a TPM2 device or list them\n" - " --tpm2-device-key=PATH\n" - " Enroll a TPM2 device using its public key\n" - " --tpm2-seal-key-handle=HANDLE\n" - " Specify handle of key to use for sealing\n" - " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n" - " Specify TPM2 PCRs to seal against\n" - " --tpm2-public-key=PATH\n" - " Enroll signed TPM2 PCR policy against PEM public key\n" - " --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n" - " Enroll signed TPM2 PCR policy for specified TPM2 PCRs\n" - " --tpm2-signature=PATH\n" - " Validate public key enrollment works with JSON signature\n" - " file\n" - " --tpm2-pcrlock=PATH\n" - " Specify pcrlock policy to lock against\n" - " --tpm2-with-pin=BOOL\n" - " Whether to require entering a PIN to unlock the volume\n" - "\nSee the %2$s for details.\n", + static const char* const groups[] = { + NULL, + "Unlocking", + "Simple Enrollment", + "PKCS#11 Enrollment", + "FIDO2 Enrollment", + "TPM2 Enrollment", + }; + + _cleanup_(table_unref_many) Table *tables[ELEMENTSOF(groups) + 1] = {}; + + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + r = option_parser_get_help_table_group(groups[i], &tables[i]); + if (r < 0) + return r; + } + + (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], tables[3], tables[4], tables[5]); + + printf("%s [OPTIONS...] [BLOCK-DEVICE]\n\n" + "%sEnroll a security token or authentication credential to a LUKS volume.%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + printf("\n%s%s:%s\n", ansi_underline(), groups[i] ?: "Options", ansi_normal()); + + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_PASSWORD, - ARG_RECOVERY_KEY, - ARG_UNLOCK_KEYFILE, - ARG_UNLOCK_FIDO2_DEVICE, - ARG_UNLOCK_TPM2_DEVICE, - ARG_PKCS11_TOKEN_URI, - ARG_FIDO2_DEVICE, - ARG_FIDO2_SALT_FILE, - ARG_FIDO2_PARAMETERS_IN_HEADER, - ARG_TPM2_DEVICE, - ARG_TPM2_DEVICE_KEY, - ARG_TPM2_SEAL_KEY_HANDLE, - ARG_TPM2_PCRS, - ARG_TPM2_PUBLIC_KEY, - ARG_TPM2_PUBLIC_KEY_PCRS, - ARG_TPM2_SIGNATURE, - ARG_TPM2_PCRLOCK, - ARG_TPM2_WITH_PIN, - ARG_WIPE_SLOT, - ARG_FIDO2_WITH_PIN, - ARG_FIDO2_WITH_UP, - ARG_FIDO2_WITH_UV, - ARG_FIDO2_CRED_ALG, - ARG_LIST_DEVICES, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "password", no_argument, NULL, ARG_PASSWORD }, - { "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY }, - { "unlock-key-file", required_argument, NULL, ARG_UNLOCK_KEYFILE }, - { "unlock-fido2-device", required_argument, NULL, ARG_UNLOCK_FIDO2_DEVICE }, - { "unlock-tpm2-device", required_argument, NULL, ARG_UNLOCK_TPM2_DEVICE }, - { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, - { "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG }, - { "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE }, - { "fido2-salt-file", required_argument, NULL, ARG_FIDO2_SALT_FILE }, - { "fido2-parameters-in-header", required_argument, NULL, ARG_FIDO2_PARAMETERS_IN_HEADER }, - { "fido2-with-client-pin", required_argument, NULL, ARG_FIDO2_WITH_PIN }, - { "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP }, - { "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "tpm2-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY }, - { "tpm2-seal-key-handle", required_argument, NULL, ARG_TPM2_SEAL_KEY_HANDLE }, - { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, - { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY }, - { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS }, - { "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE }, - { "tpm2-pcrlock", required_argument, NULL, ARG_TPM2_PCRLOCK }, - { "tpm2-with-pin", required_argument, NULL, ARG_TPM2_WITH_PIN }, - { "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT }, - { "list-devices", no_argument, NULL, ARG_LIST_DEVICES }, - {} - }; - bool auto_public_key_pcr_mask = true, auto_pcrlock = true; - int c, r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_FIDO2_WITH_PIN: - r = parse_boolean_argument("--fido2-with-client-pin=", optarg, NULL); - if (r < 0) - return r; - - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r); - break; - - case ARG_FIDO2_WITH_UP: - r = parse_boolean_argument("--fido2-with-user-presence=", optarg, NULL); - if (r < 0) - return r; - - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r); - break; + OPTION_LONG("list-devices", NULL, + "List candidate block devices to operate on"): + return blockdev_list(BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_REQUIRE_LUKS, + /* ret_devices= */ NULL, + /* ret_n_devices= */ NULL); - case ARG_FIDO2_WITH_UV: - r = parse_boolean_argument("--fido2-with-user-verification=", optarg, NULL); + OPTION_LONG("wipe-slot", "SLOT1,SLOT2,…", + "Wipe specified slots"): + r = parse_wipe_slot(arg); if (r < 0) return r; - - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r); break; - case ARG_PASSWORD: - if (arg_enroll_type >= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Multiple operations specified at once, refusing."); - - arg_enroll_type = ENROLL_PASSWORD; - break; - - case ARG_RECOVERY_KEY: - if (arg_enroll_type >= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Multiple operations specified at once, refusing."); + OPTION_GROUP("Unlocking"): {} - arg_enroll_type = ENROLL_RECOVERY; - break; - - case ARG_UNLOCK_KEYFILE: + OPTION_LONG("unlock-key-file", "PATH", + "Use a file to unlock the volume"): if (arg_unlock_type != UNLOCK_PASSWORD) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple unlock methods specified at once, refusing."); - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_unlock_keyfile); + r = parse_path_argument(arg, /* suppress_root= */ true, &arg_unlock_keyfile); if (r < 0) return r; arg_unlock_type = UNLOCK_KEYFILE; break; - case ARG_UNLOCK_FIDO2_DEVICE: { + OPTION_LONG("unlock-fido2-device", "PATH", + "Use a FIDO2 device to unlock the volume"): { _cleanup_free_ char *device = NULL; if (arg_unlock_type != UNLOCK_PASSWORD) @@ -440,8 +331,8 @@ static int parse_argv(int argc, char *argv[]) { assert(!arg_unlock_fido2_device); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(arg, "auto")) { + device = strdup(arg); if (!device) return log_oom(); } @@ -451,7 +342,8 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_UNLOCK_TPM2_DEVICE: { + OPTION_LONG("unlock-tpm2-device", "PATH", + "Use a TPM2 device to unlock the volume"): { _cleanup_free_ char *device = NULL; if (arg_unlock_type != UNLOCK_PASSWORD) @@ -460,8 +352,8 @@ static int parse_argv(int argc, char *argv[]) { assert(!arg_unlock_tpm2_device); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(arg, "auto")) { + device = strdup(arg); if (!device) return log_oom(); } @@ -471,25 +363,48 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_PKCS11_TOKEN_URI: { + OPTION_GROUP("Simple Enrollment"): {} + + OPTION_LONG("password", NULL, + "Enroll a user-supplied password"): + if (arg_enroll_type >= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Multiple operations specified at once, refusing."); + + arg_enroll_type = ENROLL_PASSWORD; + break; + + OPTION_LONG("recovery-key", NULL, + "Enroll a recovery key"): + if (arg_enroll_type >= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Multiple operations specified at once, refusing."); + + arg_enroll_type = ENROLL_RECOVERY; + break; + + OPTION_GROUP("PKCS#11 Enrollment"): {} + + OPTION_LONG("pkcs11-token-uri", "URI|auto|list", + "Enroll a PKCS#11 security token or list them"): { _cleanup_free_ char *uri = NULL; - if (streq(optarg, "list")) + if (streq(arg, "list")) return pkcs11_list_tokens(); if (arg_enroll_type >= 0 || arg_pkcs11_token_uri) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple operations specified at once, refusing."); - if (streq(optarg, "auto")) { + if (streq(arg, "auto")) { r = pkcs11_find_token_auto(&uri); if (r < 0) return r; } else { - if (!pkcs11_uri_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", optarg); + if (!pkcs11_uri_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", arg); - uri = strdup(optarg); + uri = strdup(arg); if (!uri) return log_oom(); } @@ -499,24 +414,21 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_FIDO2_CRED_ALG: - r = parse_fido2_algorithm(optarg, &arg_fido2_cred_alg); - if (r < 0) - return log_error_errno(r, "Failed to parse COSE algorithm: %s", optarg); - break; + OPTION_GROUP("FIDO2 Enrollment"): {} - case ARG_FIDO2_DEVICE: { + OPTION_LONG("fido2-device", "PATH|auto|list", + "Enroll a FIDO2-HMAC security token or list them"): { _cleanup_free_ char *device = NULL; - if (streq(optarg, "list")) + if (streq(arg, "list")) return fido2_list_devices(); if (arg_enroll_type >= 0 || arg_fido2_device) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple operations specified at once, refusing."); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(arg, "auto")) { + device = strdup(arg); if (!device) return log_oom(); } @@ -526,32 +438,66 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_FIDO2_SALT_FILE: - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_fido2_salt_file); + OPTION_LONG("fido2-salt-file", "PATH", + "Use salt from a file instead of generating one"): + r = parse_path_argument(arg, /* suppress_root= */ true, &arg_fido2_salt_file); if (r < 0) return r; + break; + OPTION_LONG("fido2-parameters-in-header", "BOOL", + "Whether to store FIDO2 parameters in the LUKS2 header"): + r = parse_boolean_argument("--fido2-parameters-in-header=", arg, &arg_fido2_parameters_in_header); + if (r < 0) + return r; break; - case ARG_FIDO2_PARAMETERS_IN_HEADER: - r = parse_boolean_argument("--fido2-parameters-in-header=", optarg, &arg_fido2_parameters_in_header); + OPTION_LONG("fido2-credential-algorithm", "STRING", + "Specify COSE algorithm for FIDO2 credential"): + r = parse_fido2_algorithm(arg, &arg_fido2_cred_alg); + if (r < 0) + return log_error_errno(r, "Failed to parse COSE algorithm: %s", arg); + break; + + OPTION_LONG("fido2-with-client-pin", "BOOL", + "Whether to require entering a PIN to unlock the volume"): + r = parse_boolean_argument("--fido2-with-client-pin=", arg, NULL); if (r < 0) return r; + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r); + break; + OPTION_LONG("fido2-with-user-presence", "BOOL", + "Whether to require user presence to unlock the volume"): + r = parse_boolean_argument("--fido2-with-user-presence=", arg, NULL); + if (r < 0) + return r; + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r); + break; + + OPTION_LONG("fido2-with-user-verification", "BOOL", + "Whether to require user verification to unlock the volume"): + r = parse_boolean_argument("--fido2-with-user-verification=", arg, NULL); + if (r < 0) + return r; + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r); break; - case ARG_TPM2_DEVICE: { + OPTION_GROUP("TPM2 Enrollment"): {} + + OPTION_LONG("tpm2-device", "PATH|auto|list", + "Enroll a TPM2 device or list them"): { _cleanup_free_ char *device = NULL; - if (streq(optarg, "list")) + if (streq(arg, "list")) return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); if (arg_enroll_type >= 0 || arg_tpm2_device) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple operations specified at once, refusing."); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(arg, "auto")) { + device = strdup(arg); if (!device) return log_oom(); } @@ -561,115 +507,96 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_TPM2_DEVICE_KEY: + OPTION_LONG("tpm2-device-key", "PATH", + "Enroll a TPM2 device using its public key"): if (arg_enroll_type >= 0 || arg_tpm2_device_key) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple operations specified at once, refusing."); - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_device_key); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_device_key); if (r < 0) return r; arg_enroll_type = ENROLL_TPM2; break; - case ARG_TPM2_SEAL_KEY_HANDLE: - r = safe_atou32_full(optarg, 16, &arg_tpm2_seal_key_handle); + OPTION_LONG("tpm2-seal-key-handle", "HANDLE", + "Specify handle of key to use for sealing"): + r = safe_atou32_full(arg, 16, &arg_tpm2_seal_key_handle); if (r < 0) - return log_error_errno(r, "Could not parse TPM2 seal key handle index '%s': %m", optarg); - + return log_error_errno(r, "Could not parse TPM2 seal key handle index '%s': %m", arg); break; - case ARG_TPM2_PCRS: - r = tpm2_parse_pcr_argument_append(optarg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); + OPTION_LONG("tpm2-pcrs", "PCR1+PCR2+PCR3+…", + "Specify TPM2 PCRs to seal against"): + r = tpm2_parse_pcr_argument_append(arg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); if (r < 0) return r; - break; - case ARG_TPM2_PUBLIC_KEY: + OPTION_LONG("tpm2-public-key", "PATH", + "Enroll signed TPM2 PCR policy against PEM public key"): /* an empty argument disables loading a public key */ - if (isempty(optarg)) { + if (isempty(arg)) { arg_tpm2_load_public_key = false; arg_tpm2_public_key = mfree(arg_tpm2_public_key); break; } - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_public_key); if (r < 0) return r; arg_tpm2_load_public_key = true; - break; - case ARG_TPM2_PUBLIC_KEY_PCRS: + OPTION_LONG("tpm2-public-key-pcrs", "PCR1+PCR2+PCR3+…", + "Enroll signed TPM2 PCR policy for specified TPM2 PCRs"): auto_public_key_pcr_mask = false; - r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); + r = tpm2_parse_pcr_argument_to_mask(arg, &arg_tpm2_public_key_pcr_mask); if (r < 0) return r; - break; - case ARG_TPM2_SIGNATURE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_signature); + OPTION_LONG("tpm2-signature", "PATH", + "Validate public key enrollment works with JSON signature file"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_signature); if (r < 0) return r; - break; - case ARG_TPM2_PCRLOCK: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_pcrlock); + OPTION_LONG("tpm2-pcrlock", "PATH", + "Specify pcrlock policy to lock against"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_pcrlock); if (r < 0) return r; - auto_pcrlock = false; break; - case ARG_TPM2_WITH_PIN: - r = parse_boolean_argument("--tpm2-with-pin=", optarg, &arg_tpm2_pin); - if (r < 0) - return r; - - break; - - case ARG_WIPE_SLOT: - r = parse_wipe_slot(optarg); + OPTION_LONG("tpm2-with-pin", "BOOL", + "Whether to require entering a PIN to unlock the volume"): + r = parse_boolean_argument("--tpm2-with-pin=", arg, &arg_tpm2_pin); if (r < 0) return r; break; - - case ARG_LIST_DEVICES: - return blockdev_list(BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_REQUIRE_LUKS, - /* ret_devices= */ NULL, - /* ret_n_devices= */ NULL); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (argc > optind+1) + char **args = option_parser_get_args(&state); + + if (strv_length(args) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments, refusing."); - if (optind < argc) { - r = parse_path_argument(argv[optind], false, &arg_node); - if (r < 0) - return r; - } else { - if (wipe_requested()) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Wiping requested and no block device node specified, refusing."); - + if (args[0]) + r = parse_path_argument(args[0], false, &arg_node); + else if (!wipe_requested()) r = determine_default_node(); - if (r < 0) - return r; - } + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Wiping requested and no block device node specified, refusing."); + if (r < 0) + return r; if (arg_enroll_type == ENROLL_FIDO2) { - if (arg_unlock_type == UNLOCK_FIDO2 && !(arg_fido2_device && arg_unlock_fido2_device)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When both enrolling and unlocking with FIDO2 tokens, automatic discovery is unsupported. " @@ -722,7 +649,7 @@ static int check_for_homed(struct crypt_device *cd) { /* Politely refuse operating on homed volumes. The enrolled tokens for the user record and the LUKS2 * volume should not get out of sync. */ - for (int token = 0; token < crypt_token_max(CRYPT_LUKS2); token++) { + for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { r = cryptsetup_get_token_as_json(cd, token, "systemd-homed", NULL); if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE)) continue; @@ -761,7 +688,7 @@ static int load_volume_key_keyfile( if (r < 0) return log_error_errno(r, "Reading keyfile %s failed: %m", arg_unlock_keyfile); - r = crypt_volume_key_get( + r = sym_crypt_volume_key_get( cd, CRYPT_ANY_SLOT, ret_vk, @@ -783,13 +710,13 @@ static int prepare_luks( assert(ret_cd); - r = crypt_init(&cd, arg_node); + r = sym_crypt_init(&cd, arg_node); if (r < 0) return log_error_errno(r, "Failed to allocate libcryptsetup context: %m"); cryptsetup_enable_logging(cd); - r = crypt_load(cd, CRYPT_LUKS2, NULL); + r = sym_crypt_load(cd, CRYPT_LUKS2, NULL); if (r < 0) return log_error_errno(r, "Failed to load LUKS2 superblock of %s: %m", arg_node); @@ -802,7 +729,7 @@ static int prepare_luks( return 0; } - r = crypt_get_volume_key_size(cd); + r = sym_crypt_get_volume_key_size(cd); if (r <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size"); @@ -856,11 +783,13 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + r = dlopen_cryptsetup(LOG_ERR); + if (r < 0) + return r; + /* A delicious drop of snake oil */ (void) safe_mlockall(MCL_CURRENT|MCL_FUTURE|MCL_ONFAULT); - cryptsetup_enable_logging(NULL); - if (arg_enroll_type < 0) r = prepare_luks(&cd, /* ret_volume_key= */ NULL); /* No need to unlock device if we don't need the volume key because we don't need to enroll anything */ else diff --git a/src/cryptenroll/meson.build b/src/cryptenroll/meson.build index 488ceea14d1ef..8213a0e672572 100644 --- a/src/cryptenroll/meson.build +++ b/src/cryptenroll/meson.build @@ -21,9 +21,10 @@ executables += [ 'public' : true, 'sources' : systemd_cryptenroll_sources, 'dependencies' : [ - libcryptsetup, + libcryptsetup_cflags, libdl, - libopenssl, + libfido2_cflags, + libopenssl_cflags, libp11kit_cflags, ], }, diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c index 02ed4dd273c6f..a2804e033a1de 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c @@ -162,7 +162,7 @@ _public_ int cryptsetup_token_validate( assert(json); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m."); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c index 4c6e28500a396..16cf910fe6d89 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c @@ -115,7 +115,7 @@ _public_ int cryptsetup_token_validate( sd_json_variant *w; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m."); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index 933d18e2fd7a9..58dc37c5bfb74 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -72,7 +72,7 @@ _public_ int cryptsetup_token_open_pin( if (usrptr) params = *(systemd_tpm2_plugin_params *)usrptr; - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Failed to parse token JSON data: %m"); @@ -186,7 +186,7 @@ _public_ void cryptsetup_token_dump( assert(json); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON object: %m"); @@ -275,7 +275,7 @@ _public_ int cryptsetup_token_validate( assert(json); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m"); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c index 18b0e4f37f93f..c6cfdcf6efeb8 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c @@ -97,7 +97,7 @@ int parse_luks2_fido2_data( assert(ret_cid_size); assert(ret_required); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_error_errno(cd, r, "Failed to parse JSON token data: %m"); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c index 9f11f81c4ac7b..723265479cc1b 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c @@ -246,7 +246,7 @@ int parse_luks2_pkcs11_data( assert(ret_encrypted_key); assert(ret_encrypted_key_size); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return r; diff --git a/src/cryptsetup/cryptsetup-tokens/meson.build b/src/cryptsetup/cryptsetup-tokens/meson.build index 804e18bc67a2e..0fd6309201bb7 100644 --- a/src/cryptsetup/cryptsetup-tokens/meson.build +++ b/src/cryptsetup/cryptsetup-tokens/meson.build @@ -58,7 +58,7 @@ modules += [ 'sources' : cryptsetup_token_systemd_fido2_sources, 'dependencies' : [ libcryptsetup, - libfido2, + libfido2_cflags, ], }, template + { diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index dbd0b14d7bd1b..d772ba9a9afee 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -11,9 +10,9 @@ #include "sd-messages.h" #include "alloc-util.h" -#include "argv-util.h" #include "ask-password-api.h" #include "build.h" +#include "crypto-util.h" #include "cryptsetup-fido2.h" #include "cryptsetup-keyfile.h" #include "cryptsetup-pkcs11.h" @@ -27,6 +26,7 @@ #include "escape.h" #include "extract-word.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "hexdecoct.h" #include "json-util.h" @@ -36,6 +36,7 @@ #include "main-func.h" #include "memory-util.h" #include "nulstr-util.h" +#include "options.h" #include "parse-util.h" #include "path-util.h" #include "pkcs11-util.h" @@ -537,6 +538,10 @@ static int parse_one_option(const char *option) { #if HAVE_OPENSSL _cleanup_strv_free_ char **l = NULL; + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + l = strv_split(val, ":"); if (!l) return log_oom(); @@ -544,11 +549,11 @@ static int parse_one_option(const char *option) { STRV_FOREACH(i, l) { const EVP_MD *implementation; - implementation = EVP_get_digestbyname(*i); + implementation = sym_EVP_get_digestbyname(*i); if (!implementation) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", val); - if (strv_extend(&arg_tpm2_measure_banks, EVP_MD_name(implementation)) < 0) + if (strv_extend(&arg_tpm2_measure_banks, sym_EVP_MD_get0_name(implementation)) < 0) return log_oom(); } #else @@ -823,19 +828,19 @@ static PassphraseType check_registered_passwords(struct crypt_device *cd) { assert(cd); - if (!streq_ptr(crypt_get_type(cd), CRYPT_LUKS2)) { - log_debug("%s: not a LUKS2 device, only passphrases are supported", crypt_get_device_name(cd)); + if (!streq_ptr(sym_crypt_get_type(cd), CRYPT_LUKS2)) { + log_debug("%s: not a LUKS2 device, only passphrases are supported", sym_crypt_get_device_name(cd)); return PASSPHRASE_REGULAR; } /* Search all used slots */ - assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0); + assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0); slots = new(bool, slot_max); if (!slots) return log_oom(); for (int slot = 0; slot < slot_max; slot++) - slots[slot] = IN_SET(crypt_keyslot_status(cd, slot), CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST); + slots[slot] = IN_SET(sym_crypt_keyslot_status(cd, slot), CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST); /* Iterate all LUKS2 tokens and keep track of all their slots */ for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { @@ -1030,11 +1035,11 @@ static int measure_volume_key( return 0; } - r = efi_measured_uki(LOG_WARNING); + r = efi_measured_os(LOG_WARNING); if (r < 0) return r; if (r == 0) { - log_debug("Kernel stub did not measure kernel image into the expected PCR, skipping userspace volume key measurement, too."); + log_debug("OS measurements not explicitly requested and kernel stub did not measure kernel image into the expected PCR, skipping userspace volume key measurement, too."); return 0; } @@ -1109,11 +1114,11 @@ static int measure_keyslot( } #if HAVE_TPM2 - r = efi_measured_uki(LOG_WARNING); + r = efi_measured_os(LOG_WARNING); if (r < 0) return r; if (r == 0) { - log_debug("Kernel stub did not measure kernel image into the expected PCR, skipping userspace key slot measurement, too."); + log_debug("OS measurements not explicitly requested and kernel stub did not measure kernel image into the expected PCR, skipping userspace key slot measurement, too."); return 0; } @@ -1132,7 +1137,7 @@ static int measure_keyslot( return log_oom(); _cleanup_free_ char *s = NULL; - s = strjoin("cryptsetup-keyslot:", escaped, ":", strempty(crypt_get_uuid(cd)), ":", strempty(mechanism), ":", strempty(k)); + s = strjoin("cryptsetup-keyslot:", escaped, ":", strempty(sym_crypt_get_uuid(cd)), ":", strempty(mechanism), ":", strempty(k)); if (!s) return log_oom(); @@ -1207,7 +1212,7 @@ static int measured_crypt_activate_by_volume_key( key_id, arg_fixate_volume_key); } - r = crypt_activate_by_volume_key(cd, name, volume_key, volume_key_size, flags); + r = sym_crypt_activate_by_volume_key(cd, name, volume_key, volume_key_size, flags); if (r == -EEXIST) /* volume is already active */ return log_external_activation(r, name); if (r < 0) @@ -1250,7 +1255,7 @@ static int measured_crypt_activate_by_passphrase( if (arg_tpm2_measure_pcr == UINT_MAX && !arg_fixate_volume_key) goto shortcut; - r = crypt_get_volume_key_size(cd); + r = sym_crypt_get_volume_key_size(cd); if (r < 0) return r; if (r == 0) { @@ -1262,14 +1267,14 @@ static int measured_crypt_activate_by_passphrase( if (!vk) return -ENOMEM; - keyslot = crypt_volume_key_get(cd, keyslot, vk, &vks, passphrase, passphrase_size); + keyslot = sym_crypt_volume_key_get(cd, keyslot, vk, &vks, passphrase, passphrase_size); if (keyslot < 0) return keyslot; return measured_crypt_activate_by_volume_key(cd, name, mechanism, keyslot, vk, vks, flags); shortcut: - keyslot = crypt_activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags); + keyslot = sym_crypt_activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags); if (keyslot == -EEXIST) /* volume is already active */ return log_external_activation(keyslot, name); if (keyslot < 0) @@ -1320,7 +1325,7 @@ static int attach_tcrypt( if (key_data) { params.passphrase = key_data->iov_base; params.passphrase_size = key_data->iov_len; - r = crypt_load(cd, CRYPT_TCRYPT, ¶ms); + r = sym_crypt_load(cd, CRYPT_TCRYPT, ¶ms); } else if (key_file) { r = read_one_line_file(key_file, &passphrase); if (r < 0) { @@ -1329,13 +1334,13 @@ static int attach_tcrypt( } params.passphrase = passphrase; params.passphrase_size = strlen(passphrase); - r = crypt_load(cd, CRYPT_TCRYPT, ¶ms); + r = sym_crypt_load(cd, CRYPT_TCRYPT, ¶ms); } else { r = -EINVAL; STRV_FOREACH(p, passwords){ params.passphrase = *p; params.passphrase_size = strlen(*p); - r = crypt_load(cd, CRYPT_TCRYPT, ¶ms); + r = sym_crypt_load(cd, CRYPT_TCRYPT, ¶ms); if (r >= 0) break; } @@ -1353,7 +1358,7 @@ static int attach_tcrypt( return r; } - return log_error_errno(r, "Failed to load tcrypt superblock on device %s: %m", crypt_get_device_name(cd)); + return log_error_errno(r, "Failed to load tcrypt superblock on device %s: %m", sym_crypt_get_device_name(cd)); } r = measured_crypt_activate_by_volume_key( @@ -1365,7 +1370,7 @@ static int attach_tcrypt( /* volume_key_size= */ 0, flags); if (r < 0) - return log_error_errno(r, "Failed to activate tcrypt device %s: %m", crypt_get_device_name(cd)); + return log_error_errno(r, "Failed to activate tcrypt device %s: %m", sym_crypt_get_device_name(cd)); return 0; } @@ -1509,7 +1514,7 @@ static bool use_token_plugins(void) { if (r == 0) return false; - return crypt_token_external_path(); + return sym_crypt_token_external_path(); #else return false; #endif @@ -1554,7 +1559,7 @@ static int crypt_activate_by_token_pin_ask_password( _cleanup_strv_free_erase_ char **pins = NULL; int r; - r = crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, /* pin= */ NULL, /* pin_size= */ 0, userdata, activation_flags); + r = sym_crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, /* pin= */ NULL, /* pin_size= */ 0, userdata, activation_flags); if (r > 0) /* returns unlocked keyslot id on success */ return 0; if (r == -EEXIST) /* volume is already active */ @@ -1567,7 +1572,7 @@ static int crypt_activate_by_token_pin_ask_password( return r; STRV_FOREACH(p, pins) { - r = crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, *p, strlen(*p), userdata, activation_flags); + r = sym_crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, *p, strlen(*p), userdata, activation_flags); if (r > 0) /* returns unlocked keyslot id on success */ return 0; if (r == -EEXIST) /* volume is already active */ @@ -1597,7 +1602,7 @@ static int crypt_activate_by_token_pin_ask_password( return r; STRV_FOREACH(p, pins) { - r = crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, *p, strlen(*p), userdata, activation_flags); + r = sym_crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, *p, strlen(*p), userdata, activation_flags); if (r > 0) /* returns unlocked keyslot id on success */ return 0; if (r == -EEXIST) /* volume is already active */ @@ -1658,7 +1663,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "FIDO2 mode with manual parameters selected, but no keyfile specified, refusing."); - friendly = friendly_disk_name(crypt_get_device_name(cd), name); + friendly = friendly_disk_name(sym_crypt_get_device_name(cd), name); if (!friendly) return log_oom(); @@ -1776,7 +1781,7 @@ static int attach_luks2_by_pkcs11_via_plugin( #if HAVE_LIBCRYPTSETUP_PLUGINS int r; - if (!streq_ptr(crypt_get_type(cd), CRYPT_LUKS2)) + if (!streq_ptr(sym_crypt_get_type(cd), CRYPT_LUKS2)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Automatic PKCS#11 metadata requires LUKS2 device."); systemd_pkcs11_plugin_params params = { @@ -1786,7 +1791,7 @@ static int attach_luks2_by_pkcs11_via_plugin( .askpw_flags = arg_ask_password_flags, }; - r = crypt_activate_by_token_pin(cd, name, "systemd-pkcs11", CRYPT_ANY_TOKEN, NULL, 0, ¶ms, flags); + r = sym_crypt_activate_by_token_pin(cd, name, "systemd-pkcs11", CRYPT_ANY_TOKEN, NULL, 0, ¶ms, flags); if (r > 0) /* returns unlocked keyslot id on success */ r = 0; if (r == -EEXIST) /* volume is already active */ @@ -1842,7 +1847,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PKCS#11 mode selected but no key file specified, refusing."); } - friendly = friendly_disk_name(crypt_get_device_name(cd), name); + friendly = friendly_disk_name(sym_crypt_get_device_name(cd), name); if (!friendly) return log_oom(); @@ -2036,7 +2041,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( assert(name); assert(arg_tpm2_device || arg_tpm2_device_auto); - friendly = friendly_disk_name(crypt_get_device_name(cd), name); + friendly = friendly_disk_name(sym_crypt_get_device_name(cd), name); if (!friendly) return log_oom(); @@ -2382,7 +2387,7 @@ static int attach_luks_or_plain_or_bitlk( assert(cd); assert(name); - if ((!arg_type && !crypt_get_type(cd)) || streq_ptr(arg_type, CRYPT_PLAIN)) { + if ((!arg_type && !sym_crypt_get_type(cd)) || streq_ptr(arg_type, CRYPT_PLAIN)) { struct crypt_params_plain params = { .offset = arg_offset, .skip = arg_skip, @@ -2421,7 +2426,7 @@ static int attach_luks_or_plain_or_bitlk( /* In contrast to what the name crypt_format() might suggest this doesn't actually format * anything, it just configures encryption parameters when used for plain mode. */ - r = crypt_format(cd, CRYPT_PLAIN, cipher, cipher_mode, NULL, NULL, arg_keyfile_size, ¶ms); + r = sym_crypt_format(cd, CRYPT_PLAIN, cipher, cipher_mode, NULL, NULL, arg_keyfile_size, ¶ms); if (r < 0) return log_error_errno(r, "Loading of cryptographic parameters failed: %m"); @@ -2430,10 +2435,10 @@ static int attach_luks_or_plain_or_bitlk( } log_info("Set cipher %s, mode %s, key size %i bits for device %s.", - crypt_get_cipher(cd), - crypt_get_cipher_mode(cd), - crypt_get_volume_key_size(cd)*8, - crypt_get_device_name(cd)); + sym_crypt_get_cipher(cd), + sym_crypt_get_cipher_mode(cd), + sym_crypt_get_volume_key_size(cd)*8, + sym_crypt_get_device_name(cd)); if (token_type == TOKEN_TPM2) return attach_luks_or_plain_or_bitlk_by_tpm2(cd, name, key_file, key_data, until, flags, pass_volume_key); @@ -2451,61 +2456,69 @@ static int attach_luks_or_plain_or_bitlk( static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-cryptsetup", "8", &link); if (r < 0) return log_oom(); - printf("%1$s attach VOLUME SOURCE-DEVICE [KEY-FILE] [CONFIG]\n" - "%1$s detach VOLUME\n\n" - "%2$sAttach or detach an encrypted block device.%3$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\nSee the %4$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "%sAttach or detach an encrypted block device.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, ansi_highlight(), ansi_normal(), - link); + ansi_underline(), + ansi_normal()); - return 0; -} + r = table_print_or_warn(verbs); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); + return 0; +} - int c; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - if (argv_looks_like_help(argc, argv)) - return help(); + OptionParser state = { argc, argv }; + const char *arg; - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } @@ -2540,6 +2553,8 @@ static uint32_t determine_flags(void) { static void remove_and_erasep(const char **p) { int r; + assert(p); + if (!*p) return; @@ -2586,7 +2601,9 @@ static int discover_key(const char *key_file, const char *volume, TokenType toke return r; } -static int verb_attach(int argc, char *argv[], void *userdata) { +VERB(verb_attach, "attach", "VOLUME SOURCE-DEVICE [KEY-FILE] [CONFIG]", 3, 5, 0, + "Attach an encrypted block device"); +static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _unused_ _cleanup_(remove_and_erasep) const char *destroy_key_file = NULL; crypt_status_info status; @@ -2631,19 +2648,19 @@ static int verb_attach(int argc, char *argv[], void *userdata) { if (arg_header) { if (streq_ptr(arg_type, CRYPT_TCRYPT)){ log_debug("tcrypt header: %s", arg_header); - r = crypt_init_data_device(&cd, arg_header, source); + r = sym_crypt_init_data_device(&cd, arg_header, source); } else { log_debug("LUKS header: %s", arg_header); - r = crypt_init(&cd, arg_header); + r = sym_crypt_init(&cd, arg_header); } } else - r = crypt_init(&cd, source); + r = sym_crypt_init(&cd, source); if (r < 0) return log_error_errno(r, "crypt_init() failed: %m"); cryptsetup_enable_logging(cd); - status = crypt_status(cd, volume); + status = sym_crypt_status(cd, volume); if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { log_info("Volume %s already active.", volume); return 0; @@ -2668,21 +2685,21 @@ static int verb_attach(int argc, char *argv[], void *userdata) { } if (!arg_type || STR_IN_SET(arg_type, ANY_LUKS, CRYPT_LUKS1, CRYPT_LUKS2)) { - r = crypt_load(cd, !arg_type || streq(arg_type, ANY_LUKS) ? CRYPT_LUKS : arg_type, NULL); + r = sym_crypt_load(cd, !arg_type || streq(arg_type, ANY_LUKS) ? CRYPT_LUKS : arg_type, NULL); if (r < 0) - return log_error_errno(r, "Failed to load LUKS superblock on device %s: %m", crypt_get_device_name(cd)); + return log_error_errno(r, "Failed to load LUKS superblock on device %s: %m", sym_crypt_get_device_name(cd)); /* since cryptsetup 2.7.0 (Jan 2024) */ #if HAVE_CRYPT_SET_KEYRING_TO_LINK if (arg_link_key_description) { - r = crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring); + r = sym_crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring); if (r < 0) log_warning_errno(r, "Failed to set keyring or key description to link volume key in, ignoring: %m"); } #endif if (arg_header) { - r = crypt_set_data_device(cd, source); + r = sym_crypt_set_data_device(cd, source); if (r < 0) return log_error_errno(r, "Failed to set LUKS data device %s: %m", source); } @@ -2704,14 +2721,14 @@ static int verb_attach(int argc, char *argv[], void *userdata) { return 0; } - log_debug_errno(r, "Token activation unsuccessful for device %s: %m", crypt_get_device_name(cd)); + log_debug_errno(r, "Token activation unsuccessful for device %s: %m", sym_crypt_get_device_name(cd)); } } if (streq_ptr(arg_type, CRYPT_BITLK)) { - r = crypt_load(cd, CRYPT_BITLK, NULL); + r = sym_crypt_load(cd, CRYPT_BITLK, NULL); if (r < 0) - return log_error_errno(r, "Failed to load Bitlocker superblock on device %s: %m", crypt_get_device_name(cd)); + return log_error_errno(r, "Failed to load Bitlocker superblock on device %s: %m", sym_crypt_get_device_name(cd)); } bool use_cached_passphrase = true, try_discover_key = !key_file; @@ -2826,7 +2843,9 @@ static int verb_attach(int argc, char *argv[], void *userdata) { return 0; } -static int verb_detach(int argc, char *argv[], void *userdata) { +VERB(verb_detach, "detach", "VOLUME", 2, 2, 0, + "Detach an encrypted block device"); +static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; const char *volume = ASSERT_PTR(argv[1]); int r; @@ -2836,7 +2855,7 @@ static int verb_detach(int argc, char *argv[], void *userdata) { if (!filename_is_valid(volume)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - r = crypt_init_by_name(&cd, volume); + r = sym_crypt_init_by_name(&cd, volume); if (r == -ENODEV) { log_info("Volume %s already inactive.", volume); return 0; @@ -2846,7 +2865,7 @@ static int verb_detach(int argc, char *argv[], void *userdata) { cryptsetup_enable_logging(cd); - r = crypt_deactivate(cd, volume); + r = sym_crypt_deactivate(cd, volume); if (r < 0) return log_error_errno(r, "Failed to deactivate '%s': %m", volume); @@ -2860,19 +2879,16 @@ static int run(int argc, char *argv[]) { umask(0022); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - cryptsetup_enable_logging(NULL); - - static const Verb verbs[] = { - { "attach", 3, 5, 0, verb_attach }, - { "detach", 2, 2, 0, verb_detach }, - {} - }; + r = dlopen_cryptsetup(LOG_ERR); + if (r < 0) + return r; - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/cryptsetup/meson.build b/src/cryptsetup/meson.build index d9778259c2fce..9b7f3fa344da5 100644 --- a/src/cryptsetup/meson.build +++ b/src/cryptsetup/meson.build @@ -18,9 +18,10 @@ executables += [ 'public' : true, 'sources' : systemd_cryptsetup_sources, 'dependencies' : [ - libcryptsetup, + libcryptsetup_cflags, + libfido2_cflags, libmount_cflags, - libopenssl, + libopenssl_cflags, libp11kit_cflags, ], }, diff --git a/src/debug-generator/debug-generator.c b/src/debug-generator/debug-generator.c index 878e1152328ae..9ef271343cc59 100644 --- a/src/debug-generator/debug-generator.c +++ b/src/debug-generator/debug-generator.c @@ -101,7 +101,8 @@ static int parse_breakpoint_from_string(const char *s, uint32_t *ret_breakpoints FOREACH_ELEMENT(i, breakpoint_info_table) if (FLAGS_SET(i->validity, BREAKPOINT_DEFAULT) && breakpoint_applies(i, INT_MAX)) { - breakpoints |= 1 << i->type; + assert(i->type >= 0 && i->type < _BREAKPOINT_TYPE_MAX); /* silence coverity */ + breakpoints |= UINT32_C(1) << i->type; found_default = true; break; } @@ -127,7 +128,7 @@ static int parse_breakpoint_from_string(const char *s, uint32_t *ret_breakpoints } if (breakpoint_applies(&breakpoint_info_table[tt], LOG_WARNING)) - breakpoints |= 1 << tt; + breakpoints |= UINT32_C(1) << tt; } *ret_breakpoints = breakpoints; diff --git a/src/delta/delta.c b/src/delta/delta.c index df28a730a54e6..4a134ad2355f3 100644 --- a/src/delta/delta.c +++ b/src/delta/delta.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" @@ -10,12 +9,14 @@ #include "errno-util.h" #include "extract-word.h" #include "fd-util.h" +#include "format-table.h" #include "fs-util.h" #include "glyph-util.h" #include "hashmap.h" #include "log.h" #include "main-func.h" #include "nulstr-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "path-util.h" @@ -459,23 +460,26 @@ static int process_suffix_chop(const char *arg) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-delta", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] [SUFFIX...]\n\n" - "Find overridden configuration files.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --diff[=1|0] Show a diff when overridden files differ\n" - " -t --type=LIST... Only display a selected set of override types\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + "Find overridden configuration files.\n\n", + program_invocation_short_name); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } @@ -509,66 +513,45 @@ static int parse_flags(const char *flag_str, int flags) { } } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_NO_PAGER = 0x100, - ARG_DIFF, - ARG_VERSION - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "diff", optional_argument, NULL, ARG_DIFF }, - { "type", required_argument, NULL, 't' }, - {} - }; - - int c, r; - - assert(argc >= 1); +static int parse_argv(int argc, char *argv[], char ***ret_args) { + assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case 't': { - int f; - f = parse_flags(optarg, arg_flags); - if (f < 0) + OPTION('t', "type", "TYPE...", "Only display a selected set of override types"): + r = parse_flags(arg, arg_flags); + if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse flags field."); - arg_flags = f; + arg_flags = r; break; - } - case ARG_DIFF: - r = parse_boolean_argument("--diff", optarg, NULL); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "diff", "yes|no", + "Show a diff when overridden files differ"): + r = parse_boolean_argument("--diff", arg, NULL); if (r < 0) return r; arg_diff = r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } @@ -577,7 +560,8 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -591,17 +575,16 @@ static int run(int argc, char *argv[]) { pager_open(arg_pager_flags); - if (optind < argc) { - for (int i = optind; i < argc; i++) { - path_simplify(argv[i]); + if (!strv_isempty(args)) { + STRV_FOREACH(i, args) { + path_simplify(*i); - k = process_suffix_chop(argv[i]); + k = process_suffix_chop(*i); if (k < 0) r = k; else n_found += k; } - } else { k = process_suffixes(NULL); if (k < 0) diff --git a/src/detect-virt/detect-virt.c b/src/detect-virt/detect-virt.c index d28e3024805e0..3c5b3dd39cb34 100644 --- a/src/detect-virt/detect-virt.c +++ b/src/detect-virt/detect-virt.c @@ -1,12 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "confidential-virt.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pretty-print.h" #include "string-table.h" #include "virt.h" @@ -23,109 +24,78 @@ static enum { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-detect-virt", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...]\n\n" - "Detect execution in a virtualized environment.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -c --container Only detect whether we are run in a container\n" - " -v --vm Only detect whether we are run in a VM\n" - " -r --chroot Detect whether we are run in a chroot() environment\n" - " --private-users Only detect whether we are running in a user namespace\n" - " --cvm Only detect whether we are run in a confidential VM\n" - " -q --quiet Don't output anything, just set return value\n" - " --list List all known and detectable types of virtualization\n" - " --list-cvm List all known and detectable types of confidential \n" - " virtualization\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\n%sDetect execution in a virtualized environment.%s\n" + "\nOptions:\n", program_invocation_short_name, - link); + ansi_highlight(), + ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_PRIVATE_USERS, - ARG_LIST, - ARG_CVM, - ARG_LIST_CVM, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "container", no_argument, NULL, 'c' }, - { "vm", no_argument, NULL, 'v' }, - { "chroot", no_argument, NULL, 'r' }, - { "private-users", no_argument, NULL, ARG_PRIVATE_USERS }, - { "quiet", no_argument, NULL, 'q' }, - { "cvm", no_argument, NULL, ARG_CVM }, - { "list", no_argument, NULL, ARG_LIST }, - { "list-cvm", no_argument, NULL, ARG_LIST_CVM }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hqcvr", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'q': + OPTION('q', "quiet", NULL, "Don't output anything, just set return value"): arg_quiet = true; break; - case 'c': + OPTION('c', "container", NULL, "Only detect whether we are run in a container"): arg_mode = ONLY_CONTAINER; break; - case ARG_PRIVATE_USERS: + OPTION_LONG("private-users", NULL, "Only detect whether we are running in a user namespace"): arg_mode = ONLY_PRIVATE_USERS; break; - case 'v': + OPTION('v', "vm", NULL, "Only detect whether we are run in a VM"): arg_mode = ONLY_VM; break; - case 'r': + OPTION('r', "chroot", NULL, "Detect whether we are run in a chroot() environment"): arg_mode = ONLY_CHROOT; break; - case ARG_LIST: + OPTION_LONG("list", NULL, "List all known and detectable types of virtualization"): return DUMP_STRING_TABLE(virtualization, Virtualization, _VIRTUALIZATION_MAX); - case ARG_CVM: + OPTION_LONG("cvm", NULL, "Only detect whether we are run in a confidential VM"): arg_mode = ONLY_CVM; return 1; - case ARG_LIST_CVM: + OPTION_LONG("list-cvm", NULL, "List all known and detectable types of confidential virtualization"): return DUMP_STRING_TABLE(confidential_virtualization, ConfidentialVirtualization, _CONFIDENTIAL_VIRTUALIZATION_MAX); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s takes no arguments.", program_invocation_short_name); diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index aafbd872ae713..973a954acd1c2 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -42,6 +42,7 @@ #include "mountpoint-util.h" #include "namespace-util.h" #include "nsresource.h" +#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" @@ -122,6 +123,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_filter, image_filter_freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *commands = NULL; int r; pager_open(arg_pager_flags); @@ -130,6 +132,17 @@ static int help(void) { if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("Commands", &commands); + if (r < 0) + return r; + + /* Make the 1st column same width in both tables */ + (void) table_sync_column_widths(0, options, commands); + printf("%1$s [OPTIONS...] IMAGE\n" "%1$s [OPTIONS...] --mount IMAGE PATH\n" "%1$s [OPTIONS...] --umount PATH\n" @@ -144,110 +157,26 @@ static int help(void) { "%1$s [OPTIONS...] --discover\n" "%1$s [OPTIONS...] --validate IMAGE\n" "%1$s [OPTIONS...] --shift IMAGE UIDBASE\n" - "\n%5$sDissect a Discoverable Disk Image (DDI).%6$s\n\n" - "%3$sOptions:%4$s\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " -r --read-only Mount read-only\n" - " --fsck=BOOL Run fsck before mounting\n" - " --growfs=BOOL Grow file system to partition size, if marked\n" - " --mkdir Make mount directory before mounting, if missing\n" - " --rmdir Remove mount directory after unmounting\n" - " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n" - " --in-memory Copy image into memory\n" - " --root-hash=HASH Specify root hash for verity\n" - " --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n" - " as a DER encoded PKCS7, either as a path to a file\n" - " or as an ASCII base64 encoded string prefixed by\n" - " 'base64:'\n" - " --verity-data=PATH Specify data file with hash tree for verity if it is\n" - " not embedded in IMAGE\n" - " --image-policy=POLICY\n" - " Specify image dissection policy\n" - " --image-filter=FILTER\n" - " Specify image dissection filter\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " --loop-ref=NAME Set reference string for loopback device\n" - " --loop-ref-auto Derive reference string from image file name\n" - " --mtree-hash=BOOL Whether to include SHA256 hash in the mtree output\n" - " --copy-ownership=BOOL\n" - " Whether to copy ownership when copying files\n" - " --user Discover user images\n" - " --system Discover system images\n" - " --all Show hidden images too\n" - "\n%3$sCommands:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -m --mount Mount the image to the specified directory\n" - " -M Shortcut for --mount --mkdir\n" - " -u --umount Unmount the image from the specified directory\n" - " -U Shortcut for --umount --rmdir\n" - " --attach Attach the disk image to a loopback block device\n" - " --detach Detach a loopback block device again\n" - " -l --list List all the files and directories of the specified\n" - " OS image\n" - " --mtree Show BSD mtree manifest of OS image\n" - " --with Mount, run command, unmount\n" - " -x --copy-from Copy files from image to host\n" - " -a --copy-to Copy files from host to image\n" - " --make-archive Convert the DDI to an archive file\n" - " --discover Discover DDIs in well known directories\n" - " --validate Validate image and image policy\n" - " --shift Shift UID range to selected base\n" - " -q --quiet Suppress output of chosen loopback block device\n" - "\nSee the %2$s for details.\n", + "\n%2$sDissect a Discoverable Disk Image (DDI).%3$s\n" + "\n%4$sOptions:%5$s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); - return 0; -} - -static int patch_argv(int *argc, char ***argv, char ***buf) { - _cleanup_free_ char **l = NULL; - char **e; - - assert(argc); - assert(*argc >= 0); - assert(argv); - assert(*argv); - assert(buf); - - /* Ugly hack: if --with is included in command line, also insert "--" immediately after it, to make - * getopt_long() stop processing switches */ - - for (e = *argv + 1; e < *argv + *argc; e++) { - assert(*e); - - if (streq(*e, "--with")) - break; - } - - if (e >= *argv + *argc || streq_ptr(e[1], "--")) { - /* No --with used? Or already followed by "--"? Then don't do anything */ - *buf = NULL; - return 0; - } - - /* Insert the extra "--" right after the --with */ - l = new(char*, *argc + 2); - if (!l) - return log_oom(); + r = table_print_or_warn(options); + if (r < 0) + return r; - size_t idx = e - *argv + 1; - memcpy(l, *argv, sizeof(char*) * idx); /* copy everything up to and including the --with */ - l[idx] = (char*) "--"; /* insert "--" */ - memcpy(l + idx + 1, e + 1, sizeof(char*) * (*argc - idx + 1)); /* copy the rest, including trailing NULL entry */ + printf("\n%sCommands:%s\n", ansi_underline(), ansi_normal()); - (*argc)++; - (*argv) = l; + r = table_print_or_warn(commands); + if (r < 0) + return r; - *buf = TAKE_PTR(l); - return 1; + printf("\nSee the %s for details.\n", link); + return 0; } static int parse_image_path_argument(const char *path, char **ret_root, char **ret_image) { @@ -261,6 +190,25 @@ static int parse_image_path_argument(const char *path, char **ret_root, char **r if (r < 0) return r; + /* If we got a sysfs path (e.g. from a udev-instantiated template unit's %f specifier), + * resolve it to the corresponding devnode. */ + if (path_startswith(p, "/sys/")) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + const char *devname; + + r = sd_device_new_from_syspath(&dev, p); + if (r < 0) + return log_error_errno(r, "Failed to get device from syspath '%s': %m", p); + + r = sd_device_get_devname(dev, &devname); + if (r < 0) + return log_error_errno(r, "Failed to get devname for '%s': %m", p); + + r = free_and_strdup(&p, devname); + if (r < 0) + return log_oom(); + } + if (stat(p, &st) < 0) return log_error_errno(errno, "Failed to stat %s: %m", p); @@ -276,190 +224,51 @@ static int parse_image_path_argument(const char *path, char **ret_root, char **r } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_WITH, - ARG_DISCARD, - ARG_FSCK, - ARG_GROWFS, - ARG_ROOT_HASH, - ARG_ROOT_HASH_SIG, - ARG_USR_HASH, - ARG_USR_HASH_SIG, - ARG_VERITY_DATA, - ARG_MKDIR, - ARG_RMDIR, - ARG_IN_MEMORY, - ARG_JSON, - ARG_MTREE, - ARG_DISCOVER, - ARG_ATTACH, - ARG_DETACH, - ARG_LOOP_REF, - ARG_LOOP_REF_AUTO, - ARG_IMAGE_POLICY, - ARG_VALIDATE, - ARG_MTREE_HASH, - ARG_MAKE_ARCHIVE, - ARG_SHIFT, - ARG_SYSTEM, - ARG_USER, - ARG_ALL, - ARG_IMAGE_FILTER, - ARG_COPY_OWNERSHIP, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "mount", no_argument, NULL, 'm' }, - { "umount", no_argument, NULL, 'u' }, - { "attach", no_argument, NULL, ARG_ATTACH }, - { "detach", no_argument, NULL, ARG_DETACH }, - { "with", no_argument, NULL, ARG_WITH }, - { "read-only", no_argument, NULL, 'r' }, - { "discard", required_argument, NULL, ARG_DISCARD }, - { "fsck", required_argument, NULL, ARG_FSCK }, - { "growfs", required_argument, NULL, ARG_GROWFS }, - { "root-hash", required_argument, NULL, ARG_ROOT_HASH }, - { "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG }, - { "usr-hash", required_argument, NULL, ARG_USR_HASH }, - { "usr-hash-sig", required_argument, NULL, ARG_USR_HASH_SIG }, - { "verity-data", required_argument, NULL, ARG_VERITY_DATA }, - { "mkdir", no_argument, NULL, ARG_MKDIR }, - { "rmdir", no_argument, NULL, ARG_RMDIR }, - { "in-memory", no_argument, NULL, ARG_IN_MEMORY }, - { "list", no_argument, NULL, 'l' }, - { "mtree", no_argument, NULL, ARG_MTREE }, - { "copy-from", no_argument, NULL, 'x' }, - { "copy-to", no_argument, NULL, 'a' }, - { "json", required_argument, NULL, ARG_JSON }, - { "discover", no_argument, NULL, ARG_DISCOVER }, - { "loop-ref", required_argument, NULL, ARG_LOOP_REF }, - { "loop-ref-auto", no_argument, NULL, ARG_LOOP_REF_AUTO }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "validate", no_argument, NULL, ARG_VALIDATE }, - { "mtree-hash", required_argument, NULL, ARG_MTREE_HASH }, - { "make-archive", no_argument, NULL, ARG_MAKE_ARCHIVE }, - { "shift", no_argument, NULL, ARG_SHIFT }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "all", no_argument, NULL, ARG_ALL }, - { "quiet", no_argument, NULL, 'q' }, - { "image-filter", required_argument, NULL, ARG_IMAGE_FILTER }, - { "copy-ownership", required_argument, NULL, ARG_COPY_OWNERSHIP }, - {} - }; - - _cleanup_free_ char **buf = NULL; /* we use free(), not strv_free() here, as we don't copy the strings here */ bool system_scope_requested = false, user_scope_requested = false; - int c, r; + int r; assert(argc >= 0); assert(argv); - r = patch_argv(&argc, &argv, &buf); - if (r < 0) - return r; - - while ((c = getopt_long(argc, argv, "hmurMUlxaq", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const Option *current; + const char *arg; + FOREACH_OPTION_FULL(&state, c, ¤t, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(); - - case ARG_VERSION: - return version(); - - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case 'm': - arg_action = ACTION_MOUNT; - break; - - case ARG_MKDIR: - arg_flags |= DISSECT_IMAGE_MKDIR; - break; - - case 'M': - /* Shortcut combination of the above two */ - arg_action = ACTION_MOUNT; + OPTION_LONG("mkdir", NULL, "Make mount directory before mounting, if missing"): arg_flags |= DISSECT_IMAGE_MKDIR; break; - case 'u': - arg_action = ACTION_UMOUNT; - break; - - case ARG_RMDIR: + OPTION_LONG("rmdir", NULL, "Remove mount directory after unmounting"): arg_rmdir = true; break; - case 'U': - /* Shortcut combination of the above two */ - arg_action = ACTION_UMOUNT; - arg_rmdir = true; - break; - - case ARG_ATTACH: - arg_action = ACTION_ATTACH; - break; - - case ARG_DETACH: - arg_action = ACTION_DETACH; - break; - - case 'l': - arg_action = ACTION_LIST; + OPTION('r', "read-only", NULL, "Mount read-only"): arg_flags |= DISSECT_IMAGE_READ_ONLY; break; - case ARG_MTREE: - arg_action = ACTION_MTREE; - arg_flags |= DISSECT_IMAGE_READ_ONLY; - break; - - case ARG_WITH: - arg_action = ACTION_WITH; - break; - - case 'x': - arg_action = ACTION_COPY_FROM; - arg_flags |= DISSECT_IMAGE_READ_ONLY; - break; - - case 'a': - arg_action = ACTION_COPY_TO; - break; - - case 'r': - arg_flags |= DISSECT_IMAGE_READ_ONLY; - break; - - case ARG_DISCARD: { + OPTION_LONG("discard", "MODE", "Choose discard mode (disabled, loop, all, crypto)"): { DissectImageFlags flags; - if (streq(optarg, "disabled")) + if (streq(arg, "disabled")) flags = 0; - else if (streq(optarg, "loop")) + else if (streq(arg, "loop")) flags = DISSECT_IMAGE_DISCARD_ON_LOOP; - else if (streq(optarg, "all")) + else if (streq(arg, "all")) flags = DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD; - else if (streq(optarg, "crypt")) + else if (streq(arg, "crypt")) flags = DISSECT_IMAGE_DISCARD_ANY; - else if (streq(optarg, "list")) { + else if (streq(arg, "list")) { puts("disabled\n" "all\n" "crypt\n" @@ -467,32 +276,32 @@ static int parse_argv(int argc, char *argv[]) { return 0; } else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown --discard= parameter: %s", - optarg); + "Unknown --discard= parameter: %s", arg); arg_flags = (arg_flags & ~DISSECT_IMAGE_DISCARD_ANY) | flags; break; } - case ARG_IN_MEMORY: + OPTION_LONG("in-memory", NULL, "Copy image into memory"): arg_in_memory = true; break; - case ARG_ROOT_HASH: - case ARG_USR_HASH: { + OPTION_LONG("root-hash", "HASH", "Specify root hash for verity"): {} + OPTION_LONG("usr-hash", "HASH", "Same, but for the usr partition"): { _cleanup_(iovec_done) struct iovec roothash = {}; - PartitionDesignator d = c == ARG_USR_HASH ? PARTITION_USR : PARTITION_ROOT; + PartitionDesignator d = streq(current->long_code, "root-hash") ? PARTITION_ROOT : PARTITION_USR; if (arg_verity_settings.designator >= 0 && arg_verity_settings.designator != d) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --root-hash=/--root-hash-sig= and --usr-hash=/--usr-hash-sig= options."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Cannot combine --root-hash=/--root-hash-sig= and --usr-hash=/--usr-hash-sig= options."); - r = unhexmem(optarg, &roothash.iov_base, &roothash.iov_len); + r = unhexmem(arg, &roothash.iov_base, &roothash.iov_len); if (r < 0) - return log_error_errno(r, "Failed to parse root hash '%s': %m", optarg); + return log_error_errno(r, "Failed to parse root hash '%s': %m", arg); if (roothash.iov_len < sizeof(sd_id128_t)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Root hash must be at least 128-bit long: %s", optarg); + "Root hash must be at least 128-bit long: %s", arg); iovec_done(&arg_verity_settings.root_hash); arg_verity_settings.root_hash = TAKE_STRUCT(roothash); @@ -500,24 +309,28 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_ROOT_HASH_SIG: - case ARG_USR_HASH_SIG: { - char *value; + OPTION_LONG("root-hash-sig", "SIG", + "Specify signature of root hash for verity as DER-encoded PKCS7, " + "either as a path to a file or as an ASCII base64-encoded string " + "prefixed by 'base64:'"): {} + OPTION_LONG("usr-hash-sig", "SIG", "Same, but for the usr partition"): { + const char *value; _cleanup_(iovec_done) struct iovec sig = {}; - PartitionDesignator d = c == ARG_USR_HASH_SIG ? PARTITION_USR : PARTITION_ROOT; + PartitionDesignator d = streq(current->long_code, "root-hash-sig") ? PARTITION_ROOT : PARTITION_USR; if (arg_verity_settings.designator >= 0 && arg_verity_settings.designator != d) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --root-hash=/--root-hash-sig= and --usr-hash=/--usr-hash-sig= options."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Cannot combine --root-hash=/--root-hash-sig= and --usr-hash=/--usr-hash-sig= options."); - if ((value = startswith(optarg, "base64:"))) { + if ((value = startswith(arg, "base64:"))) { r = unbase64mem(value, &sig.iov_base, &sig.iov_len); if (r < 0) - return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg); + return log_error_errno(r, "Failed to parse root hash signature '%s': %m", arg); } else { - r = read_full_file(optarg, (char**) &sig.iov_base, &sig.iov_len); + r = read_full_file(arg, (char**) &sig.iov_base, &sig.iov_len); if (r < 0) - return log_error_errno(r, "Failed to read root hash signature file '%s': %m", optarg); + return log_error_errno(r, "Failed to read root hash signature file '%s': %m", arg); } iovec_done(&arg_verity_settings.root_hash_sig); @@ -526,142 +339,195 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_VERITY_DATA: - r = parse_path_argument(optarg, false, &arg_verity_settings.data_path); + OPTION_LONG("verity-data", "PATH", + "Specify data file with hash tree for verity if it is not embedded in IMAGE"): + r = parse_path_argument(arg, false, &arg_verity_settings.data_path); if (r < 0) return r; break; - case ARG_FSCK: - r = parse_boolean(optarg); + OPTION_LONG("fsck", "BOOL", "Run fsck before mounting"): + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --fsck= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse --fsck= parameter: %s", arg); SET_FLAG(arg_flags, DISSECT_IMAGE_FSCK, r); break; - case ARG_GROWFS: - r = parse_boolean(optarg); + OPTION_LONG("growfs", "BOOL", "Grow file system to partition size, if marked"): + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --growfs= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse --growfs= parameter: %s", arg); SET_FLAG(arg_flags, DISSECT_IMAGE_GROWFS, r); break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case ARG_DISCOVER: - arg_action = ACTION_DISCOVER; - break; - - case ARG_LOOP_REF: - if (isempty(optarg)) { + OPTION_LONG("loop-ref", "NAME", "Set reference string for loopback device"): + if (isempty(arg)) { arg_loop_ref = mfree(arg_loop_ref); arg_loop_ref_auto = false; break; } - if (strlen(optarg) >= sizeof_field(struct loop_info64, lo_file_name)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Loop device ref string '%s' is too long.", optarg); + if (strlen(arg) >= sizeof_field(struct loop_info64, lo_file_name)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Loop device ref string '%s' is too long.", arg); - r = free_and_strdup_warn(&arg_loop_ref, optarg); + r = free_and_strdup_warn(&arg_loop_ref, arg); if (r < 0) return r; arg_loop_ref_auto = false; break; - case ARG_LOOP_REF_AUTO: + OPTION_LONG("loop-ref-auto", NULL, "Derive reference string from image file name"): arg_loop_ref = mfree(arg_loop_ref); arg_loop_ref_auto = true; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image-policy", "POLICY", "Specify image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_VALIDATE: - arg_action = ACTION_VALIDATE; - break; - - case ARG_MTREE_HASH: - r = parse_boolean_argument("--mtree-hash=", optarg, &arg_mtree_hash); + OPTION_LONG("mtree-hash", "BOOL", "Whether to include SHA256 hash in the mtree output"): + r = parse_boolean_argument("--mtree-hash=", arg, &arg_mtree_hash); if (r < 0) return r; break; - case ARG_MAKE_ARCHIVE: - r = dlopen_libarchive(); - if (r < 0) - return log_error_errno(r, "Archive support not available (compiled without libarchive, or libarchive not installed?)."); - - arg_action = ACTION_MAKE_ARCHIVE; - break; - - case ARG_SHIFT: - arg_action = ACTION_SHIFT; - break; - - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Discover system images"): system_scope_requested = true; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Discover user images"): user_scope_requested = true; break; - case ARG_ALL: + OPTION_LONG("all", NULL, "Show hidden images too"): arg_all = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress output of chosen loopback block device"): arg_quiet = true; break; - case ARG_IMAGE_FILTER: { + OPTION_LONG("image-filter", "FILTER", "Specify image dissection filter"): { _cleanup_(image_filter_freep) ImageFilter *f = NULL; - r = image_filter_parse(optarg, &f); + r = image_filter_parse(arg, &f); if (r < 0) - return log_error_errno(r, "Failed to parse image filter expression: %s", optarg); + return log_error_errno(r, "Failed to parse image filter expression: %s", arg); image_filter_free(arg_image_filter); arg_image_filter = TAKE_PTR(f); break; } - case ARG_COPY_OWNERSHIP: - r = parse_tristate_argument_with_auto("--copy-ownership=", optarg, &arg_copy_ownership); + OPTION_LONG("copy-ownership", "BOOL", "Whether to copy ownership when copying files"): + r = parse_tristate_argument_with_auto("--copy-ownership=", arg, &arg_copy_ownership); if (r < 0) return r; break; - case '?': - return -EINVAL; + /************************************ Commands ***************************************/ + OPTION_GROUP("Commands"): {} - default: - assert_not_reached(); + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('m', "mount", NULL, "Mount the image to the specified directory"): + arg_action = ACTION_MOUNT; + break; + + OPTION_SHORT('M', NULL, "Shortcut for --mount --mkdir"): + arg_action = ACTION_MOUNT; + arg_flags |= DISSECT_IMAGE_MKDIR; + break; + + OPTION('u', "umount", NULL, "Unmount the image from the specified directory"): + arg_action = ACTION_UMOUNT; + break; + + OPTION_SHORT('U', NULL, "Shortcut for --umount --rmdir"): + arg_action = ACTION_UMOUNT; + arg_rmdir = true; + break; + + OPTION_LONG("attach", NULL, "Attach the disk image to a loopback block device"): + arg_action = ACTION_ATTACH; + break; + + OPTION_LONG("detach", NULL, "Detach a loopback block device again"): + arg_action = ACTION_DETACH; + break; + + OPTION('l', "list", NULL, "List all the files and directories of the specified OS image"): + arg_action = ACTION_LIST; + arg_flags |= DISSECT_IMAGE_READ_ONLY; + break; + + OPTION_LONG("mtree", NULL, "Show BSD mtree manifest of OS image"): + arg_action = ACTION_MTREE; + arg_flags |= DISSECT_IMAGE_READ_ONLY; + break; + + OPTION_FULL(OPTION_STOPS_PARSING, /* sc= */ 0, "with", NULL, "Mount, run command, unmount"): + arg_action = ACTION_WITH; + break; + + OPTION('x', "copy-from", NULL, "Copy files from image to host"): + arg_action = ACTION_COPY_FROM; + arg_flags |= DISSECT_IMAGE_READ_ONLY; + break; + + OPTION('a', "copy-to", NULL, "Copy files from host to image"): + arg_action = ACTION_COPY_TO; + break; + + OPTION_LONG("make-archive", NULL, "Convert the DDI to an archive file"): + r = dlopen_libarchive(LOG_ERR); + if (r < 0) + return r; + + arg_action = ACTION_MAKE_ARCHIVE; + break; + + OPTION_LONG("discover", NULL, "Discover DDIs in well known directories"): + arg_action = ACTION_DISCOVER; + break; + + OPTION_LONG("validate", NULL, "Validate image and image policy"): + arg_action = ACTION_VALIDATE; + break; + + OPTION_LONG("shift", NULL, "Shift UID range to selected base"): + arg_action = ACTION_SHIFT; + break; } - } if (system_scope_requested || user_scope_requested) arg_runtime_scope = system_scope_requested && user_scope_requested ? _RUNTIME_SCOPE_INVALID : system_scope_requested ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER; + char **args = option_parser_get_args(&state); + switch (arg_action) { case ACTION_DISSECT: - if (optind + 1 != argc) + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path as only argument."); - r = parse_image_path_argument(argv[optind], NULL, &arg_image); + r = parse_image_path_argument(args[0], NULL, &arg_image); if (r < 0) return r; @@ -670,15 +536,15 @@ static int parse_argv(int argc, char *argv[]) { break; case ACTION_MOUNT: - if (optind + 2 != argc) + if (strv_length(args) != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path and mount point path as only arguments."); - r = parse_image_path_argument(argv[optind], NULL, &arg_image); + r = parse_image_path_argument(args[0], NULL, &arg_image); if (r < 0) return r; - r = parse_path_argument(argv[optind+1], /* suppress_root= */ false, &arg_path); + r = parse_path_argument(args[1], /* suppress_root= */ false, &arg_path); if (r < 0) return r; @@ -686,42 +552,42 @@ static int parse_argv(int argc, char *argv[]) { break; case ACTION_UMOUNT: - if (optind + 1 != argc) + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a mount point path as only argument."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_path); + r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_path); if (r < 0) return r; break; case ACTION_ATTACH: - if (optind + 1 != argc) + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path as only argument."); - r = parse_image_path_argument(argv[optind], NULL, &arg_image); + r = parse_image_path_argument(args[0], NULL, &arg_image); if (r < 0) return r; break; case ACTION_DETACH: - if (optind + 1 != argc) + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path or loopback device as only argument."); - r = parse_image_path_argument(argv[optind], NULL, &arg_image); + r = parse_image_path_argument(args[0], NULL, &arg_image); if (r < 0) return r; break; case ACTION_LIST: case ACTION_MTREE: - if (optind + 1 != argc) + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file or directory path as only argument."); - r = parse_image_path_argument(argv[optind], &arg_root, &arg_image); + r = parse_image_path_argument(args[0], &arg_root, &arg_image); if (r < 0) return r; @@ -729,63 +595,63 @@ static int parse_argv(int argc, char *argv[]) { break; case ACTION_MAKE_ARCHIVE: - if (argc < optind + 1 || argc > optind + 2) + if (!IN_SET(strv_length(args), 1, 2)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file, and an optional target path as only arguments."); - r = parse_image_path_argument(argv[optind], &arg_root, &arg_image); + r = parse_image_path_argument(args[0], &arg_root, &arg_image); if (r < 0) return r; - arg_target = argc > optind + 1 ? empty_or_dash_to_null(argv[optind + 1]) : NULL; + arg_target = empty_or_dash_to_null(args[1]); arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT; break; case ACTION_COPY_FROM: - if (argc < optind + 2 || argc > optind + 3) + if (!IN_SET(strv_length(args), 2, 3)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file or directory path, a source path and an optional destination path as only arguments."); - r = parse_image_path_argument(argv[optind], &arg_root, &arg_image); + r = parse_image_path_argument(args[0], &arg_root, &arg_image); if (r < 0) return r; - arg_source = argv[optind + 1]; - arg_target = argc > optind + 2 ? argv[optind + 2] : "-" /* this means stdout */ ; + arg_source = args[1]; + arg_target = args[2] ?: "-"; /* this means stdout */ ; arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT; break; case ACTION_COPY_TO: - if (argc < optind + 2 || argc > optind + 3) + if (!IN_SET(strv_length(args), 2, 3)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file or directory path, an optional source path and a destination path as only arguments."); - r = parse_image_path_argument(argv[optind], &arg_root, &arg_image); + r = parse_image_path_argument(args[0], &arg_root, &arg_image); if (r < 0) return r; - if (argc > optind + 2) { - arg_source = argv[optind + 1]; - arg_target = argv[optind + 2]; + if (args[2]) { + arg_source = args[1]; + arg_target = args[2]; } else { arg_source = "-"; /* this means stdin */ - arg_target = argv[optind + 1]; + arg_target = args[1]; } arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT; break; case ACTION_WITH: - if (optind >= argc) + if (strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path and an optional command line."); - r = parse_image_path_argument(argv[optind], NULL, &arg_image); + r = parse_image_path_argument(args[0], NULL, &arg_image); if (r < 0) return r; - if (argc > optind + 1) { - arg_argv = strv_copy(argv + optind + 1); + if (args[1]) { + arg_argv = strv_copy(args + 1); if (!arg_argv) return log_oom(); } @@ -793,17 +659,17 @@ static int parse_argv(int argc, char *argv[]) { break; case ACTION_DISCOVER: - if (optind != argc) + if (!strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument."); break; case ACTION_VALIDATE: - if (optind + 1 != argc) + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path as only argument."); - r = parse_image_path_argument(argv[optind], NULL, &arg_image); + r = parse_image_path_argument(args[0], NULL, &arg_image); if (r < 0) return r; @@ -812,27 +678,29 @@ static int parse_argv(int argc, char *argv[]) { break; case ACTION_SHIFT: - if (optind + 2 != argc) + if (strv_length(args) != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image path and a UID base as only argument."); - r = parse_image_path_argument(argv[optind], &arg_root, &arg_image); + r = parse_image_path_argument(args[0], &arg_root, &arg_image); if (r < 0) return r; - if (streq(argv[optind + 1], "foreign")) + if (streq(args[1], "foreign")) arg_uid_base = FOREIGN_UID_BASE; else { - r = parse_uid(argv[optind + 1], &arg_uid_base); + r = parse_uid(args[1], &arg_uid_base); if (r < 0) - return log_error_errno(r, "Failed to parse UID base: %s", argv[optind + 1]); + return log_error_errno(r, "Failed to parse UID base: %s", args[1]); if ((arg_uid_base & 0xFFFF) != 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected UID base not a multiple of 64K: " UID_FMT, arg_uid_base); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Selected UID base not a multiple of 64K: " UID_FMT, arg_uid_base); if (arg_uid_base != 0 && !uid_is_container(arg_uid_base) && !uid_is_foreign(arg_uid_base)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected UID range is not in the container range, nor the foreign one, refusing."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Selected UID range is not in the container range, nor the foreign one, refusing."); } arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT; @@ -1237,9 +1105,9 @@ static int action_dissect( if (!sd_json_format_enabled(arg_json_format_flags)) { table_set_header(t, arg_legend); - r = table_print(t, NULL); + r = table_print_or_warn(t); if (r < 0) - return table_log_print_error(r); + return r; } else { _cleanup_(sd_json_variant_unrefp) sd_json_variant *jt = NULL; @@ -2170,7 +2038,7 @@ static int run(int argc, char *argv[]) { uint32_t loop_flags; int open_flags; - open_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR; + open_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : -1; loop_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN; if (arg_in_memory) @@ -2184,7 +2052,7 @@ static int run(int argc, char *argv[]) { log_debug_errno(r, "Lacking permissions or missing /dev/loop-control to set up loopback block device for %s, using service: %m", arg_image); arg_via_service = true; } else { - if (arg_loop_ref) { + if (arg_loop_ref && !LOOP_DEVICE_IS_FOREIGN(d)) { r = loop_device_set_filename(d, arg_loop_ref); if (r < 0) log_warning_errno(r, "Failed to set loop reference string to '%s', ignoring: %m", arg_loop_ref); diff --git a/src/escape/escape-tool.c b/src/escape/escape-tool.c index 621182897d8f8..aa4129cdeed98 100644 --- a/src/escape/escape-tool.c +++ b/src/escape/escape-tool.c @@ -1,12 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" #include "build.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "path-util.h" #include "pretty-print.h" #include "string-util.h" @@ -26,107 +27,83 @@ static bool arg_instance = false; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-escape", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [NAME...]\n\n" - "%3$sEscape strings for usage in systemd unit names.%4$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --suffix=SUFFIX Unit suffix to append to escaped strings\n" - " --template=TEMPLATE Insert strings as instance into template\n" - " --instance With --unescape, show just the instance part\n" - " -u --unescape Unescape strings\n" - " -m --mangle Mangle strings\n" - " -p --path When escaping/unescaping assume the string is a path\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] [NAME...]\n\n" + "%sEscape strings for usage in systemd unit names.%s\n\n", program_invocation_short_name, - link, ansi_highlight(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_SUFFIX, - ARG_TEMPLATE - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "suffix", required_argument, NULL, ARG_SUFFIX }, - { "template", required_argument, NULL, ARG_TEMPLATE }, - { "unescape", no_argument, NULL, 'u' }, - { "mangle", no_argument, NULL, 'm' }, - { "path", no_argument, NULL, 'p' }, - { "instance", no_argument, NULL, 'i' }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hump", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_SUFFIX: { - UnitType t = unit_type_from_string(optarg); + OPTION_LONG("suffix", "SUFFIX", "Unit suffix to append to escaped strings"): { + UnitType t = unit_type_from_string(arg); if (t < 0) - return log_error_errno(t, "Invalid unit suffix type \"%s\".", optarg); + return log_error_errno(t, "Invalid unit suffix type \"%s\".", arg); - arg_suffix = optarg; + arg_suffix = arg; break; } - case ARG_TEMPLATE: - if (!unit_name_is_valid(optarg, UNIT_NAME_TEMPLATE)) + OPTION_LONG("template", "TEMPLATE", "Insert strings as instance into template"): + if (!unit_name_is_valid(arg, UNIT_NAME_TEMPLATE)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Template name %s is not valid.", optarg); + "Template name %s is not valid.", arg); - arg_template = optarg; + arg_template = arg; break; - case 'u': + OPTION_LONG("instance", NULL, "With --unescape, show just the instance part"): + arg_instance = true; + break; + + OPTION('u', "unescape", NULL, "Unescape strings"): arg_action = ACTION_UNESCAPE; break; - case 'm': + OPTION('m', "mangle", NULL, "Mangle strings"): arg_action = ACTION_MANGLE; break; - case 'p': + OPTION('p', "path", NULL, + "When escaping/unescaping assume the string is a path"): arg_path = true; break; - - case 'i': - arg_instance = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind >= argc) + if (option_parser_get_n_args(&state) == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not enough arguments."); @@ -154,6 +131,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--instance may not be combined with --template."); + *ret_args = option_parser_get_args(&state); return 1; } @@ -162,11 +140,12 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - STRV_FOREACH(i, argv + optind) { + STRV_FOREACH(i, args) { _cleanup_free_ char *e = NULL; switch (arg_action) { @@ -267,7 +246,7 @@ static int run(int argc, char *argv[]) { break; } - if (i != argv + optind) + if (i != args) fputc(' ', stdout); fputs(e, stdout); diff --git a/src/factory-reset/factory-reset-tool.c b/src/factory-reset/factory-reset-tool.c index 2f0fe97ca6f01..ec3f7e43c492b 100644 --- a/src/factory-reset/factory-reset-tool.c +++ b/src/factory-reset/factory-reset-tool.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-json.h" #include "sd-varlink.h" @@ -12,9 +10,11 @@ #include "efivars.h" #include "errno-util.h" #include "factory-reset.h" +#include "format-table.h" #include "fs-util.h" #include "json-util.h" #include "main-func.h" +#include "options.h" #include "os-util.h" #include "pretty-print.h" #include "udev-util.h" @@ -28,76 +28,66 @@ static bool arg_varlink = false; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-factory-reset", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND\n" - "\n%5$sQuery, request, cancel factory reset operation.%6$s\n" - "\n%3$sCommands:%4$s\n" - " status Report current factory reset status\n" - " request Request a factory reset on next boot\n" - " cancel Cancel a prior factory reset request for next boot\n" - " complete Mark a factory reset as complete\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --retrigger Retrigger block devices\n" - " -q --quiet Suppress output\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options, verbs); + + printf("%s [OPTIONS...] COMMAND\n" + "\n%sQuery, request, cancel factory reset operation.%s\n" + "\nCommands:\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\nOptions:\n"); + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_RETRIGGER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "retrigger", no_argument, NULL, ARG_RETRIGGER }, - { "quiet", no_argument, NULL, 'q' }, - {} - }; - - int r, c; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hq", options, NULL)) >= 0) - switch (c) { + OptionParser state = { argc, argv }; + const char *arg; - case 'h': + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + switch (c) { + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_RETRIGGER: + OPTION_LONG("retrigger", NULL, "Retrigger block devices"): arg_retrigger = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress output"): arg_quiet = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); @@ -106,10 +96,12 @@ static int parse_argv(int argc, char *argv[]) { if (r > 0) arg_varlink = true; + *ret_args = option_parser_get_args(&state); return 1; } -static int verb_status(int argc, char *argv[], void *userdata) { +VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, "Report current factory reset status"); +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { static const int exit_status_table[_FACTORY_RESET_MODE_MAX] = { /* Report current mode also as via exit status, but only return a subset of states */ [FACTORY_RESET_UNSUPPORTED] = EXIT_SUCCESS, @@ -130,7 +122,8 @@ static int verb_status(int argc, char *argv[], void *userdata) { return exit_status_table[f]; } -static int verb_request(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_request, "request", "Request a factory reset on next boot"); +static int verb_request(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; FactoryResetMode f = factory_reset_mode(); @@ -197,7 +190,8 @@ static int verb_request(int argc, char *argv[], void *userdata) { return 0; } -static int verb_cancel(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_cancel, "cancel", "Cancel a prior factory reset request for next boot"); +static int verb_cancel(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; FactoryResetMode f = factory_reset_mode(); @@ -269,7 +263,8 @@ static int retrigger_block_devices(void) { return 0; } -static int verb_complete(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_complete, "complete", "Mark a factory reset as complete"); +static int verb_complete(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; FactoryResetMode f = factory_reset_mode(); @@ -359,26 +354,19 @@ static int varlink_service(void) { } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "request", VERB_ANY, 1, 0, verb_request }, - { "cancel", VERB_ANY, 1, 0, verb_cancel }, - { "complete", VERB_ANY, 1, 0, verb_complete }, - {} - }; - int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; if (arg_varlink) return varlink_service(); - return dispatch_verb(argc, argv, verbs, /* userdata= */ NULL); + return dispatch_verb_with_args(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 38e3adaed6eca..3006d93407255 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "sd-bus.h" @@ -24,6 +23,7 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "glyph-util.h" #include "hostname-util.h" @@ -38,9 +38,9 @@ #include "main-func.h" #include "memory-util.h" #include "mount-util.h" +#include "options.h" #include "os-util.h" #include "parse-argument.h" -#include "parse-util.h" #include "password-quality-util.h" #include "path-util.h" #include "plymouth-util.h" @@ -339,8 +339,8 @@ static int process_locale(int rfd, sd_varlink **mute_console_link) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, etc_locale_conf(), - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, etc_locale_conf(), + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/locale.conf: %m"); @@ -412,11 +412,15 @@ static int prompt_keymap(int rfd, sd_varlink **mute_console_link) { if (arg_keymap) return 0; - r = read_credential("firstboot.keymap", (void**) &arg_keymap, NULL); + _cleanup_free_ char *km = NULL; + r = read_credential("firstboot.keymap", (void**) &km, NULL); if (r < 0) log_debug_errno(r, "Failed to read credential firstboot.keymap, ignoring: %m"); + else if (!keymap_is_valid(km)) + log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Keymap '%s' supplied via credential is not valid, ignoring.", km); else { log_debug("Acquired keymap from credential."); + arg_keymap = TAKE_PTR(km); return 0; } @@ -470,8 +474,8 @@ static int process_keymap(int rfd, sd_varlink **mute_console_link) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, etc_vconsole_conf(), - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, etc_vconsole_conf(), + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/vconsole.conf: %m"); @@ -540,11 +544,15 @@ static int prompt_timezone(int rfd, sd_varlink **mute_console_link) { if (arg_timezone) return 0; - r = read_credential("firstboot.timezone", (void**) &arg_timezone, NULL); + _cleanup_free_ char *tz = NULL; + r = read_credential("firstboot.timezone", (void**) &tz, NULL); if (r < 0) log_debug_errno(r, "Failed to read credential firstboot.timezone, ignoring: %m"); + else if (!timezone_is_valid(tz, LOG_DEBUG)) + log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Timezone '%s' supplied via credential is not valid, ignoring.", tz); else { log_debug("Acquired timezone from credential."); + arg_timezone = TAKE_PTR(tz); return 0; } @@ -582,8 +590,8 @@ static int process_timezone(int rfd, sd_varlink **mute_console_link) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, etc_localtime(), - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, etc_localtime(), + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/localtime: %m"); @@ -647,6 +655,19 @@ static int prompt_hostname(int rfd, sd_varlink **mute_console_link) { if (arg_hostname) return 0; + _cleanup_free_ char *hn = NULL; + r = read_credential("firstboot.hostname", (void**) &hn, NULL); + if (r < 0) + log_debug_errno(r, "Failed to read credential firstboot.hostname, ignoring: %m"); + else if (!hostname_is_valid(hn, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK)) + log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Hostname '%s' supplied via credential is not valid, ignoring.", hn); + else { + log_debug("Acquired hostname from credentials."); + arg_hostname = TAKE_PTR(hn); + hostname_cleanup(arg_hostname); + return 0; + } + if (!arg_prompt_hostname) { log_debug("Prompting for hostname was not requested."); return 0; @@ -682,9 +703,7 @@ static int process_hostname(int rfd, sd_varlink **mute_console_link) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, etc_hostname(), - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN, - &f); + pfd = chase_and_open_parent_at(rfd, rfd, etc_hostname(), CHASE_MKDIR_0755|CHASE_WARN, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/hostname: %m"); @@ -717,8 +736,8 @@ static int process_machine_id(int rfd) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, "/etc/machine-id", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, "/etc/machine-id", + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/machine-id: %m"); @@ -827,7 +846,7 @@ static int find_shell(int rfd, const char *path) { if (!valid_shell(path)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s is not a valid shell", path); - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL); + r = chaseat(rfd, rfd, path, /* flags= */ 0, /* ret_path= */ NULL, /* ret_fd= */ NULL); if (r < 0) return log_error_errno(r, "Failed to resolve shell %s: %m", path); @@ -1031,8 +1050,8 @@ static int process_root_account(int rfd, sd_varlink **mute_console_link) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, "/etc/passwd", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, "/etc/passwd", + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, NULL); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/passwd: %m"); @@ -1148,8 +1167,8 @@ static int process_kernel_cmdline(int rfd) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, "/etc/kernel/cmdline", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, "/etc/kernel/cmdline", + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/kernel/cmdline: %m"); @@ -1181,7 +1200,7 @@ static int reset_one(int rfd, const char *path) { assert(rfd >= 0); assert(path); - pfd = chase_and_open_parent_at(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_WARN|CHASE_NOFOLLOW, &f); + pfd = chase_and_open_parent_at(rfd, rfd, path, CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd == -ENOENT) return 0; if (pfd < 0) @@ -1219,380 +1238,252 @@ static int process_reset(int rfd) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-firstboot", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...]\n" - "\n%3$sConfigures basic settings of the system.%4$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY Specify disk image dissection policy\n" - " --locale=LOCALE Set primary locale (LANG=)\n" - " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n" - " --keymap=KEYMAP Set keymap\n" - " --timezone=TIMEZONE Set timezone\n" - " --hostname=NAME Set hostname\n" - " --setup-machine-id Set a random machine ID\n" - " --machine-id=ID Set specified machine ID\n" - " --root-password=PASSWORD Set root password from plaintext password\n" - " --root-password-file=FILE Set root password from file\n" - " --root-password-hashed=HASH Set root password from hashed password\n" - " --root-shell=SHELL Set root shell\n" - " --kernel-command-line=CMDLINE\n" - " Set kernel command line\n" - " --prompt-locale Prompt the user for locale settings\n" - " --prompt-keymap Prompt the user for keymap settings\n" - " --prompt-keymap-auto Prompt the user for keymap settings if invoked\n" - " on local console\n" - " --prompt-timezone Prompt the user for timezone\n" - " --prompt-hostname Prompt the user for hostname\n" - " --prompt-root-password Prompt the user for root password\n" - " --prompt-root-shell Prompt the user for root shell\n" - " --prompt Prompt for all of the above\n" - " --copy-locale Copy locale from host\n" - " --copy-keymap Copy keymap from host\n" - " --copy-timezone Copy timezone from host\n" - " --copy-root-password Copy root password from host\n" - " --copy-root-shell Copy root shell from host\n" - " --copy Copy locale, keymap, timezone, root password\n" - " --force Overwrite existing files\n" - " --delete-root-password Delete root password\n" - " --welcome=no Disable the welcome text\n" - " --chrome=no Don't show color bar at top and bottom of\n" - " terminal\n" - " --mute-console=yes Tell kernel/PID 1 to not write to the console\n" - " while running\n" - " --reset Remove existing files\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n\n" + "%sConfigures basic settings of the system.%s\n\n", program_invocation_short_name, - link, ansi_highlight(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_LOCALE, - ARG_LOCALE_MESSAGES, - ARG_KEYMAP, - ARG_TIMEZONE, - ARG_HOSTNAME, - ARG_SETUP_MACHINE_ID, - ARG_MACHINE_ID, - ARG_ROOT_PASSWORD, - ARG_ROOT_PASSWORD_FILE, - ARG_ROOT_PASSWORD_HASHED, - ARG_ROOT_SHELL, - ARG_KERNEL_COMMAND_LINE, - ARG_PROMPT, - ARG_PROMPT_LOCALE, - ARG_PROMPT_KEYMAP, - ARG_PROMPT_KEYMAP_AUTO, - ARG_PROMPT_TIMEZONE, - ARG_PROMPT_HOSTNAME, - ARG_PROMPT_ROOT_PASSWORD, - ARG_PROMPT_ROOT_SHELL, - ARG_COPY, - ARG_COPY_LOCALE, - ARG_COPY_KEYMAP, - ARG_COPY_TIMEZONE, - ARG_COPY_ROOT_PASSWORD, - ARG_COPY_ROOT_SHELL, - ARG_FORCE, - ARG_DELETE_ROOT_PASSWORD, - ARG_WELCOME, - ARG_CHROME, - ARG_RESET, - ARG_MUTE_CONSOLE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "locale", required_argument, NULL, ARG_LOCALE }, - { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES }, - { "keymap", required_argument, NULL, ARG_KEYMAP }, - { "timezone", required_argument, NULL, ARG_TIMEZONE }, - { "hostname", required_argument, NULL, ARG_HOSTNAME }, - { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID }, - { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, - { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD }, - { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE }, - { "root-password-hashed", required_argument, NULL, ARG_ROOT_PASSWORD_HASHED }, - { "root-shell", required_argument, NULL, ARG_ROOT_SHELL }, - { "kernel-command-line", required_argument, NULL, ARG_KERNEL_COMMAND_LINE }, - { "prompt", no_argument, NULL, ARG_PROMPT }, - { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE }, - { "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP }, - { "prompt-keymap-auto", no_argument, NULL, ARG_PROMPT_KEYMAP_AUTO }, - { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE }, - { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME }, - { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD }, - { "prompt-root-shell", no_argument, NULL, ARG_PROMPT_ROOT_SHELL }, - { "copy", no_argument, NULL, ARG_COPY }, - { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE }, - { "copy-keymap", no_argument, NULL, ARG_COPY_KEYMAP }, - { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE }, - { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD }, - { "copy-root-shell", no_argument, NULL, ARG_COPY_ROOT_SHELL }, - { "force", no_argument, NULL, ARG_FORCE }, - { "delete-root-password", no_argument, NULL, ARG_DELETE_ROOT_PASSWORD }, - { "welcome", required_argument, NULL, ARG_WELCOME }, - { "chrome", required_argument, NULL, ARG_CHROME }, - { "reset", no_argument, NULL, ARG_RESET }, - { "mute-console", required_argument, NULL, ARG_MUTE_CONSOLE }, - {} - }; - - int r, c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ROOT: - r = parse_path_argument(optarg, true, &arg_root); + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(arg, true, &arg_root); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, false, &arg_image); + OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): + r = parse_path_argument(arg, false, &arg_image); if (r < 0) return r; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_LOCALE: - r = free_and_strdup(&arg_locale, optarg); + OPTION_LONG("locale", "LOCALE", "Set primary locale (LANG=)"): + r = free_and_strdup_warn(&arg_locale, arg); if (r < 0) - return log_oom(); - + return r; break; - case ARG_LOCALE_MESSAGES: - r = free_and_strdup(&arg_locale_messages, optarg); + OPTION_LONG("locale-messages", "LOCALE", "Set message locale (LC_MESSAGES=)"): + r = free_and_strdup_warn(&arg_locale_messages, arg); if (r < 0) - return log_oom(); - + return r; break; - case ARG_KEYMAP: - if (!keymap_is_valid(optarg)) + OPTION_LONG("keymap", "KEYMAP", "Set keymap"): + if (!keymap_is_valid(arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Keymap %s is not valid.", optarg); + "Keymap %s is not valid.", arg); - r = free_and_strdup(&arg_keymap, optarg); + r = free_and_strdup_warn(&arg_keymap, arg); if (r < 0) - return log_oom(); - + return r; break; - case ARG_TIMEZONE: - if (!timezone_is_valid(optarg, LOG_ERR)) + OPTION_LONG("timezone", "TIMEZONE", "Set timezone"): + if (!timezone_is_valid(arg, LOG_ERR)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Timezone %s is not valid.", optarg); + "Timezone %s is not valid.", arg); - r = free_and_strdup(&arg_timezone, optarg); + r = free_and_strdup_warn(&arg_timezone, arg); if (r < 0) - return log_oom(); - + return r; break; - case ARG_ROOT_PASSWORD: - r = free_and_strdup(&arg_root_password, optarg); + OPTION_LONG("hostname", "NAME", "Set hostname"): + if (!hostname_is_valid(arg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Host name %s is not valid.", arg); + + r = free_and_strdup_warn(&arg_hostname, arg); if (r < 0) - return log_oom(); + return r; - arg_root_password_is_hashed = false; + hostname_cleanup(arg_hostname); break; - case ARG_ROOT_PASSWORD_FILE: - arg_root_password = mfree(arg_root_password); - - r = read_one_line_file(optarg, &arg_root_password); + OPTION_LONG("setup-machine-id", NULL, "Set a random machine ID"): + r = sd_id128_randomize(&arg_machine_id); if (r < 0) - return log_error_errno(r, "Failed to read %s: %m", optarg); - - arg_root_password_is_hashed = false; + return log_error_errno(r, "Failed to generate randomized machine ID: %m"); break; - case ARG_ROOT_PASSWORD_HASHED: - r = free_and_strdup(&arg_root_password, optarg); + OPTION_LONG("machine-id", "ID", "Set specified machine ID"): + r = sd_id128_from_string(arg, &arg_machine_id); if (r < 0) - return log_oom(); - - arg_root_password_is_hashed = true; + return log_error_errno(r, "Failed to parse machine id %s.", arg); break; - case ARG_ROOT_SHELL: - r = free_and_strdup(&arg_root_shell, optarg); + OPTION_LONG("root-password", "PASSWORD", "Set root password from plaintext password"): + r = free_and_strdup_warn(&arg_root_password, arg); if (r < 0) - return log_oom(); + return r; + arg_root_password_is_hashed = false; break; - case ARG_HOSTNAME: - if (!hostname_is_valid(optarg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Host name %s is not valid.", optarg); + OPTION_LONG("root-password-file", "FILE", "Set root password from file"): + arg_root_password = mfree(arg_root_password); - r = free_and_strdup(&arg_hostname, optarg); + r = read_one_line_file(arg, &arg_root_password); if (r < 0) - return log_oom(); + return log_error_errno(r, "Failed to read %s: %m", arg); - hostname_cleanup(arg_hostname); + arg_root_password_is_hashed = false; break; - case ARG_SETUP_MACHINE_ID: - r = sd_id128_randomize(&arg_machine_id); + OPTION_LONG("root-password-hashed", "HASH", "Set root password from hashed password"): + r = free_and_strdup_warn(&arg_root_password, arg); if (r < 0) - return log_error_errno(r, "Failed to generate randomized machine ID: %m"); + return r; + arg_root_password_is_hashed = true; break; - case ARG_MACHINE_ID: - r = sd_id128_from_string(optarg, &arg_machine_id); + OPTION_LONG("root-shell", "SHELL", "Set root shell"): + r = free_and_strdup_warn(&arg_root_shell, arg); if (r < 0) - return log_error_errno(r, "Failed to parse machine id %s.", optarg); - + return r; break; - case ARG_KERNEL_COMMAND_LINE: - r = free_and_strdup(&arg_kernel_cmdline, optarg); + OPTION_LONG("kernel-command-line", "CMDLINE", "Set kernel command line"): + r = free_and_strdup_warn(&arg_kernel_cmdline, arg); if (r < 0) - return log_oom(); - - break; - - case ARG_PROMPT: - arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = - arg_prompt_root_password = arg_prompt_root_shell = true; - arg_prompt_keymap_auto = false; + return r; break; - case ARG_PROMPT_LOCALE: + OPTION_LONG("prompt-locale", NULL, "Prompt the user for locale settings"): arg_prompt_locale = true; break; - case ARG_PROMPT_KEYMAP: + OPTION_LONG("prompt-keymap", NULL, "Prompt the user for keymap settings"): arg_prompt_keymap = true; arg_prompt_keymap_auto = false; break; - case ARG_PROMPT_KEYMAP_AUTO: + OPTION_LONG("prompt-keymap-auto", NULL, + "Prompt the user for keymap settings if invoked on local console"): arg_prompt_keymap_auto = true; break; - case ARG_PROMPT_TIMEZONE: + OPTION_LONG("prompt-timezone", NULL, "Prompt the user for timezone"): arg_prompt_timezone = true; break; - case ARG_PROMPT_HOSTNAME: + OPTION_LONG("prompt-hostname", NULL, "Prompt the user for hostname"): arg_prompt_hostname = true; break; - case ARG_PROMPT_ROOT_PASSWORD: + OPTION_LONG("prompt-root-password", NULL, "Prompt the user for root password"): arg_prompt_root_password = true; break; - case ARG_PROMPT_ROOT_SHELL: + OPTION_LONG("prompt-root-shell", NULL, "Prompt the user for root shell"): arg_prompt_root_shell = true; break; - case ARG_COPY: - arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = - arg_copy_root_shell = true; + OPTION_LONG("prompt", NULL, "Prompt for all of the above"): + arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = + arg_prompt_root_password = arg_prompt_root_shell = true; + arg_prompt_keymap_auto = false; break; - case ARG_COPY_LOCALE: + OPTION_LONG("copy-locale", NULL, "Copy locale from host"): arg_copy_locale = true; break; - case ARG_COPY_KEYMAP: + OPTION_LONG("copy-keymap", NULL, "Copy keymap from host"): arg_copy_keymap = true; break; - case ARG_COPY_TIMEZONE: + OPTION_LONG("copy-timezone", NULL, "Copy timezone from host"): arg_copy_timezone = true; break; - case ARG_COPY_ROOT_PASSWORD: + OPTION_LONG("copy-root-password", NULL, "Copy root password from host"): arg_copy_root_password = true; break; - case ARG_COPY_ROOT_SHELL: + OPTION_LONG("copy-root-shell", NULL, "Copy root shell from host"): arg_copy_root_shell = true; break; - case ARG_FORCE: + OPTION_LONG("copy", NULL, "Copy all of the above"): + arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = + arg_copy_root_shell = true; + break; + + OPTION_LONG("force", NULL, "Overwrite existing files"): arg_force = true; break; - case ARG_DELETE_ROOT_PASSWORD: + OPTION_LONG("delete-root-password", NULL, "Delete root password"): arg_delete_root_password = true; break; - case ARG_WELCOME: - r = parse_boolean(optarg); + OPTION_LONG("welcome", "BOOL", "Whether to show the welcome text"): + r = parse_boolean_argument("--welcome=", arg, &arg_welcome); if (r < 0) - return log_error_errno(r, "Failed to parse --welcome= argument: %s", optarg); - - arg_welcome = r; + return r; break; - case ARG_CHROME: - r = parse_boolean_argument("--chrome=", optarg, &arg_chrome); + OPTION_LONG("chrome", "BOOL", + "Whether to show a color bar at top and bottom of terminal"): + r = parse_boolean_argument("--chrome=", arg, &arg_chrome); if (r < 0) return r; - break; - case ARG_RESET: - arg_reset = true; - break; - - case ARG_MUTE_CONSOLE: - r = parse_boolean_argument("--mute-console=", optarg, &arg_mute_console); + OPTION_LONG("mute-console", "BOOL", + "Whether to disallow kernel/PID 1 writes to the console while running"): + r = parse_boolean_argument("--mute-console=", arg, &arg_mute_console); if (r < 0) return r; - break; - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION_LONG("reset", NULL, "Remove existing files"): + arg_reset = true; + break; } if (arg_delete_root_password && (arg_copy_root_password || arg_root_password || arg_prompt_root_password)) diff --git a/src/fsck/fsck.c b/src/fsck/fsck.c index 9767568724faf..405e9c34fddc4 100644 --- a/src/fsck/fsck.c +++ b/src/fsck/fsck.c @@ -28,6 +28,7 @@ #include "process-util.h" #include "socket-util.h" #include "special.h" +#include "stat-util.h" #include "stdio-util.h" #include "string-table.h" #include "string-util.h" @@ -218,6 +219,7 @@ static int process_progress(int fd, FILE* console) { /* Only update once every 50ms */ t = now(CLOCK_MONOTONIC); + assert_cc(50 * USEC_PER_MSEC <= USEC_INFINITY); if (last + 50 * USEC_PER_MSEC > t) continue; @@ -298,10 +300,9 @@ static int run(int argc, char *argv[]) { if (stat(device, &st) < 0) return log_error_errno(errno, "Failed to stat %s: %m", device); - if (!S_ISBLK(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s is not a block device.", - device); + r = stat_verify_block(&st); + if (r < 0) + return log_error_errno(r, "'%s' is not a block device.", device); r = sd_device_new_from_stat_rdev(&dev, &st); if (r < 0) diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index d60db7e9c1de1..572f30a1cf7f4 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -221,6 +221,7 @@ static int add_swap( _cleanup_free_ char *name = NULL; _cleanup_fclose_ FILE *f = NULL; + bool is_network; int r; assert(what); @@ -240,11 +241,14 @@ static int add_swap( return true; } - log_debug("Found swap entry what=%s makefs=%s growfs=%s pcrfs=%s validatefs=%s noauto=%s nofail=%s", + is_network = fstab_test_option(options, "_netdev\0"); + + log_debug("Found swap entry what=%s makefs=%s growfs=%s pcrfs=%s validatefs=%s noauto=%s nofail=%s netdev=%s", what, yes_no(flags & MOUNT_MAKEFS), yes_no(flags & MOUNT_GROWFS), yes_no(flags & MOUNT_PCRFS), yes_no(flags & MOUNT_VALIDATEFS), - yes_no(flags & MOUNT_NOAUTO), yes_no(flags & MOUNT_NOFAIL)); + yes_no(flags & MOUNT_NOAUTO), yes_no(flags & MOUNT_NOFAIL), + yes_no(is_network)); r = unit_name_from_path(what, ".swap", &name); if (r < 0) @@ -285,6 +289,12 @@ static int add_swap( if (r < 0) return r; + if (is_network) { + r = generator_write_network_device_deps(arg_dest, what, /* where= */ NULL, options); + if (r < 0) + return r; + } + if (flags & MOUNT_MAKEFS) { r = generator_hook_up_mkswap(arg_dest, what); if (r < 0) @@ -300,7 +310,8 @@ static int add_swap( log_warning("%s: validating swap devices is currently unsupported.", what); if (!(flags & MOUNT_NOAUTO)) { - r = generator_add_symlink(arg_dest, SPECIAL_SWAP_TARGET, + const char *target = is_network ? SPECIAL_REMOTE_FS_TARGET : SPECIAL_SWAP_TARGET; + r = generator_add_symlink(arg_dest, target, (flags & MOUNT_NOFAIL) ? "wants" : "requires", name); if (r < 0) return r; @@ -672,9 +683,9 @@ static int add_mount( } if (flags & MOUNT_PCRFS) { - r = efi_measured_uki(LOG_WARNING); + r = efi_measured_os(LOG_WARNING); if (r == 0) - log_debug("Kernel stub did not measure kernel image into PCR, skipping userspace measurement, too."); + log_debug("OS measurements not explicitly requested and kernel stub did not measure kernel image into PCR, skipping userspace measurement, too."); else if (r > 0) { r = generator_hook_up_pcrfs(dest, where, target_unit); if (r < 0) diff --git a/src/fundamental/assert-fundamental.h b/src/fundamental/assert-fundamental.h index 3168e5699aa93..d3425cb54df0c 100644 --- a/src/fundamental/assert-fundamental.h +++ b/src/fundamental/assert-fundamental.h @@ -100,3 +100,16 @@ static inline int __coverity_check_and_return__(int condition) { assert_se(_expr_ >= _zero); \ _expr_; \ }) + +/* Mark a pointer parameter as intentionally nullable. This is a no-op at runtime but suppresses + * the coccinelle check-pointer-deref warning for parameters that are safely handled before any + * dereference (e.g. passed to a NULL-safe helper like iovec_is_set()). */ +#define POINTER_MAY_BE_NULL(ptr) ({ (void) (ptr); }) + +/* sizeof() does not evaluate its argument - it is a compile-time constant expression - so *ptr + * inside sizeof() is not a real dereference. However, coccinelle cannot distinguish this from an + * actual dereference, and when sizeof(*ptr) appears in a variable initializer the assert(ptr) that + * follows cannot come first (declarations must precede statements). Use this macro in place + * of sizeof() to avoid the false positive - coccinelle sees SIZEOF() as a function call (via + * parsing_hacks.h) and never looks inside the argument. */ +#define SIZEOF(x) sizeof(x) diff --git a/src/fundamental/cleanup-fundamental.h b/src/fundamental/cleanup-fundamental.h index 86b9851fd54ab..9094cff2331e0 100644 --- a/src/fundamental/cleanup-fundamental.h +++ b/src/fundamental/cleanup-fundamental.h @@ -45,6 +45,35 @@ #define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(type, macro, empty) \ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO_RENAME(type, macro, macro##p, empty) +/* Clean up a NULL-terminated array by dropping all the items in it (up to the first NULL). + * The array itself is not deallocated. */ +#define DEFINE_ARRAY_DONE_FUNC(type, helper) \ + void helper ## _many(type (*p)[]) { \ + for (type *t = *ASSERT_PTR(p); *t; t++) \ + *t = helper(*t); \ + } + +/* Clean up an array of pointers to objects by dropping all the items in it. + * The size of the array is passed in as a parameter, so NULL items may appear in the middle of the array. + * Free the array itself afterwards. */ +#define DEFINE_POINTER_ARRAY_FREE_FUNC(type, helper) \ + void helper ## _array(type *array, size_t n) { \ + assert(array || n == 0); \ + FOREACH_ARRAY(item, array, n) \ + helper(*item); \ + free(array); \ + } + +/* Clean up an array of objects of known size by dropping all the items in it. + * Then free the array itself. */ +#define DEFINE_ARRAY_FREE_FUNC(name, type, helper) \ + void name(type *array, size_t n) { \ + assert(array || n == 0); \ + FOREACH_ARRAY(item, array, n) \ + helper(item); \ + free(array); \ + } + typedef void (*free_array_func_t)(void *p, size_t n); /* An automatic _cleanup_-like logic for destroy arrays (i.e. pointers + size) when leaving scope */ diff --git a/src/fundamental/iovec-util-fundamental.h b/src/fundamental/iovec-util-fundamental.h index 707274f86e53f..5b693742f3a7c 100644 --- a/src/fundamental/iovec-util-fundamental.h +++ b/src/fundamental/iovec-util-fundamental.h @@ -23,6 +23,25 @@ struct iovec { .iov_len = (len), \ } +static inline struct iovec* iovec_shift(const struct iovec *iovec, size_t shift, struct iovec *ret) { + assert(iovec); + assert(ret); + + /* This returns an empty iovec when 'shift' is larger or equals to the input iovec length. + * The 'iovec' and 'ret' can point to the same object. */ + + *ret = IOVEC_MAKE(iovec->iov_len > shift ? (uint8_t*) iovec->iov_base + shift : NULL, + LESS_BY(iovec->iov_len, shift)); + return ret; +} + +#define IOVEC_SHIFT(iov, shift) \ + *iovec_shift(iov, shift, &(struct iovec){}) + +static inline struct iovec* iovec_inc(struct iovec *iovec, size_t shift) { + return iovec_shift(iovec, shift, iovec); +} + static inline void iovec_done(struct iovec *iovec) { /* A _cleanup_() helper that frees the iov_base in the iovec */ assert(iovec); diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index e8757b1fc37a4..6ed6cf2f8a0a1 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -88,7 +88,6 @@ #define _printf_(a, b) __attribute__((__format__(printf, a, b))) #define _public_ __attribute__((__visibility__("default"))) #define _pure_ __attribute__((__pure__)) -#define _retain_ __attribute__((__retain__)) #define _returns_nonnull_ __attribute__((__returns_nonnull__)) #define _section_(x) __attribute__((__section__(x))) #define _sentinel_ __attribute__((__sentinel__)) @@ -99,16 +98,28 @@ #define _weak_ __attribute__((__weak__)) #define _weakref_(x) __attribute__((__weakref__(#x))) -#ifdef __clang__ -# define _alloc_(...) -#else +#if HAVE_ATTRIBUTE_ALLOC_SIZE # define _alloc_(...) __attribute__((__alloc_size__(__VA_ARGS__))) +#else +# define _alloc_(...) #endif -#if defined(__clang__) && __clang_major__ < 10 +#if HAVE_ATTRIBUTE_FALLTHROUGH +# define _fallthrough_ __attribute__((__fallthrough__)) +#else # define _fallthrough_ +#endif + +#if HAVE_ATTRIBUTE_RETAIN +# define _retain_ __attribute__((__retain__)) #else -# define _fallthrough_ __attribute__((__fallthrough__)) +# define _retain_ +#endif + +#if HAVE_ATTRIBUTE_NO_REORDER +# define _no_reorder_ __attribute__((__no_reorder__)) +#else +# define _no_reorder_ #endif #if __GNUC__ >= 15 @@ -160,7 +171,7 @@ #define U64_GB (UINT64_C(1024) * U64_MB) #undef MAX -#define MAX(a, b) __MAX(UNIQ, (a), UNIQ, (b)) +#define MAX(a, b) __MAX(UNIQ, a, UNIQ, b) #define __MAX(aq, a, bq, b) \ ({ \ const typeof(a) UNIQ_T(A, aq) = (a); \ @@ -216,8 +227,14 @@ assert_cc(sizeof(long long) == sizeof(intmax_t)); MAX(_d, a); \ }) +#define MAX5(x, y, z, a, b) \ + ({ \ + const typeof(x) _e = MAX4(x, y, z, a); \ + MAX(_e, b); \ + }) + #undef MIN -#define MIN(a, b) __MIN(UNIQ, (a), UNIQ, (b)) +#define MIN(a, b) __MIN(UNIQ, a, UNIQ, b) #define __MIN(aq, a, bq, b) \ ({ \ const typeof(a) UNIQ_T(A, aq) = (a); \ @@ -225,6 +242,14 @@ assert_cc(sizeof(long long) == sizeof(intmax_t)); UNIQ_T(A, aq) < UNIQ_T(B, bq) ? UNIQ_T(A, aq) : UNIQ_T(B, bq); \ }) +#define ABS_DIFF(a, b) __ABS_DIFF(UNIQ, a, UNIQ, b) +#define __ABS_DIFF(aq, a, bq, b) \ + ({ \ + const typeof(a) UNIQ_T(A, aq) = (a); \ + const typeof(b) UNIQ_T(B, bq) = (b); \ + UNIQ_T(A, aq) < UNIQ_T(B, bq) ? UNIQ_T(B, bq) - UNIQ_T(A, aq) : UNIQ_T(A, aq) - UNIQ_T(B, bq); \ + }) + /* evaluates to (void) if _A or _B are not constant or of different types */ #define CONST_MIN(_A, _B) \ (__builtin_choose_expr( \ @@ -295,7 +320,7 @@ assert_cc(sizeof(long long) == sizeof(intmax_t)); }) #undef CLAMP -#define CLAMP(x, low, high) __CLAMP(UNIQ, (x), UNIQ, (low), UNIQ, (high)) +#define CLAMP(x, low, high) __CLAMP(UNIQ, x, UNIQ, low, UNIQ, high) #define __CLAMP(xq, x, lowq, low, highq, high) \ ({ \ const typeof(x) UNIQ_T(X, xq) = (x); \ @@ -312,7 +337,7 @@ assert_cc(sizeof(long long) == sizeof(intmax_t)); * computation should be possible in the given type. Therefore, we use * [x / y + !!(x % y)]. Note that on "Real CPUs" a division returns both the * quotient and the remainder, so both should be equally fast. */ -#define DIV_ROUND_UP(x, y) __DIV_ROUND_UP(UNIQ, (x), UNIQ, (y)) +#define DIV_ROUND_UP(x, y) __DIV_ROUND_UP(UNIQ, x, UNIQ, y) #define __DIV_ROUND_UP(xq, x, yq, y) \ ({ \ const typeof(x) UNIQ_T(X, xq) = (x); \ @@ -324,11 +349,11 @@ assert_cc(sizeof(long long) == sizeof(intmax_t)); #define __ROUND_UP(q, x, y) \ ({ \ const typeof(y) UNIQ_T(A, q) = (y); \ - const typeof(x) UNIQ_T(B, q) = DIV_ROUND_UP((x), UNIQ_T(A, q)); \ + const typeof(x) UNIQ_T(B, q) = DIV_ROUND_UP(x, UNIQ_T(A, q)); \ typeof(x) UNIQ_T(C, q); \ MUL_SAFE(&UNIQ_T(C, q), UNIQ_T(B, q), UNIQ_T(A, q)) ? UNIQ_T(C, q) : (typeof(x)) -1; \ }) -#define ROUND_UP(x, y) __ROUND_UP(UNIQ, (x), (y)) +#define ROUND_UP(x, y) __ROUND_UP(UNIQ, x, y) #define CASE_F_1(X) case X: #define CASE_F_2(X, ...) case X: CASE_F_1( __VA_ARGS__) diff --git a/src/fundamental/memory-util-fundamental.c b/src/fundamental/memory-util-fundamental.c new file mode 100644 index 0000000000000..1a64fbe514ff8 --- /dev/null +++ b/src/fundamental/memory-util-fundamental.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "memory-util-fundamental.h" + +bool memeqbyte(uint8_t byte, const void *data, size_t length) { + assert(data || length == 0); + + /* Does the buffer consist entirely of the same specific byte value? + * Copied from https://github.com/systemd/casync/, copied in turn from + * https://github.com/rustyrussell/ccan/blob/master/ccan/mem/mem.c#L92, + * which is licensed CC-0. + */ + + const uint8_t *p = data; + + /* Check first 16 bytes manually */ + for (size_t i = 0; i < 16 && length > 0; i++, length--) + if (p[i] != byte) + return false; + + if (length == 0) + return true; + + /* Now we know first 16 bytes match, memcmp() with self. */ + return memcmp(data, p + 16, length) == 0; +} diff --git a/src/fundamental/memory-util-fundamental.h b/src/fundamental/memory-util-fundamental.h index c2a99a2039770..7c88264053ccd 100644 --- a/src/fundamental/memory-util-fundamental.h +++ b/src/fundamental/memory-util-fundamental.h @@ -9,8 +9,7 @@ # include #endif -#include "assert-fundamental.h" -#include "cleanup-fundamental.h" +#include "assert-fundamental.h" /* IWYU pragma: keep */ #include "macro-fundamental.h" #define memzero(x, l) \ @@ -148,3 +147,7 @@ static inline uint64_t ALIGN_OFFSET_U64(uint64_t l, uint64_t ali) { assert(((uintptr_t) _p) % alignof(t) == 0); \ (t *) _p; \ }) + +bool memeqbyte(uint8_t byte, const void *data, size_t length) _nonnull_if_nonzero_(2, 3); +#define memeqzero(data, length) memeqbyte(0x00, data, length) +#define eqzero(x) memeqzero(x, sizeof(x)) diff --git a/src/fundamental/meson.build b/src/fundamental/meson.build index 6bc26caad2b1d..14d956ac07edf 100644 --- a/src/fundamental/meson.build +++ b/src/fundamental/meson.build @@ -8,6 +8,7 @@ fundamental_sources = files( 'edid-fundamental.c', 'efivars-fundamental.c', 'iovec-util-fundamental.h', + 'memory-util-fundamental.c', 'sha1-fundamental.c', 'sha256-fundamental.c', 'string-util-fundamental.c', diff --git a/src/fundamental/string-util-fundamental.h b/src/fundamental/string-util-fundamental.h index e2eb73a4a9dee..83b90e4e9e543 100644 --- a/src/fundamental/string-util-fundamental.h +++ b/src/fundamental/string-util-fundamental.h @@ -24,6 +24,7 @@ #define ALPHANUMERICAL LETTERS DIGITS #define HEXDIGITS DIGITS "abcdefABCDEF" #define LOWERCASE_HEXDIGITS DIGITS "abcdef" +#define UPPERCASE_HEXDIGITS DIGITS "ABCDEF" #define URI_RESERVED ":/?#[]@!$&'()*+;=" /* [RFC3986] */ #define URI_UNRESERVED ALPHANUMERICAL "-._~" /* [RFC3986] */ #define URI_VALID URI_RESERVED URI_UNRESERVED /* [RFC3986] */ diff --git a/src/fundamental/strv-fundamental.h b/src/fundamental/strv-fundamental.h index 3abcdc4b02eea..7e7e34822f515 100644 --- a/src/fundamental/strv-fundamental.h +++ b/src/fundamental/strv-fundamental.h @@ -2,9 +2,14 @@ #pragma once #include "macro-fundamental.h" +#include "string-util-fundamental.h" #define _STRV_FOREACH(s, l, i) \ for (typeof(*(l)) *s, *i = (l); (s = i) && *i; i++) #define STRV_FOREACH(s, l) \ _STRV_FOREACH(s, l, UNIQ_T(i, UNIQ)) + +static inline bool strv_isempty(sd_char * const *l) { + return !l || !*l; +} diff --git a/src/fundamental/uki.h b/src/fundamental/uki.h index 8d67b13b8b5c9..627538a0eb057 100644 --- a/src/fundamental/uki.h +++ b/src/fundamental/uki.h @@ -15,7 +15,7 @@ typedef enum UnifiedSection { UNIFIED_SECTION_DTB, UNIFIED_SECTION_UNAME, UNIFIED_SECTION_SBAT, - UNIFIED_SECTION_PCRSIG, + UNIFIED_SECTION_PCRSIG, /* This is is not measured actually */ UNIFIED_SECTION_PCRPKEY, UNIFIED_SECTION_PROFILE, UNIFIED_SECTION_DTBAUTO, diff --git a/src/fuzz/fuzz-user-record.c b/src/fuzz/fuzz-user-record.c new file mode 100644 index 0000000000000..ff08762ce0d45 --- /dev/null +++ b/src/fuzz/fuzz-user-record.c @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "fuzz.h" +#include "user-record.h" + +#include "sd-json.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_free_ char *str = NULL; + unsigned line = 0; + int r; + + if (outside_size_range(size, 0, 65536)) + return 0; + + assert_se(str = memdup_suffix0(data, size)); + assert_se(ur = user_record_new()); + + fuzz_setup_logging(); + + r = sd_json_parse(str, 0, &v, &line, /* reterr_column= */ NULL); + if (r < 0) { + (void) log_syntax(/* unit= */ NULL, LOG_DEBUG, "", line, r, "JSON parse failure."); + return 0; + } + + r = user_record_load(ur, v, USER_RECORD_LOAD_FULL|USER_RECORD_PERMISSIVE); + if (r >= 0) { + /* We have a valid record, so let's exercise a couple more functions */ + _cleanup_(user_record_unrefp) UserRecord *cloned = NULL; + (void) user_record_clone(ur, USER_RECORD_LOAD_FULL, &cloned); + + (void) user_record_test_blocked(ur); + (void) user_record_test_password_change_required(ur); + (void) user_record_can_authenticate(ur); + (void) user_record_luks_discard(ur); + (void) user_record_drop_caches(ur); + } + + return 0; +} diff --git a/src/fuzz/fuzz-varlink.c b/src/fuzz/fuzz-varlink.c index fb1584a2ea6bf..7bd7e5ab920e9 100644 --- a/src/fuzz/fuzz-varlink.c +++ b/src/fuzz/fuzz-varlink.c @@ -41,7 +41,7 @@ static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userd else assert_se(errno == EAGAIN); } else - iovec_increment(iov, 1, n); + iovec_inc(iov, n); } if (revents & EPOLLIN) { diff --git a/src/fuzz/meson.build b/src/fuzz/meson.build index a1a13950f8c6a..43539422b0f47 100644 --- a/src/fuzz/meson.build +++ b/src/fuzz/meson.build @@ -10,6 +10,7 @@ simple_fuzzers += files( 'fuzz-json.c', 'fuzz-time-util.c', 'fuzz-udev-database.c', + 'fuzz-user-record.c', 'fuzz-varlink.c', 'fuzz-varlink-idl.c', ) diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 4fd92a6057f6a..abbb955e5992e 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -115,11 +115,13 @@ static int add_cryptsetup( return log_oom(); } - r = efi_measured_uki(LOG_WARNING); - if (r > 0) + r = efi_measured_os(LOG_WARNING); + if (r > 0) { /* Enable TPM2 based unlocking automatically, if we have a TPM. See #30176. */ if (!strextend_with_separator(&options, ",", "tpm2-device=auto")) return log_oom(); + } else if (r == 0) + log_debug("Will not enable TPM based unlocking of volume '%s', OS measurements are not explicitly requested and not booted via systemd-stub with measurements enabled.", id); if (FLAGS_SET(flags, MOUNT_MEASURE)) { /* We only measure the root volume key into PCR 15 if we are booted with sd-stub (i.e. in a @@ -130,7 +132,7 @@ static int add_cryptsetup( if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes,tpm2-measure-keyslot-nvpcr=yes")) return log_oom(); if (r == 0) - log_debug("Will not measure volume key of volume '%s', not booted via systemd-stub with measurements enabled.", id); + log_debug("Will not measure volume key of volume '%s', as OS measurements are not explicitly requested and not booted via systemd-stub with measurements enabled.", id); } r = generator_write_cryptsetup_service_section(f, id, what, NULL, options); @@ -240,11 +242,11 @@ static int add_veritysetup( return log_oom(); if (FLAGS_SET(flags, MOUNT_MEASURE)) { - r = efi_measured_uki(LOG_WARNING); + r = efi_measured_os(LOG_WARNING); if (r > 0 && !strextend_with_separator(&options, ",", "tpm2-measure-nvpcr=yes")) return log_oom(); - if (r == 0) - log_debug("Will not measure root hash/signature of volume '%s', not booted via systemd-stub with measurements enabled.", id); + else if (r == 0) + log_debug("Will not measure root hash/signature of volume '%s', OS measurements not explicitly requested and not booted via systemd-stub with measurements enabled.", id); } r = generator_write_veritysetup_service_section( @@ -293,8 +295,8 @@ static int add_veritysetup( return 0; #else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Partition is Verity protected, but systemd-gpt-auto-generator was compiled without libcryptsetup support."); + log_warning("Compiled without libcryptsetup support, skipping verity setup for '%s'.", id); + return 0; #endif } #endif @@ -307,7 +309,8 @@ static int add_mount( MountPointFlags flags, const char *options, const char *description, - const char *post) { + const char *post, + const char *conflicts) { _cleanup_free_ char *unit = NULL, *crypto_what = NULL, *opts_filtered = NULL; _cleanup_fclose_ FILE *f = NULL; @@ -377,6 +380,12 @@ static int add_mount( if (r < 0) return r; + if (conflicts) + fprintf(f, + "Conflicts=%1$s\n" + "Before=%1$s\n", + conflicts); + fprintf(f, "\n" "[Mount]\n" @@ -493,7 +502,8 @@ static int add_partition_mount( (STR_IN_SET(id, "root", "var") ? MOUNT_MEASURE : 0), /* by default measure rootfs and /var, since they contain the "identity" of the system */ options, description, - SPECIAL_LOCAL_FS_TARGET); + SPECIAL_LOCAL_FS_TARGET, + /* conflicts= */ NULL); } static int add_partition_swap(DissectedPartition *p) { @@ -582,7 +592,8 @@ static int add_automount( flags, options, description, - /* post= */ NULL); + /* post= */ NULL, + /* conflicts= */ NULL); if (r < 0) return r; @@ -919,7 +930,8 @@ static int add_root_mount(void) { MOUNT_MEASURE, options, "Root Partition", - in_initrd() ? SPECIAL_INITRD_ROOT_FS_TARGET : SPECIAL_LOCAL_FS_TARGET); + in_initrd() ? SPECIAL_INITRD_ROOT_FS_TARGET : SPECIAL_LOCAL_FS_TARGET, + /* conflicts= */ NULL); #else return 0; #endif @@ -995,7 +1007,8 @@ static int add_usr_mount(void) { /* flags= */ 0, options, "/usr/ Partition", - in_initrd() ? SPECIAL_INITRD_USR_FS_TARGET : SPECIAL_LOCAL_FS_TARGET); + in_initrd() ? SPECIAL_INITRD_USR_FS_TARGET : SPECIAL_LOCAL_FS_TARGET, + /* conflicts= */ NULL); if (r < 0) return r; @@ -1009,7 +1022,8 @@ static int add_usr_mount(void) { MOUNT_VALIDATEFS, "bind", "/usr/ Partition (Final)", - SPECIAL_INITRD_FS_TARGET); + SPECIAL_INITRD_FS_TARGET, + /* conflicts= */ NULL); if (r < 0) return r; } @@ -1017,6 +1031,49 @@ static int add_usr_mount(void) { return 0; } +static int add_early_esp_mount(void) { + int r; + + /* Early ESP discovery is a bit different than the other mounts here: it's purely about the initrd, + * and goes away during the transition to the host (where it might likely be mounted again, but then + * via autofs, hence lazily). Moreover, the location is fixed → /sysefi/, i.e. we do not bother with + * XBOOTLDR vs. ESP for this. Also, the mount is not pulled in by default, but is expected to be + * pulled in by the component that uses it. + * + * The initial usecase for this is software TPM that needs a place to store its state before the root + * file system can be mounted. + * + * Or in other words: this is much simpler, more focussed on a short-lived boot-time operation than + * the regular logic during later boot. */ + + if (!in_initrd()) + return 0; + + if (!is_efi_boot()) + return 0; + + _cleanup_free_ char *options = NULL; + r = partition_pick_mount_options( + PARTITION_ESP, + "vfat", + /* rw= */ true, + /* discard= */ false, + &options, + /* ret_ms_flags= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to pick ESP mount options: %m"); + + return add_mount("esp", + "/dev/disk/by-designator/esp", + "/sysefi/", + "vfat", + MOUNT_RW, + options, + "EFI System Partition (Early)", + /* post= */ NULL, + /* conflicts= */ "initrd-switch-root.target"); +} + static int process_loader_partitions(DissectedPartition *esp, DissectedPartition *xbootldr) { sd_id128_t loader_uuid; int r; @@ -1190,7 +1247,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; /* Disable root disk logic if there's a root= value specified (unless it happens to be - * "gpt-auto" or "gpt-auto-force") */ + * "gpt-auto", "gpt-auto-force", "dissect", "dissect-force") */ arg_auto_root = parse_gpt_auto_root("root=", value); assert(arg_auto_root >= 0); @@ -1244,9 +1301,6 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (proc_cmdline_value_missing(key, value)) return 0; - /* Disable root disk logic if there's a root= value specified (unless it happens to be - * "gpt-auto" or "gpt-auto-force") */ - arg_auto_usr = parse_gpt_auto_root("mount.usr=", value); assert(arg_auto_usr >= 0); @@ -1325,6 +1379,7 @@ static int run(const char *dest, const char *dest_early, const char *dest_late) r = 0; RET_GATHER(r, add_root_mount()); RET_GATHER(r, add_usr_mount()); + RET_GATHER(r, add_early_esp_mount()); RET_GATHER(r, add_mounts()); return r; diff --git a/src/growfs/growfs.c b/src/growfs/growfs.c index d991b82d67ce6..ff6a5909e795c 100644 --- a/src/growfs/growfs.c +++ b/src/growfs/growfs.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "alloc-util.h" @@ -13,10 +12,12 @@ #include "devnum-util.h" #include "dissect-image.h" #include "fd-util.h" +#include "format-table.h" #include "format-util.h" #include "log.h" #include "main-func.h" #include "mountpoint-util.h" +#include "options.h" #include "pretty-print.h" #include "resize-fs.h" #include "string-util.h" @@ -27,14 +28,14 @@ static bool arg_dry_run = false; #if HAVE_LIBCRYPTSETUP static int resize_crypt_luks_device(dev_t devno, const char *fstype, dev_t main_devno) { _cleanup_free_ char *devpath = NULL, *main_devpath = NULL; - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_close_ int main_devfd = -EBADF; uint64_t size; int r; - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_ERR); if (r < 0) - return log_error_errno(r, "Cannot resize LUKS device: %m"); + return r; main_devfd = r = device_open_from_devnum(S_IFBLK, main_devno, O_RDONLY|O_CLOEXEC, &main_devpath); if (r < 0) @@ -92,10 +93,6 @@ static int maybe_resize_underlying_device( assert(mountfd >= 0); assert(mountpath); -#if HAVE_LIBCRYPTSETUP - cryptsetup_enable_logging(NULL); -#endif - r = get_block_device_harder_fd(mountfd, &devno); if (r < 0) return log_error_errno(r, "Failed to determine underlying block device of \"%s\": %m", @@ -132,67 +129,60 @@ static int maybe_resize_underlying_device( static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-growfs@.service", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] /path/to/mountpoint\n\n" - "Grow filesystem or encrypted payload to device size.\n\n" - "Options:\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - " -n --dry-run Just print what would be done\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + "Grow filesystem or encrypted payload to device size.\n", + program_invocation_short_name); + + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; - - int c; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version" , no_argument, NULL, ARG_VERSION }, - { "dry-run", no_argument, NULL, 'n' }, - {} - }; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hn", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'n': + OPTION('n', "dry-run", NULL, "Just print what would be done"): arg_dry_run = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind + 1 != argc) + if (option_parser_get_n_args(&state) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s excepts exactly one argument (the mount point).", + "%s expects exactly one argument (the mount point).", program_invocation_short_name); - arg_target = argv[optind]; + arg_target = option_parser_get_args(&state)[0]; return 1; } diff --git a/src/hibernate-resume/hibernate-resume-config.c b/src/hibernate-resume/hibernate-resume-config.c index 8e2ca625aeca0..8b4873924599c 100644 --- a/src/hibernate-resume/hibernate-resume-config.c +++ b/src/hibernate-resume/hibernate-resume-config.c @@ -242,6 +242,8 @@ int acquire_hibernate_info(HibernateInfo *ret) { _cleanup_(hibernate_info_done) HibernateInfo i = {}; int r; + assert(ret); + r = get_kernel_hibernate_location(&i.cmdline); if (r < 0) return r; diff --git a/src/hibernate-resume/hibernate-resume-generator.c b/src/hibernate-resume/hibernate-resume-generator.c index 79c7d41bb453d..998c8e84d9504 100644 --- a/src/hibernate-resume/hibernate-resume-generator.c +++ b/src/hibernate-resume/hibernate-resume-generator.c @@ -86,7 +86,7 @@ static int add_dissected_swap_cryptsetup(void) { r = generator_write_cryptsetup_service_section( f, "swap", DISSECTED_SWAP_LUKS_DEVICE, /* key_file= */ NULL, - efi_measured_uki(LOG_DEBUG) > 0 ? "tpm2-device=auto" : NULL); + efi_measured_os(LOG_DEBUG) > 0 ? "tpm2-device=auto" : NULL); if (r < 0) return r; diff --git a/src/hibernate-resume/hibernate-resume.c b/src/hibernate-resume/hibernate-resume.c index f3cf399e1d84b..cc48bf22fb3dc 100644 --- a/src/hibernate-resume/hibernate-resume.c +++ b/src/hibernate-resume/hibernate-resume.c @@ -1,19 +1,22 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" #include "build.h" #include "devnum-util.h" +#include "format-table.h" #include "hibernate-resume-config.h" #include "hibernate-util.h" #include "initrd-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-util.h" #include "pretty-print.h" +#include "stat-util.h" #include "static-destruct.h" +#include "strv.h" static HibernateInfo arg_info = {}; static bool arg_clear = false; @@ -22,70 +25,59 @@ STATIC_DESTRUCTOR_REGISTER(arg_info, hibernate_info_done); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-hibernate-resume", "8", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] [DEVICE [OFFSET]]\n" - "\n%sInitiate resume from hibernation.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --clear Clear hibernation storage information from EFI and exit\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] [DEVICE [OFFSET]]\n\n" + "%sInitiate resume from hibernation.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_CLEAR, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "clear", no_argument, NULL, ARG_CLEAR }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_CLEAR: + OPTION_LONG("clear", NULL, + "Clear hibernation storage information from EFI and exit"): arg_clear = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (argc > optind && arg_clear) + if (option_parser_get_n_args(&state) > 0 && arg_clear) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extraneous arguments specified with --clear, refusing."); + *ret_args = option_parser_get_args(&state); return 1; } @@ -129,11 +121,14 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - if (argc - optind > 2) + size_t n_args = strv_length(args); + + if (n_args > 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program expects zero, one, or two arguments."); umask(0022); @@ -145,7 +140,7 @@ static int run(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Not running in initrd, refusing to initiate resume from hibernation."); - if (argc <= optind) { + if (n_args == 0) { r = setup_hibernate_info_and_warn(); if (r <= 0) return r; @@ -153,21 +148,21 @@ static int run(int argc, char *argv[]) { if (arg_info.efi) (void) clear_efi_hibernate_location_and_warn(); } else { - arg_info.device = ASSERT_PTR(argv[optind]); + arg_info.device = ASSERT_PTR(args[0]); - if (argc - optind == 2) { - r = safe_atou64(argv[optind + 1], &arg_info.offset); + if (n_args == 2) { + r = safe_atou64(args[1], &arg_info.offset); if (r < 0) - return log_error_errno(r, "Failed to parse resume offset %s: %m", argv[optind + 1]); + return log_error_errno(r, "Failed to parse resume offset %s: %m", args[1]); } } if (stat(arg_info.device, &st) < 0) return log_error_errno(errno, "Failed to stat resume device '%s': %m", arg_info.device); - if (!S_ISBLK(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), - "Resume device '%s' is not a block device.", arg_info.device); + r = stat_verify_block(&st); + if (r < 0) + return log_error_errno(r, "Resume device '%s' is not a block device.", arg_info.device); /* The write shall not return if a resume takes place. */ r = write_resume_config(st.st_rdev, arg_info.offset, arg_info.device); diff --git a/src/home/homectl-pkcs11.c b/src/home/homectl-pkcs11.c index a72aecf135643..3ef1b80c225e3 100644 --- a/src/home/homectl-pkcs11.c +++ b/src/home/homectl-pkcs11.c @@ -2,12 +2,12 @@ #include "sd-json.h" +#include "crypto-util.h" #include "errno-util.h" #include "hexdecoct.h" #include "homectl-pkcs11.h" #include "libcrypt-util.h" #include "log.h" -#include "openssl-util.h" #include "pkcs11-util.h" #include "string-util.h" #include "strv.h" diff --git a/src/home/homectl.c b/src/home/homectl.c index 2b92ab6481eae..271e03587502b 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -18,6 +18,7 @@ #include "cgroup-util.h" #include "chase.h" #include "creds-util.h" +#include "crypto-util.h" #include "dirent-util.h" #include "dns-domain.h" #include "env-util.h" @@ -39,7 +40,6 @@ #include "libfido2-util.h" #include "locale-util.h" #include "main-func.h" -#include "openssl-util.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -178,7 +178,7 @@ static int acquire_bus(sd_bus **bus) { return 0; } -static int list_homes(int argc, char *argv[], void *userdata) { +static int verb_list_homes(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -627,7 +627,7 @@ static int acquire_passed_secrets(const char *user_name, UserRecord **ret) { return 0; } -static int activate_home(int argc, char *argv[], void *userdata) { +static int verb_activate_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -675,7 +675,7 @@ static int activate_home(int argc, char *argv[], void *userdata) { return ret; } -static int deactivate_home(int argc, char *argv[], void *userdata) { +static int verb_deactivate_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -758,7 +758,7 @@ static int inspect_home(sd_bus *bus, const char *name) { if (r < 0) return bus_log_parse_error(r); - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE|SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return log_error_errno(r, "Failed to parse JSON identity: %m"); @@ -775,7 +775,7 @@ static int inspect_home(sd_bus *bus, const char *name) { return 0; } -static int inspect_homes(int argc, char *argv[], void *userdata) { +static int verb_inspect_homes(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -833,7 +833,7 @@ static int authenticate_home(sd_bus *bus, const char *name) { } } -static int authenticate_homes(int argc, char *argv[], void *userdata) { +static int verb_authenticate_homes(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -1159,7 +1159,11 @@ static int acquire_new_home_record(sd_json_variant *input, UserRecord **ret) { r = sd_json_parse_file( streq(arg_identity, "-") ? stdin : NULL, - streq(arg_identity, "-") ? "" : arg_identity, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + streq(arg_identity, "-") ? "" : arg_identity, + SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, + &v, + &line, + &column); if (r < 0) return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column); } else @@ -1557,7 +1561,7 @@ static int create_home_common(sd_json_variant *input, bool show_enforce_password return 0; } -static int create_home(int argc, char *argv[], void *userdata) { +static int verb_create_home(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; if (argc >= 2) { @@ -1592,7 +1596,7 @@ static int create_home(int argc, char *argv[], void *userdata) { return create_home_common(/* input= */ NULL, /* show_enforce_password_policy_hint= */ true); } -static int verb_adopt_home(int argc, char *argv[], void *userdata) { +static int verb_adopt_home(int argc, char *argv[], uintptr_t _data, void *userdata) { int r, ret = 0; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1667,14 +1671,14 @@ static int register_home_one(sd_bus *bus, FILE *f, const char *path) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; unsigned line = 0, column = 0; - r = sd_json_parse_file(f, path, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse_file(f, path, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "[%s:%u:%u] Failed to parse user record: %m", path, line, column); return register_home_common(bus, v); } -static int verb_register_home(int argc, char *argv[], void *userdata) { +static int verb_register_home(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1705,7 +1709,7 @@ static int verb_register_home(int argc, char *argv[], void *userdata) { return r; } -static int verb_unregister_home(int argc, char *argv[], void *userdata) { +static int verb_unregister_home(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1735,7 +1739,7 @@ static int verb_unregister_home(int argc, char *argv[], void *userdata) { return ret; } -static int remove_home(int argc, char *argv[], void *userdata) { +static int verb_remove_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -1785,7 +1789,11 @@ static int acquire_updated_home_record( r = sd_json_parse_file( streq(arg_identity, "-") ? stdin : NULL, - streq(arg_identity, "-") ? "" : arg_identity, SD_JSON_PARSE_SENSITIVE, &json, &line, &column); + streq(arg_identity, "-") ? "" : arg_identity, + SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, + &json, + &line, + &column); if (r < 0) return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column); @@ -1822,7 +1830,12 @@ static int acquire_updated_home_record( if (incomplete) return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Lacking rights to acquire user record including privileged metadata, can't update record."); - r = sd_json_parse(text, SD_JSON_PARSE_SENSITIVE, &json, NULL, NULL); + r = sd_json_parse( + text, + SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, + &json, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL); if (r < 0) return log_error_errno(r, "Failed to parse JSON identity: %m"); @@ -1900,7 +1913,7 @@ static int home_record_reset_human_interaction_permission(UserRecord *hr) { return 0; } -static int update_home(int argc, char *argv[], void *userdata) { +static int verb_update_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(user_record_unrefp) UserRecord *hr = NULL, *secret = NULL; _cleanup_free_ char *buffer = NULL; @@ -2077,7 +2090,7 @@ static int update_home(int argc, char *argv[], void *userdata) { return 0; } -static int passwd_home(int argc, char *argv[], void *userdata) { +static int verb_passwd_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(user_record_unrefp) UserRecord *old_secret = NULL, *new_secret = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_free_ char *buffer = NULL; @@ -2194,7 +2207,7 @@ static int parse_disk_size(const char *t, uint64_t *ret) { return 0; } -static int resize_home(int argc, char *argv[], void *userdata) { +static int verb_resize_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(user_record_unrefp) UserRecord *secret = NULL; uint64_t ds = UINT64_MAX; @@ -2256,7 +2269,7 @@ static int resize_home(int argc, char *argv[], void *userdata) { return 0; } -static int lock_home(int argc, char *argv[], void *userdata) { +static int verb_lock_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -2287,7 +2300,7 @@ static int lock_home(int argc, char *argv[], void *userdata) { return ret; } -static int unlock_home(int argc, char *argv[], void *userdata) { +static int verb_unlock_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -2335,7 +2348,7 @@ static int unlock_home(int argc, char *argv[], void *userdata) { return ret; } -static int with_home(int argc, char *argv[], void *userdata) { +static int verb_with_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -2458,7 +2471,7 @@ static int with_home(int argc, char *argv[], void *userdata) { return ret; } -static int lock_all_homes(int argc, char *argv[], void *userdata) { +static int verb_lock_all_homes(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -2479,7 +2492,7 @@ static int lock_all_homes(int argc, char *argv[], void *userdata) { return 0; } -static int deactivate_all_homes(int argc, char *argv[], void *userdata) { +static int verb_deactivate_all_homes(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -2500,7 +2513,7 @@ static int deactivate_all_homes(int argc, char *argv[], void *userdata) { return 0; } -static int rebalance(int argc, char *argv[], void *userdata) { +static int verb_rebalance(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -2569,7 +2582,7 @@ static int create_or_register_from_credentials(void) { /* f= */ NULL, fd, de->d_name, - /* flags= */ 0, + /* flags= */ SD_JSON_PARSE_MUST_BE_OBJECT, &identity, &line, &column); @@ -2983,7 +2996,7 @@ static int create_interactively(void) { static int add_signing_keys_from_credentials(void); -static int verb_firstboot(int argc, char *argv[], void *userdata) { +static int verb_firstboot(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; /* Let's honour the systemd.firstboot kernel command line option, just like the systemd-firstboot @@ -3645,6 +3658,8 @@ static int parse_environment_field(sd_json_variant **identity, const char *field static int parse_language_field(char ***languages, const char *arg) { int r; + assert(languages); + if (isempty(arg)) { r = drop_from_identity("preferredLanguage", "additionalLanguages"); if (r < 0) @@ -3678,7 +3693,6 @@ static int parse_language_field(char ***languages, const char *arg) { } static int parse_group_field( - sd_json_variant *source_identity, sd_json_variant **identity, const char *field, const char *arg) { @@ -3704,7 +3718,7 @@ static int parse_group_field( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid group name %s.", word); _cleanup_(sd_json_variant_unrefp) sd_json_variant *mo = - sd_json_variant_ref(sd_json_variant_by_key(source_identity, field)); + sd_json_variant_ref(sd_json_variant_by_key(*identity, field)); r = sd_json_variant_strv(mo, &list); if (r < 0) @@ -3866,7 +3880,7 @@ static int parse_fido2_device_field(const char *arg) { return 1; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -3945,6 +3959,7 @@ static int help(int argc, char *argv[], void *userdata) { " --alias=ALIAS Define alias usernames for this account\n" " --email-address=EMAIL Email address for user\n" " --location=LOCATION Set location of user on earth\n" + " --birth-date=[DATE] Set user birth date (YYYY-MM-DD)\n" " --icon-name=NAME Icon name for user\n" " -d --home-dir=PATH Home directory\n" " -u --uid=UID Numeric UID for user\n" @@ -4082,6 +4097,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { _cleanup_strv_free_ char **arg_languages = NULL; @@ -4109,6 +4128,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_LOCKED, ARG_SSH_AUTHORIZED_KEYS, ARG_LOCATION, + ARG_BIRTH_DATE, ARG_ICON_NAME, ARG_PASSWORD_HINT, ARG_NICE, @@ -4195,6 +4215,7 @@ static int parse_argv(int argc, char *argv[]) { { "alias", required_argument, NULL, ARG_ALIAS }, { "email-address", required_argument, NULL, ARG_EMAIL_ADDRESS }, { "location", required_argument, NULL, ARG_LOCATION }, + { "birth-date", required_argument, NULL, ARG_BIRTH_DATE }, { "password-hint", required_argument, NULL, ARG_PASSWORD_HINT }, { "icon-name", required_argument, NULL, ARG_ICON_NAME }, { "home-dir", required_argument, NULL, 'd' }, /* Compatible with useradd(8) */ @@ -4316,7 +4337,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -4363,7 +4384,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_ALIAS: - r = parse_group_field(arg_identity_extra, &arg_identity_extra, "aliases", optarg); + r = parse_group_field(&arg_identity_extra, "aliases", optarg); if (r < 0) return r; break; @@ -4408,6 +4429,22 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_BIRTH_DATE: + if (isempty(optarg)) { + r = drop_from_identity("birthDate"); + if (r < 0) + return r; + } else { + r = parse_birth_date(optarg, /* ret= */ NULL); + if (r < 0) + return log_error_errno(r, "Invalid birth date (expected YYYY-MM-DD): %s", optarg); + + r = parse_string_field(&arg_identity_extra, "birthDate", optarg); + if (r < 0) + return r; + } + break; + case ARG_CIFS_SERVICE: if (!isempty(optarg)) { r = parse_cifs_service(optarg, /* ret_host= */ NULL, /* ret_service= */ NULL, /* ret_path= */ NULL); @@ -4629,7 +4666,7 @@ static int parse_argv(int argc, char *argv[]) { IN_SET(c, ARG_STORAGE, ARG_FS_TYPE) ? &arg_identity_extra_this_machine : &arg_identity_extra; - if (!isempty(optarg) && !string_is_safe(optarg)) + if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Parameter for field %s not valid: %s", field, optarg); @@ -4656,9 +4693,7 @@ static int parse_argv(int argc, char *argv[]) { } case 'G': - r = parse_group_field(arg_identity_extra, - match_identity ?: &arg_identity_extra, - "memberOf", optarg); + r = parse_group_field(match_identity ?: &arg_identity_extra, "memberOf", optarg); if (r < 0) return r; break; @@ -5112,7 +5147,7 @@ static int fallback_shell(int argc, char *argv[]) { if (r < 0) return bus_log_parse_error(r); - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE|SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return log_error_errno(r, "Failed to parse JSON identity: %m"); @@ -5245,7 +5280,7 @@ static int fallback_shell(int argc, char *argv[]) { return log_error_errno(errno, "Failed to execute shell '%s': %m", shell); } -static int verb_list_signing_keys(int argc, char *argv[], void *userdata) { +static int verb_list_signing_keys(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -5281,13 +5316,17 @@ static int verb_list_signing_keys(int argc, char *argv[], void *userdata) { /* Let's decode the PEM key to DER (so that we lose prefix/suffix), then truncate it * for display reasons. */ + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *key = NULL; r = openssl_pubkey_from_pem(pem, SIZE_MAX, &key); if (r < 0) return log_error_errno(r, "Failed to parse PEM: %m"); _cleanup_free_ void *der = NULL; - int n = i2d_PUBKEY(key, (unsigned char**) &der); + int n = sym_i2d_PUBKEY(key, (unsigned char**) &der); if (n < 0) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to encode key as DER."); @@ -5331,7 +5370,7 @@ static int verb_list_signing_keys(int argc, char *argv[], void *userdata) { return 0; } -static int verb_get_signing_key(int argc, char *argv[], void *userdata) { +static int verb_get_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -5387,7 +5426,7 @@ static int add_signing_key_one(sd_bus *bus, const char *fn, FILE *key) { return 0; } -static int verb_add_signing_key(int argc, char *argv[], void *userdata) { +static int verb_add_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -5494,7 +5533,7 @@ static int remove_signing_key_one(sd_bus *bus, const char *fn) { return 0; } -static int verb_remove_signing_key(int argc, char *argv[], void *userdata) { +static int verb_remove_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -5511,31 +5550,31 @@ static int verb_remove_signing_key(int argc, char *argv[], void *userdata) { static int run(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "list", VERB_ANY, 1, VERB_DEFAULT, list_homes }, - { "activate", 2, VERB_ANY, 0, activate_home }, - { "deactivate", 2, VERB_ANY, 0, deactivate_home }, - { "inspect", VERB_ANY, VERB_ANY, 0, inspect_homes }, - { "authenticate", VERB_ANY, VERB_ANY, 0, authenticate_homes }, - { "create", VERB_ANY, 2, 0, create_home }, - { "adopt", VERB_ANY, VERB_ANY, 0, verb_adopt_home }, - { "register", VERB_ANY, VERB_ANY, 0, verb_register_home }, - { "unregister", 2, VERB_ANY, 0, verb_unregister_home }, - { "remove", 2, VERB_ANY, 0, remove_home }, - { "update", VERB_ANY, 2, 0, update_home }, - { "passwd", VERB_ANY, 2, 0, passwd_home }, - { "resize", 2, 3, 0, resize_home }, - { "lock", 2, VERB_ANY, 0, lock_home }, - { "unlock", 2, VERB_ANY, 0, unlock_home }, - { "with", 2, VERB_ANY, 0, with_home }, - { "lock-all", VERB_ANY, 1, 0, lock_all_homes }, - { "deactivate-all", VERB_ANY, 1, 0, deactivate_all_homes }, - { "rebalance", VERB_ANY, 1, 0, rebalance }, - { "firstboot", VERB_ANY, 1, 0, verb_firstboot }, - { "list-signing-keys", VERB_ANY, 1, 0, verb_list_signing_keys }, - { "get-signing-key", VERB_ANY, VERB_ANY, 0, verb_get_signing_key }, - { "add-signing-key", VERB_ANY, VERB_ANY, 0, verb_add_signing_key }, - { "remove-signing-key", 2, VERB_ANY, 0, verb_remove_signing_key }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list_homes }, + { "activate", 2, VERB_ANY, 0, verb_activate_home }, + { "deactivate", 2, VERB_ANY, 0, verb_deactivate_home }, + { "inspect", VERB_ANY, VERB_ANY, 0, verb_inspect_homes }, + { "authenticate", VERB_ANY, VERB_ANY, 0, verb_authenticate_homes }, + { "create", VERB_ANY, 2, 0, verb_create_home }, + { "adopt", VERB_ANY, VERB_ANY, 0, verb_adopt_home }, + { "register", VERB_ANY, VERB_ANY, 0, verb_register_home }, + { "unregister", 2, VERB_ANY, 0, verb_unregister_home }, + { "remove", 2, VERB_ANY, 0, verb_remove_home }, + { "update", VERB_ANY, 2, 0, verb_update_home }, + { "passwd", VERB_ANY, 2, 0, verb_passwd_home }, + { "resize", 2, 3, 0, verb_resize_home }, + { "lock", 2, VERB_ANY, 0, verb_lock_home }, + { "unlock", 2, VERB_ANY, 0, verb_unlock_home }, + { "with", 2, VERB_ANY, 0, verb_with_home }, + { "lock-all", VERB_ANY, 1, 0, verb_lock_all_homes }, + { "deactivate-all", VERB_ANY, 1, 0, verb_deactivate_all_homes }, + { "rebalance", VERB_ANY, 1, 0, verb_rebalance }, + { "firstboot", VERB_ANY, 1, 0, verb_firstboot }, + { "list-signing-keys", VERB_ANY, 1, 0, verb_list_signing_keys }, + { "get-signing-key", VERB_ANY, VERB_ANY, 0, verb_get_signing_key }, + { "add-signing-key", VERB_ANY, VERB_ANY, 0, verb_add_signing_key }, + { "remove-signing-key", 2, VERB_ANY, 0, verb_remove_signing_key }, {} }; diff --git a/src/home/homed-bus.c b/src/home/homed-bus.c index c96ecf662059b..f185e87295537 100644 --- a/src/home/homed-bus.c +++ b/src/home/homed-bus.c @@ -24,7 +24,7 @@ int bus_message_read_secret(sd_bus_message *m, UserRecord **ret, sd_bus_error *e if (r < 0) return r; - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE|SD_JSON_PARSE_MUST_BE_OBJECT, &v, &line, &column); if (r < 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse JSON secret record at %u:%u: %m", line, column); @@ -57,7 +57,7 @@ int bus_message_read_home_record(sd_bus_message *m, UserRecordLoadFlags flags, U if (r < 0) return r; - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE|SD_JSON_PARSE_MUST_BE_OBJECT, &v, &line, &column); if (r < 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse JSON identity record at %u:%u: %m", line, column); diff --git a/src/home/homed-home-bus.c b/src/home/homed-home-bus.c index f16756b32ef43..10b9af8e026fc 100644 --- a/src/home/homed-home-bus.c +++ b/src/home/homed-home-bus.c @@ -801,6 +801,8 @@ static int bus_home_object_find( Home *h; int r; + assert(found); + r = sd_bus_path_decode(path, "/org/freedesktop/home1/home", &e); if (r <= 0) return 0; @@ -850,7 +852,7 @@ static int bus_home_node_enumerator( return 1; } -const sd_bus_vtable home_vtable[] = { +static const sd_bus_vtable home_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("UserName", "s", diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 985b18661cf78..f467ef7015d3b 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -589,7 +589,7 @@ static int home_parse_worker_stdout(int _fd, UserRecord **ret) { } unsigned line = 0, column = 0; - r = sd_json_parse_file(f, "stdout", SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse_file(f, "stdout", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column); @@ -2690,7 +2690,7 @@ int home_augment_status( r = sd_json_buildo(&status, SD_JSON_BUILD_PAIR_STRING("state", home_state_to_string(state)), SD_JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.Home")), - SD_JSON_BUILD_PAIR("useFallback", SD_JSON_BUILD_BOOLEAN(!HOME_STATE_IS_ACTIVE(state))), + SD_JSON_BUILD_PAIR_BOOLEAN("useFallback", !HOME_STATE_IS_ACTIVE(state)), SD_JSON_BUILD_PAIR("fallbackShell", JSON_BUILD_CONST_STRING(BINDIR "/systemd-home-fallback-shell")), SD_JSON_BUILD_PAIR("fallbackHomeDirectory", JSON_BUILD_CONST_STRING("/")), SD_JSON_BUILD_PAIR_CONDITION(disk_size != UINT64_MAX, "diskSize", SD_JSON_BUILD_UNSIGNED(disk_size)), @@ -3214,8 +3214,9 @@ static int home_get_image_path_seat(Home *h, char **ret) { if (stat(ip, &st) < 0) return -errno; - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; + r = stat_verify_block(&st); + if (r < 0) + return r; r = sd_device_new_from_stat_rdev(&d, &st); if (r < 0) diff --git a/src/home/homed-manager-bus.c b/src/home/homed-manager-bus.c index f35268567218e..b8385c781e2b4 100644 --- a/src/home/homed-manager-bus.c +++ b/src/home/homed-manager-bus.c @@ -12,6 +12,7 @@ #include "bus-message-util.h" #include "bus-object.h" #include "bus-polkit.h" +#include "crypto-util.h" #include "fileio.h" #include "format-util.h" #include "home-util.h" @@ -22,7 +23,6 @@ #include "homed-manager-bus.h" #include "homed-operation.h" #include "log.h" -#include "openssl-util.h" #include "path-util.h" #include "set.h" #include "string-util.h" @@ -936,14 +936,14 @@ static bool manager_has_public_key(Manager *m, EVP_PKEY *needle) { EVP_PKEY *pkey; HASHMAP_FOREACH(pkey, m->public_keys) { - r = EVP_PKEY_eq(pkey, needle); + r = sym_EVP_PKEY_eq(pkey, needle); if (r > 0) return true; /* EVP_PKEY_eq() returns -1 and -2 too under some conditions, which we'll all treat as "not the same" */ } - r = EVP_PKEY_eq(m->private_key, needle); + r = sym_EVP_PKEY_eq(m->private_key, needle); if (r > 0) return true; diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c index 85c92192f483d..fc6fe8a4b1c44 100644 --- a/src/home/homed-manager.c +++ b/src/home/homed-manager.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -25,6 +24,7 @@ #include "clean-ipc.h" #include "common-signal.h" #include "conf-files.h" +#include "crypto-util.h" #include "device-util.h" #include "dirent-util.h" #include "errno-util.h" @@ -43,7 +43,6 @@ #include "homed-varlink.h" #include "mkdir.h" #include "notify-recv.h" -#include "openssl-util.h" #include "ordered-set.h" #include "quota-util.h" #include "random-util.h" @@ -313,7 +312,7 @@ Manager* manager_free(Manager *m) { m->homes_by_sysfs = hashmap_free(m->homes_by_sysfs); if (m->private_key) - EVP_PKEY_free(m->private_key); + sym_EVP_PKEY_free(m->private_key); hashmap_free(m->public_keys); @@ -406,9 +405,9 @@ static int manager_add_home_by_record( goto unlink_this_file; unsigned line = 0, column = 0; - r = sd_json_parse_file_at(NULL, dir_fd, fname, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse_file_at(/* f= */ NULL, dir_fd, fname, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) - return log_error_errno(r, "Failed to parse identity record at %s:%u%u: %m", fname, line, column); + return log_error_errno(r, "Failed to parse identity record at %s:%u:%u: %m", fname, line, column); if (sd_json_variant_is_blank_object(v)) goto unlink_this_file; @@ -1317,7 +1316,7 @@ static int manager_load_key_pair(Manager *m) { assert(m); if (m->private_key) { - EVP_PKEY_free(m->private_key); + sym_EVP_PKEY_free(m->private_key); m->private_key = NULL; } @@ -1337,7 +1336,7 @@ static int manager_load_key_pair(Manager *m) { if (st.st_uid != 0 || (st.st_mode & 0077) != 0) return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Private key file is readable by more than the root user"); - m->private_key = PEM_read_PrivateKey(f, NULL, NULL, NULL); + m->private_key = sym_PEM_read_PrivateKey(f, NULL, NULL, NULL); if (!m->private_key) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to load private key pair"); @@ -1353,20 +1352,20 @@ static int manager_generate_key_pair(Manager *m) { int r; if (m->private_key) { - EVP_PKEY_free(m->private_key); + sym_EVP_PKEY_free(m->private_key); m->private_key = NULL; } - ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL); + ctx = sym_EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL); if (!ctx) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to allocate Ed25519 key generation context."); - if (EVP_PKEY_keygen_init(ctx) <= 0) + if (sym_EVP_PKEY_keygen_init(ctx) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize Ed25519 key generation context."); log_info("Generating key pair for signing local user identity records."); - if (EVP_PKEY_keygen(ctx, &m->private_key) <= 0) + if (sym_EVP_PKEY_keygen(ctx, &m->private_key) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate Ed25519 key pair"); log_info("Successfully created Ed25519 key pair."); @@ -1378,7 +1377,7 @@ static int manager_generate_key_pair(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to open key file for writing: %m"); - if (PEM_write_PUBKEY(fpublic, m->private_key) <= 0) + if (sym_PEM_write_PUBKEY(fpublic, m->private_key) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write public key."); (void) fchmod(fileno(fpublic), 0444); /* Make public key world readable */ @@ -1394,7 +1393,7 @@ static int manager_generate_key_pair(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to open key file for writing: %m"); - if (PEM_write_PrivateKey(fprivate, m->private_key, NULL, NULL, 0, NULL, NULL) <= 0) + if (sym_PEM_write_PrivateKey(fprivate, m->private_key, NULL, NULL, 0, NULL, NULL) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write private key pair."); (void) fchmod(fileno(fprivate), 0400); /* Make private key root readable */ @@ -1459,7 +1458,8 @@ int manager_sign_user_record(Manager *m, UserRecord *u, UserRecord **ret, sd_bus return user_record_sign(u, m->private_key, ret); } -DEFINE_HASH_OPS_FULL(public_key_hash_ops, char, string_hash_func, string_compare_func, free, EVP_PKEY, EVP_PKEY_free); +/* dlopen_libcrypto() must have been called before populating this hashmap. */ +DEFINE_HASH_OPS_FULL(public_key_hash_ops, char, string_hash_func, string_compare_func, free, EVP_PKEY, sym_EVP_PKEY_free); static int manager_load_public_key_one(Manager *m, const char *path) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; @@ -1495,7 +1495,7 @@ static int manager_load_public_key_one(Manager *m, const char *path) { if (st.st_uid != 0 || (st.st_mode & 0022) != 0) return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Public key file %s is writable by more than the root user, refusing.", path); - pkey = PEM_read_PUBKEY(f, &pkey, NULL, NULL); + pkey = sym_PEM_read_PUBKEY(f, &pkey, NULL, NULL); if (!pkey) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key file %s.", path); @@ -1537,6 +1537,10 @@ int manager_startup(Manager *m) { assert(m); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + r = manager_listen_notify(m); if (r < 0) return r; @@ -1889,10 +1893,10 @@ static int manager_rebalance_calculate(Manager *m) { assert(h->rebalance_usage <= usage_sum); assert(h->rebalance_weight <= weight_sum); - d = ((double) (free_sum / 4096.0) * (double) h->rebalance_weight) / (double) weight_sum; /* Calculate new space for this home in units of 4K */ + d = free_sum / 4096.0 * h->rebalance_weight / weight_sum; /* Calculate new space for this home in units of 4K */ /* Convert from units of 4K back to bytes */ - if (d >= (double) (UINT64_MAX/4096)) + if (d >= UINT64_MAX / 4096) new_free = UINT64_MAX; else new_free = (uint64_t) d * 4096; @@ -1928,7 +1932,7 @@ static int manager_rebalance_calculate(Manager *m) { h->rebalance_pending = true; } - if ((fabs((double) h->rebalance_size - (double) h->rebalance_goal) * 100 / (double) h->rebalance_size) >= 5.0) + if (ABS_DIFF(h->rebalance_size, h->rebalance_goal) * 100.0 / h->rebalance_size >= 5.0) relevant = true; } diff --git a/src/home/homed-manager.h b/src/home/homed-manager.h index fe1041e01e5fc..a399c31bf8fb1 100644 --- a/src/home/homed-manager.h +++ b/src/home/homed-manager.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include - #include "homed-forward.h" #include "user-record.h" diff --git a/src/home/homed-varlink.c b/src/home/homed-varlink.c index fb23dc9cde290..1bf4c795695ba 100644 --- a/src/home/homed-varlink.c +++ b/src/home/homed-varlink.c @@ -14,7 +14,6 @@ #include "user-record.h" #include "user-record-util.h" #include "user-util.h" -#include "varlink-util.h" typedef struct LookupParameters { const char *user_name; @@ -104,7 +103,7 @@ int vl_method_get_user_record(sd_varlink *link, sd_json_variant *parameters, sd_ if (!streq_ptr(p.service, m->userdb_service)) return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; @@ -212,7 +211,7 @@ int vl_method_get_group_record(sd_varlink *link, sd_json_variant *parameters, sd if (!streq_ptr(p.service, m->userdb_service)) return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; @@ -277,7 +276,7 @@ int vl_method_get_memberships(sd_varlink *link, sd_json_variant *parameters, sd_ if (!streq_ptr(p.service, m->userdb_service)) return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; diff --git a/src/home/homework-fscrypt.c b/src/home/homework-fscrypt.c index 1c700999825ba..6f8ae4b8c9c1c 100644 --- a/src/home/homework-fscrypt.c +++ b/src/home/homework-fscrypt.c @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include #include #include #include @@ -10,6 +8,7 @@ #include #include "alloc-util.h" +#include "crypto-util.h" #include "errno-util.h" #include "fd-util.h" #include "format-util.h" @@ -25,7 +24,6 @@ #include "mkdir.h" #include "mount-util.h" #include "nulstr-util.h" -#include "openssl-util.h" #include "parse-util.h" #include "process-util.h" #include "random-util.h" @@ -180,8 +178,8 @@ static void calculate_key_descriptor( /* Derive the key descriptor from the volume key via double SHA512, in order to be compatible with e4crypt */ - assert_se(SHA512(key, key_size, hashed) == hashed); - assert_se(SHA512(hashed, sizeof(hashed), hashed2) == hashed2); + assert_se(sym_SHA512(key, key_size, hashed) == hashed); + assert_se(sym_SHA512(hashed, sizeof(hashed), hashed2) == hashed2); assert_cc(sizeof(hashed2) >= FS_KEY_DESCRIPTOR_SIZE); @@ -211,6 +209,10 @@ static int fscrypt_slot_try_one( assert(encrypted_size > 0); assert(match_key_descriptor); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + /* Our construction is like this: * * 1. In each key slot we store a salt value plus the encrypted volume key @@ -226,37 +228,37 @@ static int fscrypt_slot_try_one( CLEANUP_ERASE(derived); - if (PKCS5_PBKDF2_HMAC( + if (sym_PKCS5_PBKDF2_HMAC( password, strlen(password), salt, salt_size, - 0xFFFF, EVP_sha512(), + 0xFFFF, sym_EVP_sha512(), sizeof(derived), derived) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed."); - context = EVP_CIPHER_CTX_new(); + context = sym_EVP_CIPHER_CTX_new(); if (!context) return log_oom(); /* We use AES256 in counter mode */ - assert_se(cc = EVP_aes_256_ctr()); + assert_se(cc = sym_EVP_aes_256_ctr()); /* We only use the first half of the derived key */ - assert(sizeof(derived) >= (size_t) EVP_CIPHER_key_length(cc)); + assert(sizeof(derived) >= (size_t) sym_EVP_CIPHER_get_key_length(cc)); - if (EVP_DecryptInit_ex(context, cc, NULL, derived, NULL) != 1) + if (sym_EVP_DecryptInit_ex(context, cc, NULL, derived, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context."); - decrypted_size = encrypted_size + EVP_CIPHER_key_length(cc) * 2; + decrypted_size = encrypted_size + sym_EVP_CIPHER_get_key_length(cc) * 2; decrypted = malloc(decrypted_size); if (!decrypted) return log_oom(); - if (EVP_DecryptUpdate(context, (uint8_t*) decrypted, &decrypted_size_out1, encrypted, encrypted_size) != 1) + if (sym_EVP_DecryptUpdate(context, (uint8_t*) decrypted, &decrypted_size_out1, encrypted, encrypted_size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt volume key."); assert((size_t) decrypted_size_out1 <= decrypted_size); - if (EVP_DecryptFinal_ex(context, (uint8_t*) decrypted_size + decrypted_size_out1, &decrypted_size_out2) != 1) + if (sym_EVP_DecryptFinal_ex(context, (uint8_t*) decrypted + decrypted_size_out1, &decrypted_size_out2) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish decryption of volume key."); assert((size_t) decrypted_size_out1 + (size_t) decrypted_size_out2 < decrypted_size); @@ -484,43 +486,47 @@ static int fscrypt_slot_set( size_t encrypted_size; ssize_t ss; + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + r = crypto_random_bytes(salt, sizeof(salt)); if (r < 0) return log_error_errno(r, "Failed to generate salt: %m"); CLEANUP_ERASE(derived); - if (PKCS5_PBKDF2_HMAC( + if (sym_PKCS5_PBKDF2_HMAC( password, strlen(password), salt, sizeof(salt), - 0xFFFF, EVP_sha512(), + 0xFFFF, sym_EVP_sha512(), sizeof(derived), derived) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed"); - context = EVP_CIPHER_CTX_new(); + context = sym_EVP_CIPHER_CTX_new(); if (!context) return log_oom(); /* We use AES256 in counter mode */ - cc = EVP_aes_256_ctr(); + cc = sym_EVP_aes_256_ctr(); /* We only use the first half of the derived key */ - assert(sizeof(derived) >= (size_t) EVP_CIPHER_key_length(cc)); + assert(sizeof(derived) >= (size_t) sym_EVP_CIPHER_get_key_length(cc)); - if (EVP_EncryptInit_ex(context, cc, NULL, derived, NULL) != 1) + if (sym_EVP_EncryptInit_ex(context, cc, NULL, derived, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context."); - encrypted_size = volume_key_size + EVP_CIPHER_key_length(cc) * 2; + encrypted_size = volume_key_size + sym_EVP_CIPHER_get_key_length(cc) * 2; encrypted = malloc(encrypted_size); if (!encrypted) return log_oom(); - if (EVP_EncryptUpdate(context, (uint8_t*) encrypted, &encrypted_size_out1, volume_key, volume_key_size) != 1) + if (sym_EVP_EncryptUpdate(context, (uint8_t*) encrypted, &encrypted_size_out1, volume_key, volume_key_size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt volume key."); assert((size_t) encrypted_size_out1 <= encrypted_size); - if (EVP_EncryptFinal_ex(context, (uint8_t*) encrypted_size + encrypted_size_out1, &encrypted_size_out2) != 1) + if (sym_EVP_EncryptFinal_ex(context, (uint8_t*) encrypted + encrypted_size_out1, &encrypted_size_out2) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish encryption of volume key."); assert((size_t) encrypted_size_out1 + (size_t) encrypted_size_out2 < encrypted_size); @@ -569,6 +575,10 @@ int home_create_fscrypt( assert(setup); assert(ret_home); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + assert_se(ip = user_record_image_path(h)); r = tempfn_random(ip, "homework", &d); diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 9f7af06a4e780..e85153d61dbc2 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -21,6 +21,7 @@ #include "blockdev-util.h" #include "btrfs-util.h" #include "chattr-util.h" +#include "crypto-util.h" #include "cryptsetup-util.h" #include "device-util.h" #include "devnum-util.h" @@ -48,7 +49,6 @@ #include "memory-util.h" #include "mkdir.h" #include "mkfs-util.h" -#include "openssl-util.h" #include "parse-util.h" #include "path-util.h" #include "pidref.h" @@ -146,7 +146,7 @@ static int probe_file_system_by_fd( assert(ret_fstype); assert(ret_uuid); - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) return r; @@ -202,16 +202,14 @@ static int probe_file_system_by_path(const char *path, char **ret_fstype, sd_id1 } static int block_get_size_by_fd(int fd, uint64_t *ret) { - struct stat st; + int r; assert(fd >= 0); assert(ret); - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; + r = fd_verify_block(fd); + if (r < 0) + return r; return blockdev_get_device_size(fd, ret); } @@ -413,7 +411,7 @@ static int luks_setup( key_serial_t *ret_key_serial) { _cleanup_(keyring_unlinkp) key_serial_t key_serial = -1; - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_(erase_and_freep) void *vk = NULL; sd_id128_t p; size_t vks; @@ -524,14 +522,14 @@ static int acquire_open_luks_device( HomeSetup *setup, bool graceful) { - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; int r; assert(h); assert(setup); assert(!setup->crypt_device); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -686,7 +684,7 @@ static int luks_validate( assert(ret_size); assert(sector_size > 0); - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) return r; @@ -805,6 +803,11 @@ static int crypt_device_to_evp_cipher(struct crypt_device *cd, const EVP_CIPHER int r; assert(cd); + assert(ret); + + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; /* Let's find the right OpenSSL EVP_CIPHER object that matches the encryption settings of the LUKS * device */ @@ -833,12 +836,12 @@ static int crypt_device_to_evp_cipher(struct crypt_device *cd, const EVP_CIPHER if (asprintf(&cipher_name, "%s-%zu-%s", cipher, key_bits, cipher_mode) < 0) return log_oom(); - cc = EVP_get_cipherbyname(cipher_name); + cc = sym_EVP_get_cipherbyname(cipher_name); if (!cc) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Selected cipher mode '%s' not supported, can't encrypt JSON record.", cipher_name); /* Verify that our key length calculations match what OpenSSL thinks */ - r = EVP_CIPHER_key_length(cc); + r = sym_EVP_CIPHER_get_key_length(cc); if (r < 0 || (uint64_t) r != key_size) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Key size of selected cipher doesn't meet our expectations."); @@ -857,6 +860,7 @@ static int luks_validate_home_record( assert(cd); assert(h); + assert(ret_luks_home_record); for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *rr = NULL; @@ -887,7 +891,7 @@ static int luks_validate_home_record( return log_error_errno(r, "Failed to read LUKS token %i: %m", token); unsigned line = 0, column = 0; - r = sd_json_parse(text, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse(text, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse LUKS token JSON data %u:%u: %m", line, column); @@ -909,27 +913,27 @@ static int luks_validate_home_record( r = crypt_device_to_evp_cipher(cd, &cc); if (r < 0) return r; - if (iv_size > INT_MAX || EVP_CIPHER_iv_length(cc) != (int) iv_size) + if (iv_size > INT_MAX || sym_EVP_CIPHER_get_iv_length(cc) != (int) iv_size) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "IV size doesn't match."); - context = EVP_CIPHER_CTX_new(); + context = sym_EVP_CIPHER_CTX_new(); if (!context) return log_oom(); - if (EVP_DecryptInit_ex(context, cc, NULL, volume_key, iv) != 1) + if (sym_EVP_DecryptInit_ex(context, cc, NULL, volume_key, iv) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context."); - decrypted_size = encrypted_size + EVP_CIPHER_key_length(cc) * 2; + decrypted_size = encrypted_size + sym_EVP_CIPHER_get_key_length(cc) * 2; decrypted = new(char, decrypted_size); if (!decrypted) return log_oom(); - if (EVP_DecryptUpdate(context, (uint8_t*) decrypted, &decrypted_size_out1, encrypted, encrypted_size) != 1) + if (sym_EVP_DecryptUpdate(context, (uint8_t*) decrypted, &decrypted_size_out1, encrypted, encrypted_size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt JSON record."); assert((size_t) decrypted_size_out1 <= decrypted_size); - if (EVP_DecryptFinal_ex(context, (uint8_t*) decrypted + decrypted_size_out1, &decrypted_size_out2) != 1) + if (sym_EVP_DecryptFinal_ex(context, (uint8_t*) decrypted + decrypted_size_out1, &decrypted_size_out2) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish decryption of JSON record."); assert((size_t) decrypted_size_out1 + (size_t) decrypted_size_out2 < decrypted_size); @@ -940,7 +944,7 @@ static int luks_validate_home_record( decrypted[decrypted_size] = 0; - r = sd_json_parse(decrypted, SD_JSON_PARSE_SENSITIVE, &rr, NULL, NULL); + r = sd_json_parse(decrypted, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &rr, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to parse decrypted JSON record, refusing."); @@ -990,8 +994,8 @@ static int format_luks_token_text( if (r < 0) return r; - key_size = EVP_CIPHER_key_length(cc); - iv_size = EVP_CIPHER_iv_length(cc); + key_size = sym_EVP_CIPHER_get_key_length(cc); + iv_size = sym_EVP_CIPHER_get_iv_length(cc); if (iv_size > 0) { iv = malloc(iv_size); @@ -1003,11 +1007,11 @@ static int format_luks_token_text( return log_error_errno(r, "Failed to generate IV: %m"); } - context = EVP_CIPHER_CTX_new(); + context = sym_EVP_CIPHER_CTX_new(); if (!context) return log_oom(); - if (EVP_EncryptInit_ex(context, cc, NULL, volume_key, iv) != 1) + if (sym_EVP_EncryptInit_ex(context, cc, NULL, volume_key, iv) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context."); r = sd_json_variant_format(hr->json, 0, &text); @@ -1021,12 +1025,12 @@ static int format_luks_token_text( if (!encrypted) return log_oom(); - if (EVP_EncryptUpdate(context, encrypted, &encrypted_size_out1, (uint8_t*) text, text_length) != 1) + if (sym_EVP_EncryptUpdate(context, encrypted, &encrypted_size_out1, (uint8_t*) text, text_length) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt JSON record."); assert((size_t) encrypted_size_out1 <= encrypted_size); - if (EVP_EncryptFinal_ex(context, (uint8_t*) encrypted + encrypted_size_out1, &encrypted_size_out2) != 1) + if (sym_EVP_EncryptFinal_ex(context, (uint8_t*) encrypted + encrypted_size_out1, &encrypted_size_out2) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish encryption of JSON record."); assert((size_t) encrypted_size_out1 + (size_t) encrypted_size_out2 <= encrypted_size); @@ -1250,10 +1254,9 @@ static int open_image_file( if (fstat(image_fd, &st) < 0) return log_error_errno(errno, "Failed to fstat() image file: %m"); - if (!S_ISREG(st.st_mode) && !S_ISBLK(st.st_mode)) - return log_error_errno( - S_ISDIR(st.st_mode) ? SYNTHETIC_ERRNO(EISDIR) : SYNTHETIC_ERRNO(EBADFD), - "Image file %s is not a regular file or block device.", ip); + r = stat_verify_regular_or_block(&st); + if (r < 0) + return log_error_errno(r, "Image file '%s' is not a regular file or block device.", ip); /* Locking block devices doesn't really make sense, as this might interfere with * udev's workings, and these locks aren't network propagated anyway, hence not what @@ -1290,7 +1293,7 @@ int home_setup_luks( assert(setup); assert(user_record_storage(h) == USER_LUKS); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -1591,7 +1594,7 @@ int home_activate_luks( assert(setup); assert(ret_home); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -1782,7 +1785,7 @@ static int luks_format( struct crypt_device **ret) { _cleanup_(user_record_unrefp) UserRecord *reduced = NULL; - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_(erase_and_freep) void *volume_key = NULL; struct crypt_pbkdf_type good_pbkdf, minimal_pbkdf; _cleanup_free_ char *text = NULL; @@ -1918,12 +1921,13 @@ static int make_partition_table( assert(label); assert(ret_offset); assert(ret_size); + assert(ret_disk_uuid); - t = fdisk_new_parttype(); + t = sym_fdisk_new_parttype(); if (!t) return log_oom(); - r = fdisk_parttype_set_typestr(t, SD_GPT_USER_HOME_STR); + r = sym_fdisk_parttype_set_typestr(t, SD_GPT_USER_HOME_STR); if (r < 0) return log_error_errno(r, "Failed to initialize partition type: %m"); @@ -1931,27 +1935,27 @@ static int make_partition_table( if (r < 0) return log_error_errno(r, "Failed to open device: %m"); - r = fdisk_create_disklabel(c, "gpt"); + r = sym_fdisk_create_disklabel(c, "gpt"); if (r < 0) return log_error_errno(r, "Failed to create GPT disk label: %m"); - p = fdisk_new_partition(); + p = sym_fdisk_new_partition(); if (!p) return log_oom(); - r = fdisk_partition_set_type(p, t); + r = sym_fdisk_partition_set_type(p, t); if (r < 0) return log_error_errno(r, "Failed to set partition type: %m"); - r = fdisk_partition_partno_follow_default(p, 1); + r = sym_fdisk_partition_partno_follow_default(p, 1); if (r < 0) return log_error_errno(r, "Failed to place partition at first free partition index: %m"); /* Use same sector size as the fdisk context when converting to bytes */ - fdisk_sector_size = fdisk_get_sector_size(c); + fdisk_sector_size = sym_fdisk_get_sector_size(c); assert(fdisk_sector_size > 0); - first_lba = fdisk_get_first_lba(c); /* Boundary where usable space starts */ + first_lba = sym_fdisk_get_first_lba(c); /* Boundary where usable space starts */ assert(first_lba <= UINT64_MAX / fdisk_sector_size); start = DISK_SIZE_ROUND_UP(first_lba * fdisk_sector_size); @@ -1960,38 +1964,38 @@ static int make_partition_table( if (start == UINT64_MAX) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Overflow while rounding up start LBA."); - last_lba = fdisk_get_last_lba(c); /* One sector before boundary where usable space ends */ + last_lba = sym_fdisk_get_last_lba(c); /* One sector before boundary where usable space ends */ assert(last_lba < UINT64_MAX / fdisk_sector_size); end = DISK_SIZE_ROUND_DOWN((last_lba + 1) * fdisk_sector_size); if (end <= start) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Resulting partition size zero or negative."); - r = fdisk_partition_set_start(p, start / fdisk_sector_size); + r = sym_fdisk_partition_set_start(p, start / fdisk_sector_size); if (r < 0) return log_error_errno(r, "Failed to place partition at offset %" PRIu64 ": %m", start); - r = fdisk_partition_set_size(p, (end - start) / fdisk_sector_size); + r = sym_fdisk_partition_set_size(p, (end - start) / fdisk_sector_size); if (r < 0) return log_error_errno(r, "Failed to end partition at offset %" PRIu64 ": %m", end); - r = fdisk_partition_set_name(p, label); + r = sym_fdisk_partition_set_name(p, label); if (r < 0) return log_error_errno(r, "Failed to set partition name: %m"); - r = fdisk_partition_set_uuid(p, SD_ID128_TO_UUID_STRING(uuid)); + r = sym_fdisk_partition_set_uuid(p, SD_ID128_TO_UUID_STRING(uuid)); if (r < 0) return log_error_errno(r, "Failed to set partition UUID: %m"); - r = fdisk_add_partition(c, p, NULL); + r = sym_fdisk_add_partition(c, p, NULL); if (r < 0) return log_error_errno(r, "Failed to add partition: %m"); - r = fdisk_write_disklabel(c); + r = sym_fdisk_write_disklabel(c); if (r < 0) return log_error_errno(r, "Failed to write disk label: %m"); - r = fdisk_get_disklabel_id(c, &disk_uuid_as_string); + r = sym_fdisk_get_disklabel_id(c, &disk_uuid_as_string); if (r < 0) return log_error_errno(r, "Failed to determine disk label UUID: %m"); @@ -1999,17 +2003,17 @@ static int make_partition_table( if (r < 0) return log_error_errno(r, "Failed to parse disk label UUID: %m"); - r = fdisk_get_partition(c, 0, &q); + r = sym_fdisk_get_partition(c, 0, &q); if (r < 0) return log_error_errno(r, "Failed to read created partition metadata: %m"); - assert(fdisk_partition_has_start(q)); - offset = fdisk_partition_get_start(q); + assert(sym_fdisk_partition_has_start(q)); + offset = sym_fdisk_partition_get_start(q); if (offset > UINT64_MAX / fdisk_sector_size) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Partition offset too large."); - assert(fdisk_partition_has_size(q)); - size = fdisk_partition_get_size(q); + assert(sym_fdisk_partition_has_size(q)); + size = sym_fdisk_partition_get_size(q); if (size > UINT64_MAX / fdisk_sector_size) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Partition size too large."); @@ -2207,7 +2211,11 @@ int home_create_luks( assert(setup->image_fd < 0); assert(ret_home); - r = dlopen_cryptsetup(); + r = dlopen_fdisk(LOG_DEBUG); + if (r < 0) + return r; + + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -2276,8 +2284,9 @@ int home_create_luks( if (setup->image_fd < 0) return setup->image_fd; - if (!S_ISBLK(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "Device is not a block device, refusing."); + r = stat_verify_block(&st); + if (r < 0) + return log_error_errno(r, "Device is not a block device, refusing."); if (asprintf(&sysfs, "/sys/dev/block/" DEVNUM_FORMAT_STR "/partition", DEVNUM_FORMAT_VAL(st.st_rdev)) < 0) return log_oom(); @@ -2786,6 +2795,7 @@ static int prepare_resize_partition( assert(fd >= 0); assert(ret_disk_uuid); assert(ret_table); + assert(ret_partition); assert((partition_offset & 511) == 0); assert((old_partition_size & 511) == 0); @@ -2804,10 +2814,10 @@ static int prepare_resize_partition( if (r < 0) return log_error_errno(r, "Failed to open device: %m"); - if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) + if (!sym_fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) return log_error_errno(SYNTHETIC_ERRNO(ENOMEDIUM), "Disk has no GPT partition table."); - r = fdisk_get_disklabel_id(c, &disk_uuid_as_string); + r = sym_fdisk_get_disklabel_id(c, &disk_uuid_as_string); if (r < 0) return log_error_errno(r, "Failed to acquire disk UUID: %m"); @@ -2815,34 +2825,36 @@ static int prepare_resize_partition( if (r < 0) return log_error_errno(r, "Failed to parse disk UUID: %m"); - r = fdisk_get_partitions(c, &t); + r = sym_fdisk_get_partitions(c, &t); if (r < 0) return log_error_errno(r, "Failed to acquire partition table: %m"); - n_partitions = fdisk_table_get_nents(t); + n_partitions = sym_fdisk_table_get_nents(t); for (size_t i = 0; i < n_partitions; i++) { struct fdisk_partition *p; uint64_t fdisk_sector_size; - p = fdisk_table_get_partition(t, i); + p = sym_fdisk_table_get_partition(t, i); if (!p) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); - if (fdisk_partition_is_used(p) <= 0) + if (sym_fdisk_partition_is_used(p) <= 0) continue; - if (fdisk_partition_has_start(p) <= 0 || fdisk_partition_has_size(p) <= 0 || fdisk_partition_has_end(p) <= 0) + if (sym_fdisk_partition_has_start(p) <= 0 || + sym_fdisk_partition_has_size(p) <= 0 || + sym_fdisk_partition_has_end(p) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found partition without a size."); - fdisk_sector_size = fdisk_get_sector_size(c); + fdisk_sector_size = sym_fdisk_get_sector_size(c); assert(fdisk_sector_size > 0); - if (fdisk_partition_get_start(p) == partition_offset / fdisk_sector_size && - fdisk_partition_get_size(p) == old_partition_size / fdisk_sector_size) { + if (sym_fdisk_partition_get_start(p) == partition_offset / fdisk_sector_size && + sym_fdisk_partition_get_size(p) == old_partition_size / fdisk_sector_size) { if (found) return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Partition found twice, refusing."); found = p; - } else if (fdisk_partition_get_end(p) > partition_offset / fdisk_sector_size) + } else if (sym_fdisk_partition_get_end(p) > partition_offset / fdisk_sector_size) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't extend, not last partition in image."); } @@ -2874,12 +2886,12 @@ static int get_maximum_partition_size( return log_error_errno(r, "Failed to create fdisk context: %m"); /* Get the probed sector size by fdisk */ - fdisk_sector_size = fdisk_get_sector_size(c); - start_lba = fdisk_partition_get_start(p); + fdisk_sector_size = sym_fdisk_get_sector_size(c); + start_lba = sym_fdisk_partition_get_start(p); assert(start_lba <= UINT64_MAX / fdisk_sector_size); start = start_lba * fdisk_sector_size; - last_lba = fdisk_get_last_lba(c); /* One sector before boundary where usable space ends */ + last_lba = sym_fdisk_get_last_lba(c); /* One sector before boundary where usable space ends */ assert(last_lba < UINT64_MAX / fdisk_sector_size); end = DISK_SIZE_ROUND_DOWN((last_lba + 1) * fdisk_sector_size); @@ -2896,14 +2908,14 @@ static int ask_cb(struct fdisk_context *c, struct fdisk_ask *ask, void *userdata assert(c); - switch (fdisk_ask_get_type(ask)) { + switch (sym_fdisk_ask_get_type(ask)) { case FDISK_ASKTYPE_STRING: result = new(char, 37); if (!result) return log_oom(); - fdisk_ask_string_set_result(ask, sd_id128_to_uuid_string(*(sd_id128_t*) userdata, result)); + sym_fdisk_ask_string_set_result(ask, sd_id128_to_uuid_string(*(sd_id128_t*) userdata, result)); break; default: @@ -2941,31 +2953,31 @@ static int apply_resize_partition( return log_error_errno(r, "Failed to open device: %m"); /* Before writing our partition patch the final size in */ - r = fdisk_partition_size_explicit(p, 1); + r = sym_fdisk_partition_size_explicit(p, 1); if (r < 0) return log_error_errno(r, "Failed to enable explicit partition size: %m"); - r = fdisk_partition_set_size(p, new_partition_size / ssz); + r = sym_fdisk_partition_set_size(p, new_partition_size / ssz); if (r < 0) return log_error_errno(r, "Failed to change partition size: %m"); - r = fdisk_create_disklabel(c, "gpt"); + r = sym_fdisk_create_disklabel(c, "gpt"); if (r < 0) return log_error_errno(r, "Failed to create GPT disk label: %m"); - r = fdisk_apply_table(c, t); + r = sym_fdisk_apply_table(c, t); if (r < 0) return log_error_errno(r, "Failed to apply partition table: %m"); - r = fdisk_set_ask(c, ask_cb, &disk_uuids); + r = sym_fdisk_set_ask(c, ask_cb, &disk_uuids); if (r < 0) return log_error_errno(r, "Failed to set libfdisk query function: %m"); - r = fdisk_set_disklabel_id(c); + r = sym_fdisk_set_disklabel_id(c); if (r < 0) return log_error_errno(r, "Failed to change disklabel ID: %m"); - r = fdisk_write_disklabel(c); + r = sym_fdisk_write_disklabel(c); if (r < 0) return log_error_errno(r, "Failed to write disk label: %m"); @@ -3238,7 +3250,11 @@ int home_resize_luks( assert(user_record_storage(h) == USER_LUKS); assert(setup); - r = dlopen_cryptsetup(); + r = dlopen_fdisk(LOG_DEBUG); + if (r < 0) + return r; + + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -3696,7 +3712,7 @@ int home_passwd_luks( assert(user_record_storage(h) == USER_LUKS); assert(setup); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; diff --git a/src/home/homework.c b/src/home/homework.c index e796f125fb56e..d63d481447382 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -571,7 +571,7 @@ static int read_identity_file(int root_fd, sd_json_variant **ret) { return log_oom(); unsigned line = 0, column = 0; - r = sd_json_parse_file(identity_file, ".identity", SD_JSON_PARSE_SENSITIVE, ret, &line, &column); + r = sd_json_parse_file(identity_file, ".identity", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, ret, &line, &column); if (r < 0) return log_error_errno(r, "[.identity:%u:%u] Failed to parse JSON data: %m", line, column); @@ -915,6 +915,7 @@ static int home_activate(UserRecord *h, UserRecord **ret_home) { int r; assert(h); + assert(ret_home); if (!h->user_name) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks user name, refusing."); @@ -1330,7 +1331,7 @@ static int determine_default_storage(UserStorage *ret) { if (r < 0) log_warning_errno(r, "Failed to determine if %s is encrypted, ignoring: %m", get_home_root()); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) log_info("Not using '%s' storage, since libcryptsetup could not be loaded.", user_storage_to_string(USER_LUKS)); else { @@ -2004,8 +2005,6 @@ static int run(int argc, char *argv[]) { log_setup(); - cryptsetup_enable_logging(NULL); - umask(0022); if (argc < 2 || argc > 3) @@ -2025,7 +2024,7 @@ static int run(int argc, char *argv[]) { } unsigned line = 0, column = 0; - r = sd_json_parse_file(json_file, json_path, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse_file(json_file, json_path, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON data: %m", json_path, line, column); diff --git a/src/home/meson.build b/src/home/meson.build index 1efee1619ef9c..53c5675c83f88 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -65,7 +65,7 @@ executables += [ 'extract' : systemd_homed_extract_sources, 'dependencies' : [ libm, - libopenssl, + libopenssl_cflags, threads, ], }, @@ -73,14 +73,10 @@ executables += [ 'name' : 'systemd-homework', 'sources' : systemd_homework_sources, 'objects' : ['systemd-homed'], - 'link_with' : [ - libshared, - libshared_fdisk - ], 'dependencies' : [ libblkid_cflags, - libfdisk, - libopenssl, + libfdisk_cflags, + libopenssl_cflags, libp11kit_cflags, threads, ], @@ -92,7 +88,7 @@ executables += [ 'objects' : ['systemd-homed'], 'dependencies' : [ libdl, - libopenssl, + libopenssl_cflags, libp11kit_cflags, threads, ], diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index c58a3433760be..1de66b4c0325c 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -194,7 +194,7 @@ static int acquire_user_record( fresh_data = true; } - r = sd_json_parse(json, /* flags= */ 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return pam_syslog_errno(pamh, LOG_ERR, r, "Failed to parse JSON user record: %m"); @@ -789,7 +789,7 @@ _public_ PAM_EXTERN int pam_sm_authenticate( bool debug = false; int r; - r = dlopen_libpam(); + r = dlopen_libpam(LOG_DEBUG); if (r < 0) return PAM_SERVICE_ERR; @@ -854,7 +854,7 @@ _public_ PAM_EXTERN int pam_sm_open_session( bool debug = false; int r; - r = dlopen_libpam(); + r = dlopen_libpam(LOG_DEBUG); if (r < 0) return PAM_SERVICE_ERR; @@ -972,7 +972,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( usec_t t; int r; - r = dlopen_libpam(); + r = dlopen_libpam(LOG_DEBUG); if (r < 0) return PAM_SERVICE_ERR; @@ -1091,7 +1091,7 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( bool debug = false; int r; - r = dlopen_libpam(); + r = dlopen_libpam(LOG_DEBUG); if (r < 0) return PAM_SERVICE_ERR; diff --git a/src/home/user-record-sign.c b/src/home/user-record-sign.c index 7a80ef1e7ab91..6bc97af27a675 100644 --- a/src/home/user-record-sign.c +++ b/src/home/user-record-sign.c @@ -1,9 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" +#include "crypto-util.h" #include "json-util.h" #include "log.h" -#include "openssl-util.h" #include "user-record-sign.h" #include "user-record.h" @@ -118,14 +118,14 @@ int user_record_verify(UserRecord *ur, EVP_PKEY *public_key) { if (r < 0) return r; - md_ctx = EVP_MD_CTX_new(); + md_ctx = sym_EVP_MD_CTX_new(); if (!md_ctx) return -ENOMEM; - if (EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL, public_key) <= 0) + if (sym_EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL, public_key) <= 0) return -EIO; - if (EVP_DigestVerify(md_ctx, signature, signature_size, (uint8_t*) text, strlen(text)) <= 0) { + if (sym_EVP_DigestVerify(md_ctx, signature, signature_size, (uint8_t*) text, strlen(text)) <= 0) { n_bad++; continue; } diff --git a/src/home/user-record-sign.h b/src/home/user-record-sign.h index 3007d00b01d01..673c7b2b372aa 100644 --- a/src/home/user-record-sign.h +++ b/src/home/user-record-sign.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include - #include "shared-forward.h" int user_record_sign(UserRecord *ur, EVP_PKEY *private_key, UserRecord **ret); diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c index b15e8f141bc72..b27d993922a60 100644 --- a/src/home/user-record-util.c +++ b/src/home/user-record-util.c @@ -159,7 +159,7 @@ int group_record_synthesize(GroupRecord *g, UserRecord *h) { SD_JSON_BUILD_PAIR_STRING("description", description), SD_JSON_BUILD_PAIR("binding", SD_JSON_BUILD_OBJECT( SD_JSON_BUILD_PAIR(SD_ID128_TO_STRING(mid), SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(user_record_gid(h))))))), + SD_JSON_BUILD_PAIR_UNSIGNED("gid", user_record_gid(h)))))), SD_JSON_BUILD_PAIR_CONDITION(h->disposition >= 0, "disposition", SD_JSON_BUILD_STRING(user_disposition_to_string(user_record_disposition(h)))), SD_JSON_BUILD_PAIR("status", SD_JSON_BUILD_OBJECT( SD_JSON_BUILD_PAIR(SD_ID128_TO_STRING(mid), SD_JSON_BUILD_OBJECT( @@ -197,6 +197,7 @@ int user_record_reconcile( assert(host); assert(embedded); + assert(ret); /* Make sure both records are initialized */ if (!host->json || !embedded->json) diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index ae1af41425703..67e82c1e7c028 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -23,6 +22,7 @@ #include "hostname-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "polkit-agent.h" #include "pretty-print.h" @@ -375,11 +375,7 @@ static int print_status_info(StatusInfo *i) { } } - r = table_print(table, NULL); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } static int get_one_name(sd_bus *bus, const char* attr, char **ret) { @@ -549,7 +545,8 @@ static int get_hostname_based_on_flag(sd_bus *bus) { return get_one_name(bus, attr, NULL); } -static int show_status(int argc, char **argv, void *userdata) { +VERB(verb_show_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, "Show current hostname settings"); +static int verb_show_status(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = userdata; int r; @@ -567,7 +564,7 @@ static int show_status(int argc, char **argv, void *userdata) { if (r < 0) return bus_log_parse_error(r); - r = sd_json_parse(text, 0, &v, NULL, NULL); + r = sd_json_parse(text, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return log_error_errno(r, "Failed to parse JSON: %m"); @@ -601,7 +598,7 @@ static int set_simple_string(sd_bus *bus, const char *target, const char *method return set_simple_string_internal(bus, NULL, target, method, value); } -static int set_hostname(int argc, char **argv, void *userdata) { +static int verb_set_hostname(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *h = NULL; const char *hostname = argv[1]; sd_bus *bus = userdata; @@ -687,191 +684,149 @@ static int set_hostname(int argc, char **argv, void *userdata) { return ret; } -static int get_or_set_hostname(int argc, char **argv, void *userdata) { +VERB(verb_get_or_set_hostname, "hostname", "[NAME]", VERB_ANY, 2, 0, "Get/set system hostname"); +VERB(verb_get_or_set_hostname, "set-hostname", "NAME", 2, 2, 0, NULL); /* obsolete */ +static int verb_get_or_set_hostname(int argc, char *argv[], uintptr_t data, void *userdata) { return argc == 1 ? get_hostname_based_on_flag(userdata) : - set_hostname(argc, argv, userdata); + verb_set_hostname(argc, argv, data, userdata); } -static int get_or_set_icon_name(int argc, char **argv, void *userdata) { +VERB(verb_get_or_set_icon_name, "icon-name", "[NAME]", VERB_ANY, 2, 0, "Get/set icon name for host"); +VERB(verb_get_or_set_icon_name, "set-icon-name", "NAME", 2, 2, 0, NULL); /* obsolete */ +static int verb_get_or_set_icon_name(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "IconName", NULL) : set_simple_string(userdata, "icon", "SetIconName", argv[1]); } -static int get_or_set_chassis(int argc, char **argv, void *userdata) { +VERB(verb_get_or_set_chassis, "chassis", "[NAME]", VERB_ANY, 2, 0, "Get/set chassis type for host"); +VERB(verb_get_or_set_chassis, "set-chassis", "NAME", 2, 2, 0, NULL); /* obsolete */ +static int verb_get_or_set_chassis(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "Chassis", NULL) : set_simple_string(userdata, "chassis", "SetChassis", argv[1]); } -static int get_or_set_deployment(int argc, char **argv, void *userdata) { +VERB(verb_get_or_set_deployment, "deployment", "[NAME]", VERB_ANY, 2, 0, "Get/set deployment environment for host"); +VERB(verb_get_or_set_deployment, "set-deployment", "NAME", 2, 2, 0, NULL); /* obsolete */ +static int verb_get_or_set_deployment(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "Deployment", NULL) : set_simple_string(userdata, "deployment", "SetDeployment", argv[1]); } -static int get_or_set_location(int argc, char **argv, void *userdata) { +VERB(verb_get_or_set_location, "location", "[NAME]", VERB_ANY, 2, 0, "Get/set location for host"); +VERB(verb_get_or_set_location, "set-location", "NAME", 2, 2, 0, NULL); /* obsolete */ +static int verb_get_or_set_location(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "Location", NULL) : set_simple_string(userdata, "location", "SetLocation", argv[1]); } static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("hostnamectl", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%2$sQuery or change system hostname.%3$s\n" - "\n%4$sCommands:%5$s\n" - " status Show current hostname settings\n" - " hostname [NAME] Get/set system hostname\n" - " icon-name [NAME] Get/set icon name for host\n" - " chassis [NAME] Get/set chassis type for host\n" - " deployment [NAME] Get/set deployment environment for host\n" - " location [NAME] Get/set location for host\n" - "\n%4$sOptions:%5$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-ask-password Do not prompt for password\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --transient Only set transient hostname\n" - " --static Only set static hostname\n" - " --pretty Only set pretty hostname\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " -j Same as --json=pretty on tty, --json=short otherwise\n" - "\nSee the %6$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options, verbs); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sQuery or change system hostname.%s\n" + "\nCommands:\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - ansi_underline(), - ansi_normal(), - link); + ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; - return 0; -} + printf("\nOptions:\n"); + r = table_print_or_warn(options); + if (r < 0) + return r; -static int verb_help(int argc, char **argv, void *userdata) { - return help(); + printf("\nSee the %s for details.\n", link); + return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_ASK_PASSWORD, - ARG_TRANSIENT, - ARG_STATIC, - ARG_PRETTY, - ARG_JSON, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "transient", no_argument, NULL, ARG_TRANSIENT }, - { "static", no_argument, NULL, ARG_STATIC }, - { "pretty", no_argument, NULL, ARG_PRETTY }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "json", required_argument, NULL, ARG_JSON }, - {} - }; +VERB_COMMON_HELP_HIDDEN(help); - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hH:M:j", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'H': + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case ARG_TRANSIENT: + OPTION_LONG("transient", NULL, "Only set transient hostname"): arg_transient = true; break; - case ARG_PRETTY: - arg_pretty = true; - break; - - case ARG_STATIC: + OPTION_LONG("static", NULL, "Only set static hostname"): arg_static = true; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION_LONG("pretty", NULL, "Only set pretty hostname"): + arg_pretty = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case 'j': + OPTION_COMMON_LOWERCASE_J: arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } -static int hostnamectl_main(sd_bus *bus, int argc, char *argv[]) { - - static const Verb verbs[] = { - { "status", VERB_ANY, 1, VERB_DEFAULT, show_status }, - { "hostname", VERB_ANY, 2, 0, get_or_set_hostname }, - { "set-hostname", 2, 2, 0, get_or_set_hostname }, /* obsolete */ - { "icon-name", VERB_ANY, 2, 0, get_or_set_icon_name }, - { "set-icon-name", 2, 2, 0, get_or_set_icon_name }, /* obsolete */ - { "chassis", VERB_ANY, 2, 0, get_or_set_chassis }, - { "set-chassis", 2, 2, 0, get_or_set_chassis }, /* obsolete */ - { "deployment", VERB_ANY, 2, 0, get_or_set_deployment }, - { "set-deployment", 2, 2, 0, get_or_set_deployment }, /* obsolete */ - { "location", VERB_ANY, 2, 0, get_or_set_location }, - { "set-location", 2, 2, 0, get_or_set_location }, /* obsolete */ - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it is created. */ - {} - }; - - return dispatch_verb(argc, argv, verbs, bus); -} - static int run(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + char **args = NULL; int r; setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -879,7 +834,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM); - return hostnamectl_main(bus, argc, argv); + return dispatch_verb_with_args(args, bus); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index a567221f8ea0b..60b48112449cf 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -49,7 +49,7 @@ #include "varlink-util.h" #include "virt.h" -#define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:") +#define VALID_DEPLOYMENT_CHARS (ALPHANUMERICAL "-.:") /* Properties we cache are indexed by an enum, to make invalidation easy and systematic (as we can iterate * through them all, and they are uniformly strings). */ @@ -398,6 +398,8 @@ static int get_hardware_sku(Context *c, char **ret) { _cleanup_free_ char *model = NULL, *sku = NULL; int r; + assert(ret); + r = get_dmi_property(c, "ID_SKU", &sku); if (r < 0) return r; @@ -419,6 +421,8 @@ static int get_hardware_version(Context *c, char **ret) { _cleanup_free_ char *version = NULL; int r; + assert(ret); + r = get_dmi_property(c, "ID_HARDWARE_VERSION", &version); if (r < 0) return r; @@ -822,6 +826,8 @@ static int context_update_kernel_hostname( } static void unset_statp(struct stat **p) { + assert(p); + if (!*p) return; @@ -1652,6 +1658,65 @@ static int method_get_hardware_serial(sd_bus_message *m, void *userdata, sd_bus_ return sd_bus_reply_method_return(m, "s", serial); } +static int method_get_machine_info(sd_bus_message *m, void *userdata, sd_bus_error *error) { + static const struct { + const char *name; + HostProperty prop; + } field_table[] = { + { "PRETTY_HOSTNAME", PROP_PRETTY_HOSTNAME }, + { "ICON_NAME", PROP_ICON_NAME }, + { "CHASSIS", PROP_CHASSIS }, + { "DEPLOYMENT", PROP_DEPLOYMENT }, + { "LOCATION", PROP_LOCATION }, + { "HARDWARE_VENDOR", PROP_HARDWARE_VENDOR }, + { "HARDWARE_MODEL", PROP_HARDWARE_MODEL }, + { "HARDWARE_SKU", PROP_HARDWARE_SKU }, + { "HARDWARE_VERSION", PROP_HARDWARE_VERSION }, + }; + + Context *c = ASSERT_PTR(userdata); + const char *field; + int r; + + assert(m); + + r = sd_bus_message_read(m, "s", &field); + if (r < 0) + return r; + + if (isempty(field)) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Field name must not be empty."); + + if (!env_name_is_valid(field)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid field name '%s'.", field); + + FOREACH_ELEMENT(e, field_table) + if (streq(field, e->name)) { + /* For fields that are also exposed as D-Bus properties, use the same Context cache as the + * property getters. Note that this returns the raw /etc/machine-info value only: property-level + * fallback logic (e.g. DMI/chassis-based synthesis) is not applied here. For custom/unknown + * fields, fall back to reading the file directly. */ + context_read_machine_info(c); + + if (isempty(c->data[e->prop])) + return sd_bus_error_setf(error, BUS_ERROR_FIELD_NOT_SET, "Field '%s' is not set or empty in /etc/machine-info.", field); + + return sd_bus_reply_method_return(m, "s", c->data[e->prop]); + } + + _cleanup_free_ char *value = NULL; + + r = parse_env_file(NULL, etc_machine_info(), + field, &value); + if (r < 0 && r != -ENOENT) + return sd_bus_error_set_errnof(error, r, "Failed to read /etc/machine-info: %m"); + + if (isempty(value)) + return sd_bus_error_setf(error, BUS_ERROR_FIELD_NOT_SET, "Field '%s' is not set or empty in /etc/machine-info.", field); + + return sd_bus_reply_method_return(m, "s", value); +} + static int build_describe_response(Context *c, bool privileged, sd_json_variant **ret) { _cleanup_free_ char *hn = NULL, *dhn = NULL, *in = NULL, *chassis = NULL, *vendor = NULL, *model = NULL, *serial = NULL, *firmware_version = NULL, @@ -1883,6 +1948,11 @@ static const sd_bus_vtable hostname_vtable[] = { SD_BUS_RESULT("s", json), method_describe, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("GetMachineInfo", + SD_BUS_ARGS("s", field), + SD_BUS_RESULT("s", value), + method_get_machine_info, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END, }; @@ -1900,7 +1970,7 @@ static int connect_bus(Context *c) { assert(c->event); assert(!c->bus); - r = sd_bus_default_system(&c->bus); + r = bus_open_system_watch_bind_with_description(&c->bus, "bus-api-hostname"); if (r < 0) return log_error_errno(r, "Failed to get system bus connection: %m"); diff --git a/src/hwdb/hwdb.c b/src/hwdb/hwdb.c index 6391b04133c5e..141387da4faf7 100644 --- a/src/hwdb/hwdb.c +++ b/src/hwdb/hwdb.c @@ -1,13 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" #include "build.h" +#include "format-table.h" #include "hwdb-util.h" #include "label-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pretty-print.h" #include "verbs.h" @@ -15,11 +15,15 @@ static const char *arg_hwdb_bin_dir = NULL; static const char *arg_root = NULL; static bool arg_strict = false; -static int verb_query(int argc, char *argv[], void *userdata) { +VERB(verb_query, "query", "MODALIAS", 2, 2, 0, + "Query database and print result"); +static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { return hwdb_query(argv[1], arg_root); } -static int verb_update(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_update, "update", + "Update the hwdb database"); +static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) { if (hwdb_bypass()) return 0; @@ -28,99 +32,93 @@ static int verb_update(int argc, char *argv[], void *userdata) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-hwdb", "8", &link); if (r < 0) return log_oom(); + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + printf("%s [OPTIONS...] COMMAND ...\n\n" "%sUpdate or query the hardware database.%s\n" - "\nCommands:\n" - " update Update the hwdb database\n" - " query MODALIAS Query database and print result\n" - "\nOptions:\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -s --strict When updating, return non-zero exit value on any parsing error\n" - " --usr Generate in " UDEVLIBEXECDIR " instead of /etc/udev\n" - " -r --root=PATH Alternative root path in the filesystem\n" - "\nSee the %s for details.\n", + "\n%sCommands:%s\n", program_invocation_short_name, ansi_highlight(), ansi_normal(), - link); + ansi_underline(), + ansi_normal()); - return 0; -} + r = table_print_or_warn(verbs); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_USR, - }; + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "usr", no_argument, NULL, ARG_USR }, - { "strict", no_argument, NULL, 's' }, - { "root", required_argument, NULL, 'r' }, - {} - }; + r = table_print_or_warn(options); + if (r < 0) + return r; - int c; + printf("\nSee the %s for details.\n", link); + return 0; +} +VERB_COMMON_HELP_HIDDEN(help); + +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); + + OptionParser state = { argc, argv }; + const char *arg; - while ((c = getopt_long(argc, argv, "sr:h", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_USR: - arg_hwdb_bin_dir = UDEVLIBEXECDIR; - break; - - case 's': + OPTION('s', "strict", NULL, + "When updating, return non-zero exit value on any parsing error"): arg_strict = true; break; - case 'r': - arg_root = optarg; + OPTION('r', "root", "PATH", "Alternative root path in the filesystem"): + arg_root = arg; break; - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION_LONG("usr", NULL, + "Generate in " UDEVLIBEXECDIR " instead of /etc/udev"): + arg_hwdb_bin_dir = UDEVLIBEXECDIR; + break; } + *ret_args = option_parser_get_args(&state); return 1; } -static int hwdb_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "update", 1, 1, 0, verb_update }, - { "query", 2, 2, 0, verb_query }, - {}, - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -128,7 +126,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return hwdb_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/id128/id128.c b/src/id128/id128.c index cdb93ac68fab2..1616bfea65bbe 100644 --- a/src/id128/id128.c +++ b/src/id128/id128.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" @@ -11,6 +10,7 @@ #include "id128-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "string-util.h" @@ -24,11 +24,13 @@ static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; -static int verb_new(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_new, "new", "Generate a new ID"); +static int verb_new(int argc, char *argv[], uintptr_t _data, void *userdata) { return id128_print_new(arg_mode); } -static int verb_machine_id(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_machine_id, "machine-id", "Print the ID of current machine"); +static int verb_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t id; int r; @@ -43,7 +45,8 @@ static int verb_machine_id(int argc, char **argv, void *userdata) { return id128_pretty_print(id, arg_mode); } -static int verb_boot_id(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_boot_id, "boot-id", "Print the ID of current boot"); +static int verb_boot_id(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t id; int r; @@ -58,7 +61,8 @@ static int verb_boot_id(int argc, char **argv, void *userdata) { return id128_pretty_print(id, arg_mode); } -static int verb_invocation_id(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_invocation_id, "invocation-id", "Print the ID of current invocation"); +static int verb_invocation_id(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t id; int r; @@ -73,7 +77,8 @@ static int verb_invocation_id(int argc, char **argv, void *userdata) { return id128_pretty_print(id, arg_mode); } -static int verb_var_uuid(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_var_uuid, "var-partition-uuid", "Print the UUID for the /var/ partition"); +static int verb_var_uuid(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t id; int r; @@ -130,7 +135,8 @@ static int show_one(Table **table, const char *name, sd_id128_t uuid, bool first arg_mode == ID128_PRINT_ID128 ? TABLE_ID128 : TABLE_UUID, uuid); } -static int verb_show(int argc, char **argv, void *userdata) { +VERB(verb_show, "show", "[NAME|UUID]", VERB_ANY, VERB_ANY, 0, "Print one or more UUIDs"); +static int verb_show(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; int r; @@ -186,158 +192,120 @@ static int verb_show(int argc, char **argv, void *userdata) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-id128", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + /* Make the 1st column same width in both tables */ + (void) table_sync_column_widths(0, options, verbs); + printf("%s [OPTIONS...] COMMAND\n\n" "%sGenerate and print 128-bit identifiers.%s\n" - "\nCommands:\n" - " new Generate a new ID\n" - " machine-id Print the ID of current machine\n" - " boot-id Print the ID of current boot\n" - " invocation-id Print the ID of current invocation\n" - " var-partition-uuid Print the UUID for the /var/ partition\n" - " show [NAME|UUID] Print one or more UUIDs\n" - " help Show this help\n" - "\nOptions:\n" - " -h --help Show this help\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --json=FORMAT Output inspection data in JSON (takes one of\n" - " pretty, short, off)\n" - " -j Equivalent to --json=pretty (on TTY) or\n" - " --json=short (otherwise)\n" - " -p --pretty Generate samples of program code\n" - " -P --value Only print the value\n" - " -a --app-specific=ID Generate app-specific IDs\n" - " -u --uuid Output in UUID format\n" - "\nSee the %s for details.\n", + "\nCommands:\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\nOptions:\n"); + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } -static int verb_help(int argc, char **argv, void *userdata) { - return help(); -} +VERB_COMMON_HELP(help); -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_JSON, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "json", required_argument, NULL, ARG_JSON }, - { "pretty", no_argument, NULL, 'p' }, - { "value", no_argument, NULL, 'P' }, - { "app-specific", required_argument, NULL, 'a' }, - { "uuid", no_argument, NULL, 'u' }, - {}, - }; - - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hpa:uPj", options, NULL)) >= 0) - switch (c) { + OptionParser state = { argc, argv }; + const char *arg; - case 'h': + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + switch (c) { + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case 'j': - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; - break; - - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_LONG("json", "FORMAT", + "Output inspection data in JSON (takes one of pretty, short, off)"): + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; + break; + OPTION_COMMON_LOWERCASE_J: + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; - case 'p': + + OPTION('p', "pretty", NULL, "Generate samples of program code"): arg_mode = ID128_PRINT_PRETTY; arg_value = false; break; - case 'P': + OPTION('P', "value", NULL, "Only print the value"): arg_value = true; if (arg_mode == ID128_PRINT_PRETTY) arg_mode = ID128_PRINT_ID128; break; - case 'a': - r = id128_from_string_nonzero(optarg, &arg_app); + OPTION('a', "app-specific", "ID", "Generate app-specific IDs"): + r = id128_from_string_nonzero(arg, &arg_app); if (r == -ENXIO) return log_error_errno(r, "Application ID cannot be all zeros."); if (r < 0) - return log_error_errno(r, "Failed to parse \"%s\" as application-ID: %m", optarg); + return log_error_errno(r, "Failed to parse \"%s\" as application ID: %m", arg); break; - case 'u': + OPTION('u', "uuid", NULL, "Output in UUID format"): arg_mode = ID128_PRINT_UUID; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } -static int id128_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "new", VERB_ANY, 1, 0, verb_new }, - { "machine-id", VERB_ANY, 1, 0, verb_machine_id }, - { "boot-id", VERB_ANY, 1, 0, verb_boot_id }, - { "invocation-id", VERB_ANY, 1, 0, verb_invocation_id }, - { "var-partition-uuid", VERB_ANY, 1, 0, verb_var_uuid }, - { "show", VERB_ANY, VERB_ANY, 0, verb_show }, - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; /* unnecessary initialization to appease gcc <= 13 */ + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return id128_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/imds/imds-generator.c b/src/imds/imds-generator.c new file mode 100644 index 0000000000000..42399783faac5 --- /dev/null +++ b/src/imds/imds-generator.c @@ -0,0 +1,192 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-hwdb.h" + +#include "dropin.h" +#include "fileio.h" +#include "generator.h" +#include "imds-util.h" +#include "log.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "special.h" +#include "string-util.h" +#include "virt.h" + +static int arg_enabled = -1; /* Whether we shall offer local IMDS APIs */ +static bool arg_import = true; /* Whether we shall import IMDS credentials, SSH keys, … into the local system */ +static ImdsNetworkMode arg_network_mode = IMDS_NETWORK_DEFAULT; + +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + int r; + + assert(key); + + if (proc_cmdline_key_streq(key, "systemd.imds")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = parse_tristate_full(value, "auto", &arg_enabled); + if (r < 0) + return log_warning_errno(r, "Failed to parse systemd.imds= value: %m"); + + } else if (proc_cmdline_key_streq(key, "systemd.imds.import")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = parse_boolean(value); + if (r < 0) + return log_warning_errno(r, "Failed to parse systemd.imds.import= value: %m"); + + arg_import = r; + } else if (proc_cmdline_key_streq(key, "systemd.imds.network")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + ImdsNetworkMode m = imds_network_mode_from_string(value); + if (m < 0) + return log_warning_errno(m, "Failed to parse systemd.imds.network= value: %m"); + + arg_network_mode = m; + } + + return 0; +} + +static int smbios_get_modalias(char **ret) { + int r; + + assert(ret); + + _cleanup_free_ char *modalias = NULL; + r = read_virtual_file("/sys/devices/virtual/dmi/id/modalias", SIZE_MAX, &modalias, /* ret_size= */ NULL); + if (r < 0) + return r; + + truncate_nl(modalias); + + /* To detect Azure we need to check the chassis asset tag. Unfortunately the kernel does not include + * it in the modalias string right now. Let's hence append it manually. This matches similar logic in + * rules.d/60-dmi-id.rules. */ + _cleanup_free_ char *cat = NULL; + r = read_virtual_file("/sys/devices/virtual/dmi/id/chassis_asset_tag", SIZE_MAX, &cat, /* ret_size= */ NULL); + if (r < 0) + log_debug_errno(r, "Failed to read chassis asset tag, ignoring: %m"); + else { + truncate_nl(cat); + + if (!string_has_cc(cat, /* ok= */ NULL) && !isempty(cat) && !strextend(&modalias, "cat", cat, ":")) + return -ENOMEM; + } + + log_debug("Constructed SMBIOS modalias string: %s", modalias); + *ret = TAKE_PTR(modalias); + return 0; +} + +static int smbios_query(void) { + int r; + + /* Let's check whether the DMI device's hwdb data suggests IMDS support is available. Note, we cannot + * ask udev for this, as we typically run long before udev. Hence we'll do the hwdb lookup via + * sd-hwdb directly. */ + + _cleanup_free_ char *modalias = NULL; + r = smbios_get_modalias(&modalias); + if (r == -ENOENT) { + log_debug("No DMI device found, assuming IMDS is not available."); + return false; + } + if (r < 0) + return log_error_errno(r, "Failed to read DMI modalias: %m"); + + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + r = sd_hwdb_new(&hwdb); + if (r < 0) + return log_error_errno(r, "Failed to open hwdb: %m"); + + r = sd_hwdb_seek(hwdb, modalias); + if (r < 0) + return log_error_errno(r, "Failed to seek in hwdb for '%s': %m", modalias); + + for (;;) { + const char *key, *value; + r = sd_hwdb_enumerate(hwdb, &key, &value); + if (r < 0) + return log_error_errno(r, "Failed to enumerate hwdb entry for '%s': %m", modalias); + if (r == 0) + break; + + if (streq(key, "IMDS_VENDOR")) + return true; + } + + log_debug("IMDS_VENDOR= property for DMI device not set, assuming IMDS is not available."); + return false; +} + +static int run(const char *dest, const char *dest_early, const char *dest_late) { + int r; + + r = proc_cmdline_parse(parse_proc_cmdline_item, /* userdata= */ NULL, PROC_CMDLINE_STRIP_RD_PREFIX); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + if (arg_enabled < 0) { + Virtualization v = detect_container(); + if (v < 0) + log_debug_errno(v, "Container detection failed, ignoring: %m"); + if (v > 0) { + log_debug("Running in a container, disabling IMDS logic."); + arg_enabled = false; + } else { + r = smbios_query(); + if (r < 0) + return r; + arg_enabled = r > 0; + } + } + + if (!arg_enabled) { + log_debug("IMDS not enabled, skipping generator."); + return 0; + } + + log_info("IMDS support enabled, pulling in IMDS units."); + + /* Enable IMDS early networking, so that we can actually reach the IMDS server. */ + if (arg_network_mode != IMDS_NETWORK_OFF) { + r = generator_add_symlink(dest_early, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imds-early-network.service"); + if (r < 0) + return log_error_errno(r, "Failed to hook in systemd-imds-early-network.service: %m"); + } + + /* Enable the IMDS service socket */ + r = generator_add_symlink(dest_early, SPECIAL_SOCKETS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imdsd.socket"); + if (r < 0) + return log_error_errno(r, "Failed to hook in systemd-imdsd.socket: %m"); + + /* We now know the SMBIOS device exists, hence it's safe now to order the IMDS service after it, so + * that it has all properties properly initialized. */ + r = write_drop_in( + dest_early, + "systemd-imdsd@.service", + 50, "dmi-id", + "# Automatically generated by systemd-imds-generator\n\n" + "[Unit]\n" + "Wants=sys-devices-virtual-dmi-id.device\n" + "After=sys-devices-virtual-dmi-id.device\n"); + if (r < 0) + return log_error_errno(r, "Failed to hook DMI id device before systemd-imdsd@.service: %m"); + + if (arg_import) { + /* Enable that we import IMDS data */ + r = generator_add_symlink(dest_early, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imds-import.service"); + if (r < 0) + return log_error_errno(r, "Failed to hook in systemd-imds-import.service: %m"); + } + + return 0; +} + +DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/imds/imds-tool.c b/src/imds/imds-tool.c new file mode 100644 index 0000000000000..0d71801aec659 --- /dev/null +++ b/src/imds/imds-tool.c @@ -0,0 +1,873 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "build.h" +#include "build-path.h" +#include "creds-util.h" +#include "dns-rr.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "format-table.h" +#include "format-util.h" +#include "fs-util.h" +#include "hexdecoct.h" +#include "imds-util.h" +#include "in-addr-util.h" +#include "io-util.h" +#include "iovec-util.h" +#include "json-util.h" +#include "log.h" +#include "main-func.h" +#include "options.h" +#include "parse-argument.h" +#include "pcrextend-util.h" +#include "pretty-print.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "tmpfile-util.h" + +static enum { + ACTION_SUMMARY, + ACTION_GET, + ACTION_USERDATA, + ACTION_IMPORT, + _ACTION_INVALID = -EINVAL, +} arg_action = _ACTION_INVALID; +static char *arg_key = NULL; +static ImdsWellKnown arg_well_known = _IMDS_WELL_KNOWN_INVALID; +static int arg_cache = -1; +static usec_t arg_refresh_usec = 0; +static bool arg_refresh_usec_set = false; + +STATIC_DESTRUCTOR_REGISTER(arg_key, freep); + +static int help(void) { + _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = terminal_urlify_man("systemd-imds", "1", &link); + if (r < 0) + return log_oom(); + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] [KEY]\n" + "\n%sIMDS data acquisition.%s\n\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('K', "well-known", "KEY", + "Select well-known key/base, one of:" + " hostname, region, zone, ipv4-public, ipv6-public, ssh-key," + " userdata, userdata-base, userdata-base64"): { + if (isempty(arg)) { + arg_well_known = _IMDS_WELL_KNOWN_INVALID; + break; + } + + if (streq(arg, "help")) + return DUMP_STRING_TABLE(imds_well_known, ImdsWellKnown, _IMDS_WELL_KNOWN_MAX); + + ImdsWellKnown wk = imds_well_known_from_string(arg); + if (wk < 0) + return log_error_errno(wk, "Failed to parse --well-known= argument: %s", arg); + + arg_well_known = wk; + break; + } + + OPTION_LONG("refresh", "SEC", "Set minimum freshness time for returned data"): { + if (isempty(arg)) { + arg_refresh_usec_set = false; + break; + } + + usec_t t; + r = parse_sec(arg, &t); + if (r < 0) + return log_error_errno(r, "Failed to parse refresh timeout: %s", arg); + + arg_refresh_usec = t; + arg_refresh_usec_set = true; + break; + } + + OPTION_LONG("cache", "BOOL", "Control cache use"): + r = parse_tristate_argument_with_auto("--cache=", arg, &arg_cache); + if (r < 0) + return r; + break; + + OPTION('u', "userdata", NULL, "Dump user data"): + arg_action = ACTION_USERDATA; + break; + + OPTION_LONG("import", NULL, + "Import system credentials from IMDS userdata" + " and place them in /run/credstore/"): + arg_action = ACTION_IMPORT; + break; + } + + char **args = option_parser_get_args(&state); + size_t n_args = option_parser_get_n_args(&state); + + if (IN_SET(arg_action, ACTION_USERDATA, ACTION_IMPORT)) { + if (n_args != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No parameters expected."); + + } else { + assert(arg_action < 0); + + if (n_args > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "None or one argument expected."); + + if (n_args == 0 && arg_well_known < 0) + arg_action = ACTION_SUMMARY; + else { + if (arg_well_known < 0) + arg_well_known = IMDS_BASE; + + if (n_args > 0) { + if (!imds_key_is_valid(args[0])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified IMDS key is not valid, refusing: %s", args[0]); + + if (!imds_well_known_can_suffix(arg_well_known)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Well known key '%s' does not take a key suffix, refusing.", imds_well_known_to_string(arg_well_known)); + + r = free_and_strdup_warn(&arg_key, args[0]); + if (r < 0) + return r; + } + + arg_action = ACTION_GET; + } + } + + return 1; +} + +static int acquire_imds_key( + sd_varlink *link, + ImdsWellKnown wk, + const char *key, + struct iovec *ret) { + + int r; + + assert(link); + assert(wk >= 0); + assert(wk < _IMDS_WELL_KNOWN_MAX); + assert(ret); + + const char *error_id = NULL; + sd_json_variant *reply = NULL; + r = sd_varlink_callbo( + link, + "io.systemd.InstanceMetadata.Get", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_CONDITION(wk != IMDS_BASE, "wellKnown", JSON_BUILD_STRING_UNDERSCORIFY(imds_well_known_to_string(wk))), + JSON_BUILD_PAIR_STRING_NON_EMPTY("key", key), + SD_JSON_BUILD_PAIR_CONDITION(arg_refresh_usec_set, "refreshUSec", SD_JSON_BUILD_UNSIGNED(arg_refresh_usec)), + SD_JSON_BUILD_PAIR_CONDITION(arg_cache >= 0, "cache", SD_JSON_BUILD_BOOLEAN(arg_cache))); + if (r < 0) + return log_error_errno(r, "Failed to issue io.systemd.InstanceMetadata.Get(): %m"); + if (error_id) { + if (STR_IN_SET(error_id, "io.systemd.InstanceMetadata.KeyNotFound", "io.systemd.InstanceMetadata.WellKnownKeyUnset")) { + *ret = (struct iovec) {}; + return 0; + } + + return log_error_errno(sd_varlink_error_to_errno(error_id, reply), "Failed to issue io.systemd.InstanceMetadata.Get(): %s", error_id); + } + + _cleanup_(iovec_done) struct iovec data = {}; + static const sd_json_dispatch_field dispatch_table[] = { + { "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, 0, SD_JSON_MANDATORY }, + {}, + }; + r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &data); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(data); + return 1; +} + +static int acquire_imds_key_as_string( + sd_varlink *link, + ImdsWellKnown wk, + const char *key, + char **ret) { + + int r; + + assert(link); + assert(wk >= 0); + assert(wk < _IMDS_WELL_KNOWN_MAX); + assert(ret); + + _cleanup_(iovec_done) struct iovec data = {}; + r = acquire_imds_key(link, wk, key, &data); + if (r < 0) + return r; + if (r == 0) { + *ret = NULL; + return 0; + } + + _cleanup_free_ char *s = NULL; + r = make_cstring(data.iov_base, data.iov_len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &s); + if (r < 0) + return r; + + *ret = TAKE_PTR(s); + return 1; +} + +static int acquire_imds_key_as_ip_address( + sd_varlink *link, + ImdsWellKnown wk, + const char *key, + int family, + union in_addr_union *ret) { + int r; + + assert(link); + assert(wk >= 0); + assert(wk < _IMDS_WELL_KNOWN_MAX); + assert(ret); + + _cleanup_free_ char *s = NULL; + r = acquire_imds_key_as_string(link, wk, key, &s); + if (r < 0) + return r; + if (r == 0 || isempty(s)) { + *ret = (union in_addr_union) {}; + return 0; + } + + r = in_addr_from_string(family, s, ret); + if (r < 0) + return r; + + return 1; +} + +static int action_summary(sd_varlink *link) { + int r; + + assert(link); + + _cleanup_(table_unrefp) Table *table = table_new_vertical(); + if (!table) + return log_oom(); + + const char *error_id = NULL; + sd_json_variant *reply = NULL; + r = sd_varlink_call( + link, + "io.systemd.InstanceMetadata.GetVendorInfo", + /* parameters= */ NULL, + &reply, + &error_id); + if (r < 0) + return log_error_errno(r, "Failed to issue io.systemd.InstanceMetadata.GetVendorInfo(): %m"); + if (error_id) + return log_error_errno(sd_varlink_error_to_errno(error_id, reply), "Failed to issue io.systemd.InstanceMetadata.GetVendorInfo(): %s", error_id); + + const char *vendor = NULL; + static const sd_json_dispatch_field dispatch_table[] = { + { "vendor", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 }, + {} + }; + r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &vendor); + if (r < 0) + return r; + if (vendor) { + r = table_add_many(table, + TABLE_FIELD, "Vendor", + TABLE_SET_JSON_FIELD_NAME, "vendor", + TABLE_STRING, vendor); + if (r < 0) + return table_log_add_error(r); + } + + static const struct { + ImdsWellKnown well_known; + const char *field; + } wktable[] = { + { IMDS_HOSTNAME, "Hostname" }, + { IMDS_REGION, "Region" }, + { IMDS_ZONE, "Zone" }, + { IMDS_IPV4_PUBLIC, "Public IPv4 Address" }, + { IMDS_IPV6_PUBLIC, "Public IPv6 Address" }, + }; + FOREACH_ELEMENT(i, wktable) { + _cleanup_free_ char *text = NULL; + + r = acquire_imds_key_as_string(link, i->well_known, /* key= */ NULL, &text); + if (r < 0) + return r; + if (r == 0 || isempty(text)) + continue; + + r = table_add_many(table, + TABLE_FIELD, i->field, + TABLE_SET_JSON_FIELD_NAME, imds_well_known_to_string(i->well_known), + TABLE_STRING, text); + if (r < 0) + return table_log_add_error(r); + } + + if (table_isempty(table)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "No well-known IMDS data available."); + + return table_print_or_warn(table); +} + +static const char* detect_json_object(const char *text) { + assert(text); + + /* Checks if the provided text looks like a JSON object. It checks if the first non-whitespace + * characters are {" or {}. */ + + text += strspn(text, WHITESPACE); + if (*text != '{') + return NULL; + + const char *e = text + 1; + e += strspn(e, WHITESPACE); + if (!IN_SET(*e, '"', '}')) + return NULL; + + return text; +} + +static int write_credential(const char *dir, const char *name, const struct iovec *data) { + int r; + + assert(dir); + assert(name); + + _cleanup_close_ int dfd = open_mkdir(dir, O_CLOEXEC|O_PATH, 0700); + if (dfd < 0) + return log_error_errno(dfd, "Failed to open credential directory '%s': %m", dir); + + if (faccessat(dfd, name, F_OK, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '%s' exists in credential directory '%s': %m", name, dir); + } else { + log_notice("Skipping importing of credential '%s', it already exists locally in '%s'.", name, dir); + return 0; + } + + _cleanup_free_ char *t = NULL; + _cleanup_close_ int fd = open_tmpfile_linkable_at(dfd, name, O_WRONLY|O_CLOEXEC, &t); + if (fd < 0) + return log_error_errno(fd, "Failed to create credential file '%s/%s': %m", dir, name); + + CLEANUP_TMPFILE_AT(dfd, t); + + r = loop_write(fd, data->iov_base, data->iov_len); + if (r < 0) + return log_error_errno(r, "Failed to write credential file '%s/%s': %m", dir, name); + + if (fchmod(fd, 0400) < 0) + return log_error_errno(errno, "Failed to set access mode on credential file '%s/%s': %m", dir, name); + + r = link_tmpfile_at(fd, dfd, t, name, /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to move credential file '%s/%s' into place: %m", dir, name); + + t = mfree(t); /* Disarm auto-cleanup */ + return 1; +} + +typedef struct CredentialData { + const char *name; + const char *text; + struct iovec data, encrypted; +} CredentialData; + +static void credential_data_done(CredentialData *d) { + assert(d); + + iovec_done(&d->data); + iovec_done(&d->encrypted); +} + +static int import_credential_one(CredentialData *d) { + int r; + + assert(d); + assert(d->name); + + log_debug("Importing credential '%s' from IMDS.", d->name); + + const char *dir = "/run/credstore"; + struct iovec *v, _v; + if (d->text) { + _v = IOVEC_MAKE_STRING(d->text); + v = &_v; + } else if (iovec_is_set(&d->data)) + v = &d->data; + else if (iovec_is_set(&d->encrypted)) { + dir = "/run/credstore.encrypted"; + v = &d->encrypted; + } else + assert_not_reached(); + + r = write_credential(dir, d->name, v); + if (r <= 0) + return r; + + log_info("Imported credential '%s' from IMDS (%s).", d->name, FORMAT_BYTES(v->iov_len)); + return 1; +} + +static int import_credentials(const char *text) { + int r; + + assert(text); + + /* We cannot be sure if the data is actually intended for us. Hence let's be somewhat defensive, and + * accept data in two ways: either immediately as a JSON object, or alternatively marked with a first + * line of "#systemd-userdata". The latter mimics the markers cloud-init employs. */ + + const char *e = startswith(text, "#systemd-userdata\n"); + if (!e) { + e = detect_json_object(text); + if (!e) { + log_info("IMDS user data does not look like JSON or systemd userdata, not processing."); + return 0; + } + } + + log_debug("Detected JSON userdata"); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + unsigned line = 0, column = 0; + r = sd_json_parse(e, /* flags= */ 0, &j, &line, &column); + if (r < 0) { + if (line > 0) + log_syntax(/* unit= */ NULL, LOG_WARNING, /* filename= */ NULL, line, r, "JSON parse failure."); + else + log_error_errno(r, "Failed to parse IMDS userdata JSON: %m"); + return 0; + } + + static const sd_json_dispatch_field top_table[] = { + { "systemd.credentials", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, 0, 0 }, + {}, + }; + + sd_json_variant *creds = NULL; + r = sd_json_dispatch(j, top_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG, &creds); + if (r < 0) + return r; + + unsigned n_imported = 0; + int ret = 0; + if (creds) { + log_debug("Found 'systemd.credentials' field"); + + sd_json_variant *c; + JSON_VARIANT_ARRAY_FOREACH(c, creds) { + static const sd_json_dispatch_field credential_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CredentialData, name), SD_JSON_MANDATORY }, + { "text", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CredentialData, text), 0 }, + { "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(CredentialData, data), 0 }, + { "encrypted", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(CredentialData, encrypted), 0 }, + {}, + }; + + _cleanup_(credential_data_done) CredentialData d = {}; + r = sd_json_dispatch(c, credential_table, SD_JSON_LOG|SD_JSON_WARNING, &d); + if (r < 0) { + RET_GATHER(ret, r); + continue; + } + + if (!credential_name_valid(d.name)) { + RET_GATHER(ret, log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Credential name '%s' is not valid, refusing.", d.name)); + continue; + } + + if ((!!d.text + !!iovec_is_set(&d.data) + !!iovec_is_set(&d.encrypted)) != 1) { + RET_GATHER(ret, log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Exactly one of 'text', 'data', 'encrypted' must be set for credential '%s', refusing.", d.name)); + continue; + } + + r = import_credential_one(&d); + if (r < 0) + RET_GATHER(ret, r); + else if (r > 0) + n_imported++; + } + } + + log_full(n_imported == 0 ? LOG_DEBUG : LOG_INFO, "Imported %u credentials from IMDS.", n_imported); + return ret; +} + +static int add_public_address_to_json_array(sd_json_variant **array, int family, const union in_addr_union *addr) { + int r; + + assert(array); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(addr); + + if (in_addr_is_null(family, addr)) + return 0; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + if (dns_resource_record_new_address(&rr, family, addr, "_public") < 0) + return log_oom(); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *rrj = NULL; + r = dns_resource_record_to_json(rr, &rrj); + if (r < 0) + return log_error_errno(r, "Failed to convert A RR to JSON: %m"); + + r = sd_json_variant_append_array(array, rrj); + if (r < 0) + return log_error_errno(r, "Failed to append A RR to JSON array: %m"); + + log_debug("Writing IMDS RR for: %s", dns_resource_record_to_string(rr)); + return 1; +} + +static int import_imds_public_addresses(sd_varlink *link) { + int r, ret = 0; + + assert(link); + + /* Creates local RRs (honoured by systemd-resolved) for our public addresses. */ + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL; + + union in_addr_union u = {}; + r = acquire_imds_key_as_ip_address(link, IMDS_IPV4_PUBLIC, /* key= */ NULL, AF_INET, &u); + if (r < 0) + RET_GATHER(ret, r); + else if (r > 0) { + r = add_public_address_to_json_array(&aj, AF_INET, &u); + if (r < 0) + return r; + } + + u = (union in_addr_union) {}; + r = acquire_imds_key_as_ip_address(link, IMDS_IPV6_PUBLIC, /* key= */ NULL, AF_INET6, &u); + if (r < 0) + RET_GATHER(ret, r); + else if (r > 0) { + r = add_public_address_to_json_array(&aj, AF_INET6, &u); + if (r < 0) + return r; + } + + if (sd_json_variant_elements(aj) == 0) { + log_debug("No IMDS public addresses known, not writing our RRs."); + return 0; + } + + _cleanup_free_ char *text = NULL; + r = sd_json_variant_format(aj, SD_JSON_FORMAT_NEWLINE, &text); + if (r < 0) + return log_error_errno(r, "Failed to format JSON text: %m"); + + r = write_string_file("/run/systemd/resolve/static.d/imds-public.rr", text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755); + if (r < 0) + return log_error_errno(r, "Failed to write IMDS RR data: %m"); + + log_debug("IMDS public addresses written out."); + return 1; +} + +static int import_imds_ssh_key(sd_varlink *link) { + int r; + + assert(link); + + _cleanup_(iovec_done) struct iovec data = {}; + r = acquire_imds_key(link, IMDS_SSH_KEY, /* key= */ NULL, &data); + if (r < 0) + return r; + if (r == 0 || !iovec_is_set(&data)) { + log_debug("No SSH key supplied via IMDS, not importing."); + return 0; + } + + r = write_credential("/run/credstore", "ssh.authorized_keys.root", &data); + if (r <= 0) + return r; + + log_info("Imported SSH key as credential 'ssh.authorized_keys.root'."); + return 0; +} + +static int import_imds_hostname(sd_varlink *link) { + int r; + + assert(link); + + _cleanup_(iovec_done) struct iovec data = {}; + r = acquire_imds_key(link, IMDS_HOSTNAME, /* key= */ NULL, &data); + if (r < 0) + return r; + if (r == 0 || !iovec_is_set(&data)) { + log_debug("No hostname supplied via IMDS, not importing."); + return 0; + } + + r = write_credential("/run/credstore", "firstboot.hostname", &data); + if (r <= 0) + return r; + + log_info("Imported hostname as credential 'firstboot.hostname'."); + return 0; +} + +static int acquire_imds_userdata(sd_varlink *link, struct iovec *ret) { + int r; + + assert(link); + assert(ret); + + /* First try our private namespace, if the concept exists, and then fall back to the singleton */ + _cleanup_(iovec_done) struct iovec data = {}; + r = acquire_imds_key(link, IMDS_USERDATA_BASE, "/systemd-userdata", &data); + if (r == 0) + r = acquire_imds_key(link, IMDS_USERDATA, /* key= */ NULL, &data); + if (r < 0) + return r; + if (r > 0) { + if (!iovec_is_set(&data)) { /* Treat empty user data like empty */ + *ret = (struct iovec) {}; + return 0; + } + + *ret = TAKE_STRUCT(data); + return 1; + } + + r = acquire_imds_key(link, IMDS_USERDATA_BASE64, /* key= */ NULL, &data); + if (r < 0) + return r; + _cleanup_(iovec_done) struct iovec decoded = {}; + if (r > 0) { + r = unbase64mem_full(data.iov_base, data.iov_len, /* secure= */ false, &decoded.iov_base, &decoded.iov_len); + if (r < 0) + return r; + } + + if (!iovec_is_set(&decoded)) { /* Treat empty user data like empty */ + *ret = (struct iovec) {}; + return 0; + } + + *ret = TAKE_STRUCT(decoded); + return 1; +} + +static int action_get(sd_varlink *link) { + int r; + + assert(link); + + _cleanup_(iovec_done) struct iovec data = {}; + r = acquire_imds_key(link, arg_well_known, arg_key, &data); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Key not available."); + + r = loop_write(STDOUT_FILENO, data.iov_base, data.iov_len); + if (r < 0) + return log_error_errno(r, "Failed to write data to standard output: %m"); + + return 0; +} + +static int action_userdata(sd_varlink *link) { + int r; + + assert(link); + + _cleanup_(iovec_done) struct iovec data = {}; + r = acquire_imds_userdata(link, &data); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "User data not available."); + + r = loop_write(STDOUT_FILENO, data.iov_base, data.iov_len); + if (r < 0) + return log_error_errno(r, "Failed to write data to standard output: %m"); + + return 0; +} + +static int remove_userdata(const char *path) { + assert(path); + + if (unlink(path) < 0) { + + if (errno != ENOENT) + log_debug_errno(errno, "Failed to remove '%s', ignoring: %m", path); + + return 0; + } + + log_debug("Removed '%s'.", path); + return 1; +} + +static int save_userdata(const struct iovec *data, const char *path) { + int r; + + assert(data); + assert(path); + + if (!iovec_is_set(data)) + return remove_userdata(path); + + r = write_data_file_atomic_at(AT_FDCWD, path, data, WRITE_DATA_FILE_MKDIR_0755); + if (r < 0) + return log_error_errno(r, "Failed to save userdata to '%s': %m", path); + + log_debug("Saved userdata to '%s'.", path); + return 1; +} + +static int action_import(sd_varlink *link) { + int r; + + assert(link); + + int ret = 0; + RET_GATHER(ret, import_imds_public_addresses(link)); + RET_GATHER(ret, import_imds_hostname(link)); + RET_GATHER(ret, import_imds_ssh_key(link)); + + _cleanup_(iovec_done) struct iovec data = {}; + r = acquire_imds_userdata(link, &data); + if (r < 0) + return RET_GATHER(ret, r); + if (r == 0) { + log_info("No IMDS data available, not importing credentials."); + (void) remove_userdata("/run/systemd/imds/userdata"); + return ret; + } + + /* Measure the userdata before we use it */ + (void) pcrextend_imds_userdata_now(&data); + + /* Keep a pristine copy of the userdata we actually applied. (Note that this data is typically also + * kept as cached item on systemd-imdsd, but that one is possibly subject to cache invalidation, + * while this one is supposed to pin the data actually in effect.) */ + (void) save_userdata(&data, "/run/systemd/imds/userdata"); + + /* Ensure no inner NUL byte */ + if (memchr(data.iov_base, 0, data.iov_len)) { + log_info("IMDS user data contains NUL byte, not processing."); + return ret; + } + + /* Turn this into a proper C string */ + if (!iovec_append(&data, &IOVEC_MAKE_BYTE(0))) + return log_oom(); + + return RET_GATHER(ret, import_credentials(data.iov_base)); +} + +static int run(int argc, char* argv[]) { + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.InstanceMetadata"); + if (r < 0) { + if (r != -ENOENT && !ERRNO_IS_NEG_DISCONNECT(r)) + return log_error_errno(r, "Failed to connect to systemd-imdsd: %m"); + + log_debug_errno(r, "Couldn't connect to /run/systemd/io.systemd.InstanceMetadata, will try to fork off systemd-imdsd as child now."); + + /* Try to fork off systemd-imdsd as a child as a fallback. If we have privileges and the + * SO_FWMARK trickery is not necessary, then this might just work. */ + _cleanup_free_ char *p = NULL; + _cleanup_close_ int pin_fd = + pin_callout_binary(LIBEXECDIR "/systemd-imdsd", &p); + if (pin_fd < 0) + return log_error_errno(pin_fd, "Failed to pick up imdsd binary: %m"); + + r = sd_varlink_connect_exec(&link, p, /* argv[]= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to connect to imdsd service: %m"); + } + + switch (arg_action) { + + case ACTION_SUMMARY: + return action_summary(link); + + case ACTION_GET: + return action_get(link); + + case ACTION_USERDATA: + return action_userdata(link); + + case ACTION_IMPORT: + return action_import(link); + + default: + assert_not_reached(); + } +} + +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/imds/imds-util.c b/src/imds/imds-util.c new file mode 100644 index 0000000000000..3c67417e4ba5f --- /dev/null +++ b/src/imds/imds-util.c @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "imds-util.h" +#include "string-table.h" +#include "string-util.h" +#include "utf8.h" + +bool imds_key_is_valid(const char *key) { + /* Just some pretty superficial validation. */ + + if (!key) + return false; + + if (!startswith(key, "/")) + return false; + + if (!ascii_is_valid(key)) + return false; + + if (string_has_cc(key, /* ok= */ NULL)) + return false; + + return true; +} + +static const char* const imds_well_known_table[_IMDS_WELL_KNOWN_MAX] = { + [IMDS_BASE] = "base", + [IMDS_HOSTNAME] = "hostname", + [IMDS_REGION] = "region", + [IMDS_ZONE] = "zone", + [IMDS_IPV4_PUBLIC] = "ipv4-public", + [IMDS_IPV6_PUBLIC] = "ipv6-public", + [IMDS_SSH_KEY] = "ssh-key", + [IMDS_USERDATA] = "userdata", + [IMDS_USERDATA_BASE] = "userdata-base", + [IMDS_USERDATA_BASE64] = "userdata-base64", +}; + +DEFINE_STRING_TABLE_LOOKUP(imds_well_known, ImdsWellKnown); + + +static const char* const imds_network_mode_table[_IMDS_NETWORK_MODE_MAX] = { + [IMDS_NETWORK_OFF] = "off", + [IMDS_NETWORK_LOCKED] = "locked", + [IMDS_NETWORK_UNLOCKED] = "unlocked", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(imds_network_mode, ImdsNetworkMode, IMDS_NETWORK_LOCKED); diff --git a/src/imds/imds-util.h b/src/imds/imds-util.h new file mode 100644 index 0000000000000..55ab79510f44e --- /dev/null +++ b/src/imds/imds-util.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "macro.h" +#include "string-table.h" /* IWYU pragma: keep */ + +typedef enum ImdsNetworkMode { + IMDS_NETWORK_OFF, /* No automatic pre-IMDS network configuration, something else has to do this. (Also: no "prohibit" route) */ + IMDS_NETWORK_LOCKED, /* "Prohibit" route for the IMDS server, unless you have SO_MARK set to 0x7FFF0815 */ + IMDS_NETWORK_UNLOCKED, /* No "prohibit" route for the IMDS server */ + _IMDS_NETWORK_MODE_MAX, + _IMDS_NETWORK_MODE_INVALID = -EINVAL, +} ImdsNetworkMode; + +/* Various well-known keys */ +typedef enum ImdsWellKnown { + IMDS_BASE, /* The same as "/", typically suffixed */ + IMDS_HOSTNAME, + IMDS_REGION, + IMDS_ZONE, + IMDS_IPV4_PUBLIC, + IMDS_IPV6_PUBLIC, + IMDS_SSH_KEY, + IMDS_USERDATA, + IMDS_USERDATA_BASE, /* typically suffixed */ + IMDS_USERDATA_BASE64, + _IMDS_WELL_KNOWN_MAX, + _IMDS_WELL_KNOWN_INVALID = -EINVAL, +} ImdsWellKnown; + +static inline bool imds_well_known_can_suffix(ImdsWellKnown wk) { + return IN_SET(wk, IMDS_BASE, IMDS_USERDATA_BASE); +} + +bool imds_key_is_valid(const char *key); + +DECLARE_STRING_TABLE_LOOKUP(imds_well_known, ImdsWellKnown); +DECLARE_STRING_TABLE_LOOKUP(imds_network_mode, ImdsNetworkMode); diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c new file mode 100644 index 0000000000000..b3d177116119e --- /dev/null +++ b/src/imds/imdsd.c @@ -0,0 +1,3098 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include + +#include "sd-bus.h" +#include "sd-device.h" +#include "sd-event.h" +#include "sd-json.h" +#include "sd-netlink.h" + +#include "alloc-util.h" +#include "build-path.h" +#include "build.h" +#include "bus-polkit.h" +#include "chase.h" +#include "copy.h" +#include "creds-util.h" +#include "curl-util.h" +#include "device-private.h" +#include "dns-rr.h" +#include "errno-util.h" +#include "escape.h" +#include "event-util.h" +#include "fd-util.h" +#include "format-ifname.h" +#include "format-table.h" +#include "hash-funcs.h" +#include "hashmap.h" +#include "imds-util.h" +#include "in-addr-util.h" +#include "io-util.h" +#include "iovec-util.h" +#include "json-util.h" +#include "log.h" +#include "main-func.h" +#include "netlink-util.h" +#include "options.h" +#include "parse-argument.h" +#include "parse-util.h" +#include "path-util.h" +#include "pretty-print.h" +#include "proc-cmdline.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "tmpfile-util.h" +#include "utf8.h" +#include "varlink-io.systemd.InstanceMetadata.h" +#include "varlink-util.h" +#include "web-util.h" +#include "xattr-util.h" + +/* This implements a client to the AWS' and Azure's "Instance Metadata Service", as well as GCP's "VM + * Metadata", i.e.: + * + * https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html + * https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service + * https://docs.cloud.google.com/compute/docs/metadata/overview + * https://docs.hetzner.cloud/reference/cloud#description/server-metadata + * + * Some notes: + * - IMDS service are heavily rate limited, and hence we want to centralize requests in one place and cache + * - In order to isolate IMDS access this expects that traffic to the IMDS address 169.254.169.254 is + * generally prohibited (via a prohibit route), but our service uses fwmark 0x7FFF0815, which (via source + * routing) can bypass this route. + * - To be robust to situations with multiple interfaces, if we have no hint which interface we shall use, + * we'll fork our own binary off, once for each interface, and communicate to it via Varlink. + * - This is supposed to run under its own UID, but with CAP_NET_ADMIN held (since we want to use + * IP_UNICAST_IF + SO_MARK) + * - This daemon either be invoked manually from the command line, to do a single request, mostly for + * debugging purposes. Or it can be invoked as a Varlink service, which is the primary intended mode of + * operation. + */ + +#define TOKEN_SIZE_MAX (4096U) +#define DATA_SIZE_MAX (4*1024*1024U) +#define FWMARK_DEFAULT UINT32_C(0x7FFF0815) +#define REFRESH_USEC_DEFAULT (15U * USEC_PER_MINUTE) +#define REFRESH_USEC_MIN (1U * USEC_PER_SEC) +#define DIRECT_OVERALL_TIMEOUT_USEC (40U * USEC_PER_SEC) /* a bit shorter than the default D-Bus/Varlink method call time-out) */ +#define INDIRECT_OVERALL_TIMEOUT_USEC (DIRECT_OVERALL_TIMEOUT_USEC + 5U * USEC_PER_SEC) +#define RETRY_MIN_USEC (20U * USEC_PER_MSEC) +#define RETRY_MAX_USEC (3U * USEC_PER_SEC) +#define RETRY_MAX 10U + +/* Which endpoint configuration source has been used, in order of preference */ +typedef enum EndpointSource { + ENDPOINT_USER, /* Explicit command line options */ + ENDPOINT_ENVIRONMENT, /* Fallback environment variables */ + ENDPOINT_PROC_CMDLINE, /* Acquired via kernel command line */ + ENDPOINT_CREDENTIALS, /* Acquired via system credentials */ + ENDPOINT_UDEV, /* Acquired via udev SMBIOS object */ + _ENDPOINT_SOURCE_MAX, + _ENDPOINT_SOURCE_INVALID = -EINVAL, +} EndpointSource; + +static char *arg_ifname = NULL; +static usec_t arg_refresh_usec = REFRESH_USEC_DEFAULT; +static uint32_t arg_fwmark = FWMARK_DEFAULT; +static bool arg_fwmark_set = true; +static ImdsWellKnown arg_well_known = _IMDS_WELL_KNOWN_INVALID; +static char* arg_key = NULL; +static bool arg_cache = true; +static bool arg_wait = false; +static bool arg_varlink = false; +static ImdsNetworkMode arg_network_mode = _IMDS_NETWORK_MODE_INVALID; +static bool arg_setup_network = false; + +/* The follow configure the IMDS service endpoint details */ +static EndpointSource arg_endpoint_source = _ENDPOINT_SOURCE_INVALID; +static char *arg_vendor = NULL; +static char *arg_token_url = NULL; +static char *arg_refresh_header_name = NULL; +static char *arg_data_url = NULL; +static char *arg_data_url_suffix = NULL; +static char *arg_token_header_name = NULL; +static char **arg_extra_header = NULL; +static struct in_addr arg_address_ipv4 = {}; +static struct in6_addr arg_address_ipv6 = {}; +static char *arg_well_known_key[_IMDS_WELL_KNOWN_MAX] = {}; + +static void imds_well_known_key_free(typeof(arg_well_known_key) *array) { + FOREACH_ARRAY(i, *array, _IMDS_WELL_KNOWN_MAX) + free(*i); +} + +STATIC_DESTRUCTOR_REGISTER(arg_ifname, freep); +STATIC_DESTRUCTOR_REGISTER(arg_key, freep); +STATIC_DESTRUCTOR_REGISTER(arg_vendor, freep); +STATIC_DESTRUCTOR_REGISTER(arg_token_url, freep); +STATIC_DESTRUCTOR_REGISTER(arg_refresh_header_name, freep); +STATIC_DESTRUCTOR_REGISTER(arg_data_url, freep); +STATIC_DESTRUCTOR_REGISTER(arg_data_url_suffix, freep); +STATIC_DESTRUCTOR_REGISTER(arg_token_header_name, freep); +STATIC_DESTRUCTOR_REGISTER(arg_extra_header, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_well_known_key, imds_well_known_key_free); + +typedef struct Context Context; + +typedef struct ChildData { + /* If there are multiple network interfaces, and we are not sure where to look for things, we'll fork + * additional instances of ourselves, one for each interface. */ + Context *context; + int ifindex; + sd_varlink *link; /* outgoing varlink connection towards the child */ + bool retry; /* If true then new information came to light and we should restart the request */ +} ChildData; + +struct Context { + /* Fields shared between requests (these remain allocated between Varlink requests) */ + sd_event *event; + sd_netlink *rtnl; + bool rtnl_attached; + sd_bus *system_bus; /* for polkit */ + CurlGlue *glue; + struct iovec token; /* token in binary */ + char *token_string; /* token as string, once complete and validated */ + int cache_dir_fd; + Hashmap *polkit_registry; + + /* Request-specific fields (these get reset whenever we start processing a new Varlink call) */ + int ifindex; + usec_t timestamp; /* CLOCK_BOOTTIME */ + int cache_fd; + char *cache_filename, *cache_temporary_filename; + uint64_t data_size; + usec_t refresh_usec; + char *key; + ImdsWellKnown well_known; + bool write_stdout; + struct iovec write_iovec; + bool cache; + bool wait; + sd_varlink *current_link; /* incoming varlink connection we are processing */ + uint32_t fwmark; + bool fwmark_set; + sd_event_source *overall_timeout_source; + + /* Mode 1 "direct": we go directly to the network (this is done if we know the interface index to + * use) */ + CURL *curl_token; + CURL *curl_data; + struct curl_slist *request_header_token, *request_header_data; + sd_event_source *retry_source; + unsigned n_retry; + usec_t retry_interval_usec; + + /* Mode 2 "indirect": we fork off a number of children which go to the network on behalf of us, + * because we have multiple network interfaces to deal with. */ + Hashmap *child_data; + sd_netlink_slot *address_change_slot; +}; + +#define CONTEXT_NULL \ + (Context) { \ + .cache_dir_fd = -EBADF, \ + .cache_fd = -EBADF, \ + .well_known = _IMDS_WELL_KNOWN_INVALID, \ + } + +/* Log helpers that cap at debug logging if we are operating on behalf of a Varlink client */ +#define context_log_errno(c, level, r, fmt, ...) \ + log_full_errno((c)->current_link ? LOG_DEBUG : (level), r, fmt, ##__VA_ARGS__) +#define context_log(c, level, fmt, ...) \ + log_full((c)->current_link ? LOG_DEBUG : (level), fmt, ##__VA_ARGS__) +#define context_log_oom(c) \ + (c)->current_link ? log_oom_debug() : log_oom() + +static int context_acquire_data(Context *c); +static int context_acquire_token(Context *c); +static int context_spawn_child(Context *c, int ifindex, sd_varlink **ret); + +static ChildData* child_data_free(ChildData *cd) { + if (!cd) + return NULL; + + if (cd->context) + hashmap_remove(cd->context->child_data, INT_TO_PTR(cd->ifindex)); + + sd_varlink_close_unref(cd->link); + return mfree(cd); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(ChildData*, child_data_free); + +static void context_reset_token(Context *c) { + assert(c); + + iovec_done(&c->token); + c->token_string = mfree(c->token_string); +} + +static void context_flush_token(Context *c) { + + if (c->cache_dir_fd >= 0) + (void) unlinkat(c->cache_dir_fd, "token", /* flags= */ 0); + + context_reset_token(c); +} + +static void context_reset_for_refresh(Context *c) { + assert(c); + + /* Flush out all fields, up to the point we can restart the current request */ + + if (c->curl_token) { + curl_glue_remove_and_free(c->glue, c->curl_token); + c->curl_token = NULL; + } + + if (c->curl_data) { + curl_glue_remove_and_free(c->glue, c->curl_data); + c->curl_data = NULL; + } + + sym_curl_slist_free_all(c->request_header_token); + c->request_header_token = NULL; + sym_curl_slist_free_all(c->request_header_data); + c->request_header_data = NULL; + + c->cache_fd = safe_close(c->cache_fd); + c->cache_filename = mfree(c->cache_filename); + + if (c->cache_temporary_filename && c->cache_dir_fd >= 0) + (void) unlinkat(c->cache_dir_fd, c->cache_temporary_filename, /* flags= */ 0); + + c->cache_temporary_filename = mfree(c->cache_temporary_filename); + + iovec_done(&c->write_iovec); + + c->child_data = hashmap_free(c->child_data); + c->data_size = 0; + + (void) sd_event_source_set_enabled(c->retry_source, SD_EVENT_OFF); +} + +static void context_reset_full(Context *c) { + assert(c); + + /* Flush out all fields relevant to the current request, comprehensively */ + + context_reset_for_refresh(c); + c->key = mfree(c->key); + c->well_known = _IMDS_WELL_KNOWN_INVALID; + c->current_link = sd_varlink_unref(c->current_link); + c->address_change_slot = sd_netlink_slot_unref(c->address_change_slot); + c->retry_source = sd_event_source_unref(c->retry_source); + c->overall_timeout_source = sd_event_source_unref(c->overall_timeout_source); + c->cache_dir_fd = safe_close(c->cache_dir_fd); +} + +static void context_new_request(Context *c) { + assert(c); + + /* Flush everything out from the previous request */ + context_reset_full(c); + + /* Reinitialize settings from defaults. */ + c->ifindex = 0; + c->timestamp = now(CLOCK_BOOTTIME); + c->refresh_usec = arg_refresh_usec; + c->cache = arg_cache; + c->wait = arg_wait; + c->fwmark = arg_fwmark; + c->fwmark_set = arg_fwmark_set; + c->n_retry = 0; +} + +static void context_done(Context *c) { + assert(c); + + /* Flush out everything specific to the current request first */ + context_reset_full(c); + context_reset_token(c); + + /* And then also flush out everything shared between requests */ + c->glue = curl_glue_unref(c->glue); + c->rtnl = sd_netlink_unref(c->rtnl); + c->event = sd_event_unref(c->event); + c->polkit_registry = hashmap_free(c->polkit_registry); + c->system_bus = sd_bus_flush_close_unref(c->system_bus); +} + +static void context_fail_full(Context *c, int r, const char *varlink_error) { + assert(c); + assert(r != 0); + + /* Called whenever the current retrieval fails asynchronously */ + + r = -abs(r); + + if (varlink_error) + context_log_errno(c, LOG_ERR, r, "Operation failed (%s).", varlink_error); + else + context_log_errno(c, LOG_ERR, r, "Operation failed (%m)."); + + /* If we are running in Varlink mode, return the error on the connection */ + if (c->current_link) { + if (varlink_error) + (void) sd_varlink_error(c->current_link, varlink_error, NULL); + else + (void) sd_varlink_error_errno(c->current_link, r); + } else + /* Otherwise terminate the whole process. */ + sd_event_exit(c->event, r); + + context_reset_full(c); +} + +static void context_fail(Context *c, int r) { + context_fail_full(c, r, /* varlink_error= */ NULL); +} + +static void context_success(Context *c) { + int r; + + assert(c); + + /* Called whenever the current retrieval succeeds asynchronously */ + + context_log(c, LOG_DEBUG, "Operation succeeded."); + + if (c->current_link) { + r = sd_varlink_replybo( + c->current_link, + JSON_BUILD_PAIR_IOVEC_BASE64("data", &c->write_iovec), + SD_JSON_BUILD_PAIR_CONDITION(c->ifindex > 0, "interface", SD_JSON_BUILD_INTEGER(c->ifindex))); + if (r < 0) + context_log_errno(c, LOG_WARNING, r, "Failed to reply to Varlink call, ignoring: %m"); + } else + sd_event_exit(c->event, 0); + + context_reset_full(c); +} + +static int setsockopt_callback(void *userdata, curl_socket_t curlfd, curlsocktype purpose) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(curlfd >= 0); + + if (purpose != CURLSOCKTYPE_IPCXN) + return CURL_SOCKOPT_OK; + + r = socket_set_unicast_if(curlfd, AF_UNSPEC, c->ifindex); + if (r < 0) { + context_log_errno(c, LOG_ERR, r, "Failed to bind HTTP socket to interface: %m"); + return CURL_SOCKOPT_ERROR; + } + + if (c->fwmark_set && + setsockopt(curlfd, SOL_SOCKET, SO_MARK, &c->fwmark, sizeof(c->fwmark)) < 0) { + context_log_errno(c, LOG_ERR, errno, "Failed to set firewall mark on HTTP socket: %m"); + return CURL_SOCKOPT_ERROR; + } + + return CURL_SOCKOPT_OK; +} + +static int context_combine_key(Context *c, char **ret) { + assert(ret); + + /* Combines the well known key with the explicitly configured key */ + + char *s; + if (c->well_known < 0 || c->well_known == IMDS_BASE) { + if (!c->key) + return -ENODATA; + + s = strdup(c->key); + } else { + const char *wk = arg_well_known_key[c->well_known]; + if (!wk) + return -ENODATA; + if (c->key) + s = strjoin(wk, c->key); + else + s = strdup(wk); + } + if (!s) + return -ENOMEM; + + *ret = TAKE_PTR(s); + return 0; +} + +static const char *context_get_runtime_directory(Context *c) { + assert(c); + + /* Returns the discovered runtime directory, but only if caching is enabled. */ + + if (!c->cache) { + context_log(c, LOG_DEBUG, "Cache disabled."); + return NULL; + } + + const char *e = secure_getenv("RUNTIME_DIRECTORY"); + if (!e) { + context_log(c, LOG_DEBUG, "Not using cache as $RUNTIME_DIRECTORY is not set."); + return NULL; + } + + return e; +} + +static int context_save_ifname(Context *c) { + int r; + + assert(c); + + /* Saves the used interface name for later retrievals, so that we don't have to wildcard search on + * all interfaces anymore. */ + + if (c->ifindex <= 0) + return 0; + + const char *d = context_get_runtime_directory(c); + if (!d) + return 0; + + _cleanup_close_ int dirfd = open(d, O_PATH|O_CLOEXEC); + if (dirfd < 0) + return context_log_errno(c, LOG_ERR, errno, "Failed to open runtime directory: %m"); + + _cleanup_free_ char *ifname = NULL; + r = rtnl_get_ifname_full(&c->rtnl, c->ifindex, &ifname, /* ret_altnames= */ NULL); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to resolve interface index %i: %m", c->ifindex); + + r = write_string_file_at(dirfd, "ifname", ifname, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to write 'ifname' file: %m"); + + return 1; +} + +typedef enum CacheResult { + CACHE_RESULT_DISABLED, /* caching is disabled */ + CACHE_RESULT_HIT, /* found a positive entry */ + CACHE_RESULT_MISS, /* did not find an entry */ + CACHE_RESULT_KEY_NOT_FOUND, /* found a negative entry */ + CACHE_RESULT_NOT_CACHEABLE, /* not suitable for caching */ + _CACHE_RESULT_MAX, + _CACHE_RESULT_INVALID = -EINVAL, + _CACHE_RESULT_ERRNO_MAX = -ERRNO_MAX, +} CacheResult; + +static CacheResult context_process_cache(Context *c) { + int r; + + assert(c); + + assert(c->key || c->well_known >= 0); + assert(c->cache_fd < 0); + assert(!c->cache_filename); + assert(!c->cache_temporary_filename); + + /* Checks the local cache – if we have one – for the current request */ + + if (c->cache_dir_fd < 0) { + const char *e = context_get_runtime_directory(c); + if (!e) + return CACHE_RESULT_DISABLED; + + char ifname[IF_NAMESIZE]; + r = format_ifname(c->ifindex, ifname); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to format interface name: %m"); + + if (!filename_is_valid(ifname)) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EINVAL), "Network interface name '%s' is not a valid filename, refusing.", ifname); + + _cleanup_free_ char *cache_dir = path_join("cache", ifname); + if (!cache_dir) + return context_log_oom(c); + + r = chase(cache_dir, + e, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT, + /* ret_path= */ NULL, + &c->cache_dir_fd); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to open cache directory: %m"); + } + + _cleanup_free_ char *k = NULL; + r = context_combine_key(c, &k); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to combine IMDS key: %m"); + + _cleanup_free_ char *escaped = xescape(k, "/."); + if (!escaped) + return context_log_oom(c); + + _cleanup_free_ char *fn = strjoin("key-", escaped); + if (!fn) + return context_log_oom(c); + + if (!filename_is_valid(fn)) { + context_log(c, LOG_WARNING, "Cache filename for '%s' is not valid, not caching.", fn); + return CACHE_RESULT_NOT_CACHEABLE; + } + + c->cache_filename = TAKE_PTR(fn); + + _cleanup_close_ int fd = openat(c->cache_dir_fd, c->cache_filename, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + if (errno != ENOENT) + return context_log_errno(c, LOG_ERR, errno, "Failed to open cache file '%s': %m", c->cache_filename); + } else { + _cleanup_free_ char *d = NULL; + size_t l; + + context_log(c, LOG_DEBUG, "Found cached file '%s'.", c->cache_filename); + + r = fgetxattr_malloc(fd, "user.imds.timestamp", &d, &l); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to read timestamp from cache file: %m"); + if (l != sizeof(usec_t)) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EBADMSG), "Invalid timestamp xattr on cache file '%s': %m", c->cache_filename); + + usec_t *u = (usec_t*) d; + if (usec_add(*u, c->refresh_usec) > c->timestamp) { + _cleanup_free_ char *result = NULL; + r = fgetxattr_malloc(fd, "user.imds.result", &result, /* ret_size= */ NULL); + if (r == -ENODATA) { + /* No user.imds.result xattr means: hit! */ + if (c->write_stdout) { + r = copy_bytes(fd, STDOUT_FILENO, /* max_bytes= */ UINT64_MAX, /* copy_flags= */ 0); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to write cached data to standard output: %m"); + } else { + assert(!iovec_is_set(&c->write_iovec)); + r = read_full_file_at(fd, /* filename= */ NULL, (char**) &c->write_iovec.iov_base, &c->write_iovec.iov_len); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to read cache data: %m"); + } + + return CACHE_RESULT_HIT; + } + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to read 'user.imds.result' extended attribute: %m"); + + if (streq(result, "key-not-found")) + return CACHE_RESULT_KEY_NOT_FOUND; + + context_log(c, LOG_WARNING, "Unexpected 'user.imds.result' extended attribute value, ignoring: %s", result); + (void) unlinkat(c->cache_dir_fd, c->cache_filename, /* flags= */ 0); + } else { + context_log(c, LOG_DEBUG, "Cached data is older than '%s', ignoring.", FORMAT_TIMESPAN(c->refresh_usec, 0)); + (void) unlinkat(c->cache_dir_fd, c->cache_filename, /* flags= */ 0); + } + } + + /* So the above was not conclusive, let's then at least try to reuse the token */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + r = sd_json_parse_file_at(/* f= */ NULL, c->cache_dir_fd, "token", /* flags= */ 0, &j, /* reterr_line= */ NULL, /* reterr_column= */ NULL); + if (r == -ENOENT) { + context_log_errno(c, LOG_DEBUG, r, "No cached token"); + return CACHE_RESULT_MISS; + } + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to read cached token: %m"); + + struct { + const char *token; + uint64_t until; + } d = {}; + + static const sd_json_dispatch_field table[] = { + { "token", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, token), SD_JSON_MANDATORY }, + { "validUntilUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(d, until), SD_JSON_MANDATORY }, + {} + }; + + r = sd_json_dispatch(j, table, SD_JSON_ALLOW_EXTENSIONS, &d); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to decode cached token data: %m"); + + if (d.until > c->timestamp) { + c->token_string = strdup(d.token); + if (!c->token_string) + return context_log_oom(c); + + context_log(c, LOG_INFO, "Reusing cached token."); + } else + context_log(c, LOG_DEBUG, "Cached token is stale, not using."); + + return CACHE_RESULT_MISS; +} + +static int on_retry(sd_event_source *s, uint64_t usec, void *userdata) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(s); + + /* Invoked whenever the retry timer event elapses and we need to retry again */ + + context_log(c, LOG_DEBUG, "Retrying..."); + + /* Maybe some other instance was successful in the meantime and already found something? */ + CacheResult cr = context_process_cache(c); + if (cr < 0) { + context_fail(c, cr); + return 0; + } + if (cr == CACHE_RESULT_HIT) { + context_success(c); + return 0; + } + if (cr == CACHE_RESULT_KEY_NOT_FOUND) { + context_fail(c, context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(ENOENT), "Cache reports: key not found")); + return 0; + } + + r = context_acquire_token(c); + if (r < 0) { + context_fail(c, r); + return 0; + } + + r = context_acquire_data(c); + if (r < 0) + context_fail(c, r); + + return 0; +} + +static int context_schedule_retry(Context *c) { + int r; + + assert(c); + + /* Schedules a new retry via a timer event */ + + if (c->n_retry >= RETRY_MAX) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EUCLEAN), "Retry limits reached, refusing."); + + if (c->n_retry == 0) + c->retry_interval_usec = RETRY_MIN_USEC; + else if (c->retry_interval_usec < RETRY_MAX_USEC / 2) + c->retry_interval_usec *= 2; + else + c->retry_interval_usec = RETRY_MAX_USEC; + + c->n_retry++; + context_log(c, LOG_DEBUG, "Retry attempt #%u in %s...", c->n_retry, FORMAT_TIMESPAN(c->retry_interval_usec, USEC_PER_MSEC)); + + context_reset_for_refresh(c); + + r = event_reset_time_relative( + c->event, + &c->retry_source, + CLOCK_BOOTTIME, + c->retry_interval_usec, + /* accuracy= */ 0, + on_retry, + c, + /* priority= */ 0, + "imds-retry", + /* force_reset= */ true); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to reset retry timer event source: %m"); + + return 0; +} + +static int context_acquire_http_status(Context *c, CURL *curl, long *ret_status) { + assert(c); + assert(ret_status); + + /* Acquires the HTTP status code, and does some generic validation that applies to both the token and + * the data transfer. + * + * Error handling as per: + * https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html#instance-metadata-returns + * https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service#rate-limiting + */ + + long status; + CURLcode code = sym_curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + if (code != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", sym_curl_easy_strerror(code)); + + context_log(c, LOG_DEBUG, "Got HTTP error code %li.", status); + + if (status == 403) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EADDRNOTAVAIL), "IMDS is not available"); + + /* Automatically retry on some transient errors from HTTP */ + if (IN_SET(status, + 503, /* AWS + GCP */ + 429 /* Azure + GCP */)) { + *ret_status = 0; + return 0; /* no immediate answer, please schedule retry */ + } + + if (status < 200 || status > 600) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "HTTP request finished with unexpected code %li.", status); + + *ret_status = status; + return 1; /* valid answer */ +} + +static int context_validate_token_http_status(Context *c, long status) { + assert(c); + + /* Specific HTTP status checks for the token transfer */ + + if (status >= 300) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "HTTP request for token finished with unexpected code %li.", status); + + return 1; /* all good */ +} + +static int context_validate_data_http_status(Context *c, long status) { + int r; + + assert(c); + + /* Specific HTTP status checks for the data transfer */ + + if (status == 401 && arg_token_url) { + /* We need a new token */ + context_log(c, LOG_DEBUG, "Server requested a new token..."); + + /* Count token requests as a retry */ + if (c->n_retry >= RETRY_MAX) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EUCLEAN), "Retry limits reached, refusing."); + c->n_retry++; + + context_flush_token(c); + context_reset_for_refresh(c); + + r = context_acquire_token(c); + if (r < 0) + return r; + + r = context_acquire_data(c); + if (r < 0) + return r; + + return 0; /* restarted right-away */ + } + + if (status == 404) { + _cleanup_free_ char *key = NULL; + r = context_combine_key(c, &key); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to combine IMDS key: %m"); + + /* Do negative caching for not found */ + if (c->cache_fd >= 0) { + if (fsetxattr(c->cache_fd, "user.imds.result", "key-not-found", STRLEN("key-not-found"), /* flags= */ 0) < 0) + context_log_errno(c, LOG_DEBUG, errno, "Failed to set result xattr on '%s', ignoring: %m", c->cache_filename); + else { + r = link_tmpfile_at(c->cache_fd, c->cache_dir_fd, c->cache_temporary_filename, c->cache_filename, LINK_TMPFILE_REPLACE); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to move cache file into place: %m"); + + c->cache_fd = safe_close(c->cache_fd); + c->cache_temporary_filename = mfree(c->cache_temporary_filename); + + context_log(c, LOG_DEBUG, "Cached negative entry for '%s'.", key); + } + } + + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(ENOENT), "Key '%s' not found.", key); + } + + if (status >= 300) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "HTTP request for data finished with unexpected code %li.", status); + + return 1; /* all good */ +} + +static int context_validate_token(Context *c) { + int r; + + assert(c); + + /* Validates that the downloaded token data actually forms a valid string */ + + _cleanup_free_ char *t = NULL; + r = make_cstring( + c->token.iov_base, + c->token.iov_len, + MAKE_CSTRING_REFUSE_TRAILING_NUL, + &t); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to convert token into C string: %m"); + + if (string_has_cc(t, NULL) || + !utf8_is_valid(t)) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EINVAL), "Token not valid UTF-8 or contains control characters, refusing."); + + free_and_replace(c->token_string, t); + return 1; /* all good */ +} + +static int context_save_token(Context *c) { + int r; + + assert(c); + assert(c->token_string); + + /* Save the acquired token in the cache, so that we can reuse it later */ + + if (c->cache_dir_fd < 0) + return 0; + + /* Only store half the valid time, to make sure we have ample time to use it */ + usec_t until = usec_add(c->timestamp, c->refresh_usec/2); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + r = sd_json_buildo( + &j, + SD_JSON_BUILD_PAIR_STRING("token", c->token_string), + SD_JSON_BUILD_PAIR_UNSIGNED("validUntilUSec", until)); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to build token JSON: %m"); + + _cleanup_free_ char *t = NULL; + r = sd_json_variant_format(j, SD_JSON_FORMAT_NEWLINE, &t); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to format JSON: %m"); + + r = write_string_file_at(c->cache_dir_fd, "token", t, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MODE_0600); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to write token cache file: %m"); + + return 0; +} + +static int context_save_data(Context *c) { + int r; + + assert(c); + + /* Finalize saving of the acquired data in the cache */ + + if (c->cache_fd < 0) + return 0; + + r = link_tmpfile_at(c->cache_fd, c->cache_dir_fd, c->cache_temporary_filename, c->cache_filename, LINK_TMPFILE_REPLACE); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to move cache file into place: %m"); + + c->cache_fd = safe_close(c->cache_fd); + c->cache_temporary_filename = mfree(c->cache_temporary_filename); + + context_log(c, LOG_DEBUG, "Cached data."); + return 0; +} + +static void curl_glue_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { + int r; + + assert(g); + + /* Called whenever libcurl did its thing and reports a download being complete or having failed */ + + Context *c = NULL; + if (sym_curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char**) &c) != CURLE_OK) + return; + + switch (result) { + + case CURLE_OK: /* yay! */ + /* If we managed to get a HTTP reply, this is good enough, let's pin the interface now for + * later calls */ + (void) context_save_ifname(c); + break; + + case CURLE_WRITE_ERROR: + /* CURLE_WRITE_ERROR we'll see if the data callbacks failed already. We'll try to look at the + * HTTP status below, and use that ideally. */ + break; + + case CURLE_COULDNT_CONNECT: + case CURLE_OPERATION_TIMEDOUT: + case CURLE_GOT_NOTHING: + case CURLE_SEND_ERROR: + case CURLE_RECV_ERROR: + context_log(c, LOG_INFO, "Connection error from curl: %s", sym_curl_easy_strerror(result)); + + /* Automatically retry on some transient errors from curl itself */ + r = context_schedule_retry(c); + if (r < 0) + return context_fail(c, r); + + return; + + default: + return context_fail_full( + c, + context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EHOSTDOWN), "Transfer failed: %s", sym_curl_easy_strerror(result)), + "io.systemd.InstanceMetadata.CommunicationFailure"); + } + + long status; + r = context_acquire_http_status(c, curl, &status); + if (r == -EADDRNOTAVAIL) + return context_fail_full(c, r, "io.systemd.InstanceMetadata.NotAvailable"); + if (r < 0) + return context_fail(c, r); + if (r == 0) { /* We shall retry */ + (void) context_schedule_retry(c); + return; + } + if (result != CURLE_OK) /* if getting the HTTP status didn't work, propagate a generic error */ + return context_fail(c, SYNTHETIC_ERRNO(ENOTRECOVERABLE)); + + if (curl == c->curl_token) { + r = context_validate_token_http_status(c, status); + if (r < 0) + return context_fail(c, r); + + r = context_validate_token(c); + if (r < 0) + return context_fail(c, r); + + context_log(c, LOG_DEBUG, "Token successfully acquired."); + + r = context_save_token(c); + if (r < 0) + return context_fail(c, r); + + r = context_acquire_data(c); + if (r < 0) + return context_fail(c, r); + + } else if (curl == c->curl_data) { + + r = context_validate_data_http_status(c, status); + if (r == -ENOENT) + return context_fail_full(c, r, "io.systemd.InstanceMetadata.KeyNotFound"); + if (r < 0) + return context_fail(c, r); + if (r == 0) /* Immediately restarted */ + return; + + context_log(c, LOG_DEBUG, "Data download successful."); + + r = context_save_data(c); + if (r < 0) + return context_fail(c, r); + + context_success(c); + } else + assert_not_reached(); +} + +static int context_acquire_glue(Context *c) { + int r; + + assert(c); + + /* Allocates a curl object if we don't have one yet */ + + if (c->glue) + return 0; + + r = curl_glue_new(&c->glue, c->event); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to allocate curl glue: %m"); + + c->glue->on_finished = curl_glue_on_finished; + c->glue->userdata = c; + + return 0; +} + +static size_t data_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) { + Context *c = ASSERT_PTR(userdata); + size_t sz = size * nmemb; + int r; + + /* Called whenever we receive new payload from the server */ + assert(contents); + + /* If we managed to get a HTTP reply, this is good enough, let's pin the interface now for later calls */ + (void) context_save_ifname(c); + + /* Before we use the acquired data, let's verify the HTTP status, if there's a failure or we need to + * restart, abort the write here. Note that the curl_glue_on_finished() call will then check the HTTP + * status again and act on it. */ + long status; + r = context_acquire_http_status(c, c->curl_data, &status); + if (r <= 0) + return 0; /* fail the thing, so that curl_glue_on_finished() can handle this failure or retry request */ + if (status >= 300) /* any status equal or above 300 needs to be handled by curl_glue_on_finished() too */ + return 0; + + if (sz > UINT64_MAX - c->data_size || + c->data_size + sz > DATA_SIZE_MAX) { + context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(E2BIG), "Data too large, refusing."); + return 0; + } + + c->data_size += sz; + + if (c->write_stdout) + (void) fwrite(contents, 1, sz, stdout); + else if (!iovec_append(&c->write_iovec, &IOVEC_MAKE(contents, sz))) { + context_log_oom(c); + return 0; + } + + if (c->cache_fd >= 0) { + r = loop_write(c->cache_fd, contents, sz); + if (r < 0) { + context_log_errno(c, LOG_ERR, r, "Failed to write data to cache: %m"); + return 0; + } + } + + return sz; +} + +static int context_acquire_data(Context *c) { + int r; + + assert(c); + assert(c->key || c->well_known >= 0); + + /* Called to initiate getting the actual IMDS key payload */ + + if (arg_token_url && !c->token_string) + return 0; /* If we need a token first, let's not do anything */ + + _cleanup_free_ char *k = NULL; + r = context_combine_key(c, &k); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to combine key: %m"); + + context_log(c, LOG_INFO, "Requesting data for key '%s'.", k); + + if (c->cache_dir_fd >= 0 && + c->cache_filename && + c->cache_fd < 0) { + c->cache_fd = open_tmpfile_linkable_at(c->cache_dir_fd, c->cache_filename, O_WRONLY|O_CLOEXEC, &c->cache_temporary_filename); + if (c->cache_fd < 0) + return context_log_errno(c, LOG_ERR, c->cache_fd, "Failed to create cache file '%s': %m", c->cache_filename); + + if (fchmod(c->cache_fd, 0600) < 0) + return context_log_errno(c, LOG_ERR, errno, "Failed to adjust cache node access mode: %m"); + + if (fsetxattr(c->cache_fd, "user.imds.timestamp", &c->timestamp, sizeof(c->timestamp), /* flags= */ 0) < 0) + return context_log_errno(c, LOG_ERR, errno, "Failed to set timestamp xattr on '%s': %m", c->cache_filename); + } + + r = context_acquire_glue(c); + if (r < 0) + return r; + + _cleanup_free_ char *url = strjoin(arg_data_url, k, arg_data_url_suffix); + if (!url) + return context_log_oom(c); + + r = curl_glue_make(&c->curl_data, url, c); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to create CURL request for data: %m"); + + if (c->token_string) { + _cleanup_free_ char *token_header = strjoin(arg_token_header_name, ": ", c->token_string); + if (!token_header) + return context_log_oom(c); + + r = curl_append_to_header(&c->request_header_data, STRV_MAKE(token_header)); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to create curl header: %m"); + } + + r = curl_append_to_header(&c->request_header_data, arg_extra_header); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to create curl header: %m"); + + if (c->request_header_data) + if (sym_curl_easy_setopt(c->curl_data, CURLOPT_HTTPHEADER, c->request_header_data) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set HTTP request header."); + + if (sym_curl_easy_setopt(c->curl_data, CURLOPT_WRITEFUNCTION, data_write_callback) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function."); + + if (sym_curl_easy_setopt(c->curl_data, CURLOPT_WRITEDATA, c) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function userdata."); + + if (sym_curl_easy_setopt(c->curl_data, CURLOPT_SOCKOPTFUNCTION, setsockopt_callback) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt function."); + + if (sym_curl_easy_setopt(c->curl_data, CURLOPT_SOCKOPTDATA, c) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt function userdata."); + + if (sym_curl_easy_setopt(c->curl_data, CURLOPT_LOCALPORT, 1L) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt local port"); + + if (sym_curl_easy_setopt(c->curl_data, CURLOPT_LOCALPORTRANGE, 1023L) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt local port range"); + + r = curl_glue_add(c->glue, c->curl_data); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to add CURL request to glue: %m"); + + return 0; +} + +static size_t token_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) { + Context *c = ASSERT_PTR(userdata); + size_t sz = size * nmemb; + int r; + + /* Called whenever we get data from the token download */ + assert(contents); + + /* If we managed to get a HTTP reply, this is good enough, let's pin the interface now for later calls */ + (void) context_save_ifname(c); + + /* Before we use acquired data, let's verify the HTTP status */ + long status; + r = context_acquire_http_status(c, c->curl_token, &status); + if (r <= 0) + return 0; /* fail the thing, so that curl_glue_on_finished() can handle this failure or retry request */ + if (status >= 300) /* any status equal or above 300 needs to be handled by curl_glue_on_finished() */ + return 0; + + if (sz > SIZE_MAX - c->token.iov_len || + c->token.iov_len + sz > TOKEN_SIZE_MAX) { + context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(E2BIG), "IMDS token too large."); + return 0; + } + + if (!iovec_append(&c->token, &IOVEC_MAKE(contents, sz))) { + context_log_oom(c); + return 0; + } + + return sz; +} + +static int context_acquire_token(Context *c) { + int r; + + assert(c); + + /* Called to initiate getting the token if we need one. */ + + if (c->token_string || !arg_token_url) + return 0; + + context_log(c, LOG_INFO, "Requesting token."); + + r = context_acquire_glue(c); + if (r < 0) + return r; + + r = curl_glue_make(&c->curl_token, arg_token_url, c); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to create CURL request for API token: %m"); + + if (arg_refresh_header_name) { + _cleanup_free_ char *ttl_header = NULL; + if (asprintf(&ttl_header, + "%s: %" PRIu64, + arg_refresh_header_name, + DIV_ROUND_UP(c->refresh_usec, USEC_PER_SEC)) < 0) + return context_log_oom(c); + + c->request_header_token = curl_slist_new(ttl_header, NULL); + if (!c->request_header_token) + return context_log_oom(c); + } + + if (sym_curl_easy_setopt(c->curl_token, CURLOPT_HTTPHEADER, c->request_header_token) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set HTTP request header."); + + if (sym_curl_easy_setopt(c->curl_token, CURLOPT_CUSTOMREQUEST, "PUT") != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set HTTP request method."); + + if (sym_curl_easy_setopt(c->curl_token, CURLOPT_WRITEFUNCTION, token_write_callback) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function."); + + if (sym_curl_easy_setopt(c->curl_token, CURLOPT_WRITEDATA, c) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function userdata."); + + if (sym_curl_easy_setopt(c->curl_token, CURLOPT_SOCKOPTFUNCTION, setsockopt_callback) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt function."); + + if (sym_curl_easy_setopt(c->curl_token, CURLOPT_SOCKOPTDATA, c) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt function userdata."); + + r = curl_glue_add(c->glue, c->curl_token); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to add CURL request to glue: %m"); + + return 0; +} + +static int vl_on_reply(sd_varlink *link, sd_json_variant *m, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) { + ChildData *cd = ASSERT_PTR(userdata); + Context *c = ASSERT_PTR(cd->context); + int r; + + assert(link); + assert(m); + + /* When we spawned off worker instances of ourselves (one for each local network interface), then + * we'll get a response from them via a Varlink reply. Handle it. */ + + if (error_id) { + r = sd_varlink_error_to_errno(error_id, m); + if (r == -EBADR) + context_log_errno(c, LOG_WARNING, r, "Varlink error from interface %i: %s", cd->ifindex, error_id); + else + context_log_errno(c, LOG_WARNING, r, "Varlink error from interface %i: %m", cd->ifindex); + + /* Propagate these errors immediately */ + if (streq(error_id, "io.systemd.InstanceMetadata.KeyNotFound")) { + context_fail_full(c, -ENOENT, error_id); + return 0; + } + if (streq(error_id, "io.systemd.InstanceMetadata.WellKnownKeyUnset")) { + context_fail_full(c, -ENODATA, error_id); + return 0; + } + if (streq(error_id, "io.systemd.InstanceMetadata.NotAvailable")) { + context_fail_full(c, -EADDRNOTAVAIL, error_id); + return 0; + } + + /* The other errors we consider transient. Let's see if we shall immediately restart the request. */ + if (cd->retry) { + context_log(c, LOG_DEBUG, "Child for network interface %i was scheduled for immediate retry, executing now.", cd->ifindex); + cd->link = sd_varlink_close_unref(cd->link); + cd->retry = false; + + r = context_spawn_child(c, cd->ifindex, &cd->link); + if (r < 0) { + context_fail(c, r); + return 0; + } + + sd_varlink_set_userdata(cd->link, cd); + return 0; + } + + /* We shall not retry immediately. In that case, we give up on the child, and propagate the + * error if it was the last child, otherwise we continue until the last one dies too. */ + cd = child_data_free(cd); + + if (hashmap_isempty(c->child_data) && !c->wait) { + /* This is the last child, propagate the error */ + context_log(c, LOG_DEBUG, "Last child failed, propagating error."); + + if (streq(error_id, "io.systemd.InstanceMetadata.CommunicationFailure")) + context_fail_full(c, -EHOSTDOWN, error_id); + else if (streq(error_id, "io.systemd.InstanceMetadata.Timeout")) + context_fail_full(c, -ETIMEDOUT, error_id); + else + context_fail_full(c, r, error_id); + + return 0; + } + + context_log(c, LOG_DEBUG, "Pending children remaining, continuing to wait."); + return 0; + } + + assert(!iovec_is_set(&c->write_iovec)); + + static const sd_json_dispatch_field table[] = { + { "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Context, write_iovec), SD_JSON_MANDATORY }, + { "interface", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(Context, ifindex), 0 }, + {} + }; + + r = sd_json_dispatch(m, table, SD_JSON_ALLOW_EXTENSIONS, c); + if (r < 0) { + context_fail(c, context_log_errno(c, LOG_ERR, r, "Failed to decode reply data: %m")); + return 0; + } + + if (c->write_stdout) { + r = loop_write(STDOUT_FILENO, c->write_iovec.iov_base, c->write_iovec.iov_len); + if (r < 0) { + context_fail(c, context_log_errno(c, LOG_ERR, r, "Failed to output data: %m")); + return 0; + } + } + + context_success(c); + return 0; +} + +static int context_load_ifname(Context *c) { + int r; + + assert(c); + + /* Tries to load the previously used interface name, so that we don't have to wildcard search on all + * interfaces. */ + + const char *e = context_get_runtime_directory(c); + if (!e) + return 0; + + _cleanup_close_ int dirfd = open(e, O_PATH|O_CLOEXEC); + if (dirfd < 0) + return context_log_errno(c, LOG_ERR, errno, "Failed to open runtime directory: %m"); + + _cleanup_free_ char *ifname = NULL; + r = read_one_line_file_at(dirfd, "ifname", &ifname); + if (r == -ENOENT) + return 0; + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to load 'ifname' file from runtime directory: %m"); + + if (!ifname_valid(ifname)) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EINVAL), "Loaded interface name not valid, refusing: %s", ifname); + + c->ifindex = rtnl_resolve_interface(&c->rtnl, ifname); + if (c->ifindex < 0) { + (void) unlinkat(dirfd, "ifname", /* flags= */ 0); + context_log_errno(c, LOG_ERR, c->ifindex, "Failed to resolve saved interface name '%s', assuming interface disappeared, ignoring: %m", ifname); + c->ifindex = 0; + return 0; + } + + log_debug("Using previously pinned interface '%s' (ifindex: %i).", ifname, c->ifindex); + return 1; +} + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + child_data_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + ChildData, + child_data_free); + +static int context_spawn_child(Context *c, int ifindex, sd_varlink **ret) { + int r; + + assert(c); + assert(ifindex > 0); + assert(ret); + + /* If we don't know yet on which network interface the IMDS server can be found, let's spawn separate + * instances of ourselves, one for each interface, and collect the results. We communicate with + * each one via Varlink, the same way as clients talk to us. */ + + context_log(c, LOG_DEBUG, "Spawning child for interface '%i'.", ifindex); + + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = pin_callout_binary(LIBEXECDIR "/systemd-imdsd", &p); + if (fd < 0) + return context_log_errno(c, LOG_ERR, fd, "Failed to find imdsd binary: %m"); + + _cleanup_strv_free_ char **argv = strv_new( + p, + "--vendor", strempty(arg_vendor), + "--token-url", strempty(arg_token_url), + "--refresh-header-name", strempty(arg_refresh_header_name), + "--data-url", strempty(arg_data_url), + "--data-url-suffix", strempty(arg_data_url_suffix), + "--token-header-name", strempty(arg_token_header_name), + "--address-ipv4", in4_addr_is_null(&arg_address_ipv4) ? "" : IN4_ADDR_TO_STRING(&arg_address_ipv4), + "--address-ipv6", in6_addr_is_null(&arg_address_ipv6) ? "" : IN6_ADDR_TO_STRING(&arg_address_ipv6)); + if (!argv) + return log_oom(); + + STRV_FOREACH(i, arg_extra_header) + if (strv_extend_strv(&argv, STRV_MAKE("--extra-header", *i), /* filter_duplicates= */ false) < 0) + return log_oom(); + + for (ImdsWellKnown wk = 0; wk < _IMDS_WELL_KNOWN_MAX; wk++) { + if (!arg_well_known_key[wk]) + continue; + + if (strv_extendf(&argv, "--well-known-key=%s:%s", imds_well_known_to_string(wk), arg_well_known_key[wk]) < 0) + return log_oom(); + } + + if (DEBUG_LOGGING) { + _cleanup_free_ char *cmdline = quote_command_line(argv, SHELL_ESCAPE_EMPTY); + log_debug("About to fork off: %s", strnull(cmdline)); + } + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = sd_varlink_connect_exec(&vl, p, argv); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to fork off imdsd binary for interface %i: %m", ifindex); + + r = sd_varlink_attach_event( + vl, + c->event, + SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to attach Varlink connection to event loop: %m"); + + r = sd_varlink_bind_reply(vl, vl_on_reply); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to bind reply callback: %m"); + + r = sd_varlink_invokebo( + vl, + "io.systemd.InstanceMetadata.Get", + JSON_BUILD_PAIR_STRING_NON_EMPTY("key", c->key), + SD_JSON_BUILD_PAIR_CONDITION(c->well_known >= 0, "wellKnown", JSON_BUILD_STRING_UNDERSCORIFY(imds_well_known_to_string(c->well_known))), + SD_JSON_BUILD_PAIR_INTEGER("interface", ifindex), + SD_JSON_BUILD_PAIR_INTEGER("refreshUSec", c->refresh_usec), + SD_JSON_BUILD_PAIR_BOOLEAN("cache", c->cache), + SD_JSON_BUILD_PAIR_CONDITION(c->fwmark_set, "firewallMark", SD_JSON_BUILD_UNSIGNED(c->fwmark)), + SD_JSON_BUILD_PAIR_CONDITION(!c->fwmark_set, "firewallMark", SD_JSON_BUILD_NULL)); /* explicitly turn off fwmark, if not set */ + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to issue Get() command to Varlink child: %m"); + + *ret = TAKE_PTR(vl); + return 0; +} + +static int context_spawn_new_child(Context *c, int ifindex) { + int r; + + assert(c); + + /* Spawn a child, and keep track of it */ + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = context_spawn_child(c, ifindex, &vl); + if (r < 0) + return r; + + _cleanup_(child_data_freep) ChildData *cd = new(ChildData, 1); + if (!cd) + return context_log_oom(c); + + *cd = (ChildData) { + .ifindex = ifindex, + .link = sd_varlink_ref(vl), + }; + + sd_varlink_set_userdata(vl, cd); + + if (hashmap_ensure_put(&c->child_data, &child_data_hash_ops, INT_TO_PTR(ifindex), cd) < 0) + return context_log_oom(c); + + cd->context = c; + TAKE_PTR(cd); + + return 0; +} + +static int on_address_change(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { + Context *c = ASSERT_PTR(userdata); + int ifindex, r; + + assert(rtnl); + assert(m); + + /* Called whenever an address appears on the network stack. We use that as hint that it is worth to + * invoke a child processing that interface (either for the first time, or again) */ + + r = sd_rtnl_message_addr_get_ifindex(m, &ifindex); + if (r < 0) { + context_log_errno(c, LOG_WARNING, r, "rtnl: could not get ifindex from message, ignoring: %m"); + return 0; + } + if (ifindex <= 0) { + context_log(c, LOG_WARNING, "rtnl: received address message with invalid ifindex %d, ignoring.", ifindex); + return 0; + } + + if (ifindex == LOOPBACK_IFINDEX) { + context_log(c, LOG_DEBUG, "Ignoring loopback device."); + return 0; + } + + if (!c->key && c->well_known < 0) + return 0; + + ChildData *existing = hashmap_get(c->child_data, INT_TO_PTR(ifindex)); + if (existing) { + /* We already have an attempt ongoing for this one? Remember there's a reason now to retry + * this, because new connectivity appeared. */ + context_log(c, LOG_DEBUG, "Child for network interface %i already spawned off, scheduling for immediate retry.", ifindex); + existing->retry = true; + return 0; + } + + return context_spawn_new_child(c, ifindex); +} + +static int context_acquire_rtnl_with_match(Context *c) { + int r; + + assert(c); + assert(c->event); + + /* Acquire a netlink connection and a match if we don't have one yet */ + + if (!c->rtnl) { + r = sd_netlink_open(&c->rtnl); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to connect to netlink: %m"); + } + + if (!c->rtnl_attached) { + /* The netlink connection might have created previously via rtnl_resolve_interface() – which + * however didn't attach it to our event loop. Do so now. */ + r = sd_netlink_attach_event(c->rtnl, c->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to attach netlink socket to event loop: %m"); + + c->rtnl_attached = true; + } + + if (!c->address_change_slot) { + r = sd_netlink_add_match(c->rtnl, &c->address_change_slot, RTM_NEWADDR, on_address_change, /* destroy_callback= */ NULL, c, "newaddr"); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to subscribe to RTM_NEWADDR events: %m"); + } + + return 0; +} + +static int context_spawn_children(Context *c) { + int r; + + assert(c); + assert(c->key || c->well_known >= 0); + + /* If we don't know yet on which interface to query, let's see which interfaces there are and spawn + * ourselves, once on each */ + + r = context_acquire_rtnl_with_match(c); + if (r < 0) + return r; + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + r = sd_rtnl_message_new_addr(c->rtnl, &req, RTM_GETADDR, /* ifindex= */ 0, AF_UNSPEC); + if (r < 0) + return r; + + r = sd_netlink_message_set_request_dump(req, true); + if (r < 0) + return r; + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *reply = NULL; + r = sd_netlink_call(c->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (sd_netlink_message *i = reply; i; i = sd_netlink_message_next(i)) { + r = on_address_change(c->rtnl, i, c); + if (r < 0) + return r; + } + + return 0; +} + +static int imds_configured(int level) { + /* Checks if we have enough endpoint information to operate */ + + if (arg_endpoint_source < 0) + return log_full_errno(level, SYNTHETIC_ERRNO(EOPNOTSUPP), "No IMDS endpoint information provided or detected, cannot operate."); + + if (!arg_data_url) + return log_full_errno(level, SYNTHETIC_ERRNO(EOPNOTSUPP), "No data base URL provided."); + + if (!!arg_token_url != !!arg_token_header_name) + return log_full_errno(level, SYNTHETIC_ERRNO(EOPNOTSUPP), "Incomplete token parameters configured for endpoint."); + + return 0; +} + +static int setup_network(void) { + int r; + + /* Generates a .network file based on the IMDS endpoint information we have */ + + if (arg_network_mode == IMDS_NETWORK_OFF) { + log_debug("IMDS networking turned off, not generating .network file."); + return 0; + } + + _cleanup_close_ int network_dir_fd = -EBADF; + r = chase("/run/systemd/network", + /* root= */ NULL, + CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + /* ret_path= */ NULL, + &network_dir_fd); + if (r < 0) + return log_error_errno(r, "Failed to open .network directory: %m"); + + _cleanup_free_ char *t = NULL; + _cleanup_fclose_ FILE *f = NULL; + r = fopen_tmpfile_linkable_at(network_dir_fd, "85-imds-early.network", O_WRONLY|O_CLOEXEC, &t, &f); + if (r < 0) + return log_error_errno(r, "Failed to create 85-imds-early.network file: %m"); + + CLEANUP_TMPFILE_AT(network_dir_fd, t); + + fputs("# Generated by systemd-imdsd, do not edit.\n" + "#\n" + "# This configures Ethernet devices on cloud hosts that support IMDS, given that\n" + "# before doing IMDS we need to activate the network.\n", f); + + if (arg_network_mode != IMDS_NETWORK_UNLOCKED && + (in4_addr_is_set(&arg_address_ipv4) || in6_addr_is_set(&arg_address_ipv6))) + fputs("#\n" + "# Note: this will create a 'prohibit' route to the IMDS endpoint,\n" + "# blocking direct access to IMDS. Direct IMDS access is then only\n" + "# available to traffic marked with fwmark 0x7FFF0815, which can be\n" + "# set via SO_MARK and various other methods, which require\n" + "# privileges.\n", + f); + + fputs("\n" + "[Match]\n" + "Type=ether\n" + "Kind=!*\n" + "\n" + "[Network]\n" + "DHCP=yes\n" + "LinkLocalAddressing=ipv6\n" + "\n" + "[DHCP]\n" + "UseTimezone=yes\n" + "UseHostname=yes\n" + "UseMTU=yes\n", f); + + if (in4_addr_is_set(&arg_address_ipv4)) + fputs("\n" + "[Link]\n" + "RequiredFamilyForOnline=ipv4\n", f); + else if (in6_addr_is_set(&arg_address_ipv6)) + fputs("\n" + "[Link]\n" + "RequiredFamilyForOnline=ipv6\n", f); + + if (arg_network_mode != IMDS_NETWORK_UNLOCKED) { + if (in4_addr_is_set(&arg_address_ipv4)) + fprintf(f, + "\n" + "# Prohibit regular access to IMDS (IPv4)\n" + "[Route]\n" + "Destination=%s\n" + "Type=prohibit\n", + IN4_ADDR_TO_STRING(&arg_address_ipv4)); + + if (in6_addr_is_set(&arg_address_ipv6)) + fprintf(f, + "\n" + "# Prohibit regular access to IMDS (IPv6)\n" + "[Route]\n" + "Destination=%s\n" + "Type=prohibit\n", + IN6_ADDR_TO_STRING(&arg_address_ipv6)); + } + + if (in4_addr_is_set(&arg_address_ipv4)) + fprintf(f, + "\n" + "# Always allow IMDS access via a special routing table (IPv4)\n" + "[Route]\n" + "Destination=%s\n" + "Scope=link\n" + "Table=0x7FFF0815\n" + "\n" + "# Sockets marked with firewall mark 0x7FFF0815 get access to the IMDS route by\n" + "# using the 0x7FFF0815 table populated above.\n" + "[RoutingPolicyRule]\n" + "Family=ipv4\n" + "FirewallMark=0x7FFF0815\n" + "Table=0x7FFF0815\n", + IN4_ADDR_TO_STRING(&arg_address_ipv4)); + + if (in6_addr_is_set(&arg_address_ipv6)) + fprintf(f, + "\n" + "# Always allow IMDS access via a special routing table (IPv6)\n" + "[Route]\n" + "Destination=%s\n" + "Table=0x7FFF0815\n" + "\n" + "# Sockets marked with firewall mark 0x7FFF0815 get access to the IMDS route by\n" + "# using the 0x7FFF0815 table populated above.\n" + "[RoutingPolicyRule]\n" + "Family=ipv6\n" + "FirewallMark=0x7FFF0815\n" + "Table=0x7FFF0815\n", + IN6_ADDR_TO_STRING(&arg_address_ipv6)); + + if (fchmod(fileno(f), 0644) < 0) + return log_error_errno(errno, "Failed to set access mode for 85-imds-early.network: %m"); + + r = flink_tmpfile_at(f, network_dir_fd, t, "85-imds-early.network", LINK_TMPFILE_REPLACE); + if (r < 0) + return log_error_errno(r, "Failed to move 85-imds-early.network into place: %m"); + + t = mfree(t); /* disarm auto-cleanup */ + + log_info("Created 85-imds-early.network."); + return 0; +} + +static int add_address_to_json_array(sd_json_variant **array, int family, const union in_addr_union *addr) { + int r; + + assert(array); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(addr); + + /* Appends the specified IP address, turned into A/AAAA RRs to the specified JSON array */ + + if (in_addr_is_null(family, addr)) + return 0; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + if (dns_resource_record_new_address(&rr, family, addr, "_imds") < 0) + return log_oom(); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *rrj = NULL; + r = dns_resource_record_to_json(rr, &rrj); + if (r < 0) + return log_error_errno(r, "Failed to convert A RR to JSON: %m"); + + r = sd_json_variant_append_array(array, rrj); + if (r < 0) + return log_error_errno(r, "Failed to append A RR to JSON array: %m"); + + log_debug("Writing IMDS RR for: %s", dns_resource_record_to_string(rr)); + return 1; +} + +static int setup_address_rrs(void) { + int r; + + /* Creates local RRs (honoured by systemd-resolved) for the IMDS endpoint addresses. */ + + if (arg_network_mode == IMDS_NETWORK_OFF) { + log_debug("IMDS networking turned off, not generating .rr file."); + return 0; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL; + + union in_addr_union u = { .in = arg_address_ipv4 }; + r = add_address_to_json_array(&aj, AF_INET, &u); + if (r < 0) + return r; + + u = (union in_addr_union) { .in6 = arg_address_ipv6 }; + r = add_address_to_json_array(&aj, AF_INET6, &u); + if (r < 0) + return r; + + if (sd_json_variant_elements(aj) == 0) { + log_debug("No IMDS endpoint addresses known, not writing out RRs."); + return 0; + } + + _cleanup_free_ char *text = NULL; + r = sd_json_variant_format(aj, SD_JSON_FORMAT_NEWLINE, &text); + if (r < 0) + return log_error_errno(r, "Failed to format JSON text: %m"); + + r = write_string_file("/run/systemd/resolve/static.d/imds-endpoint.rr", text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755); + if (r < 0) + return log_error_errno(r, "Failed to write IMDS RR data: %m"); + + log_info("Created imds-endpoint.rr."); + return 0; +} + +static int on_overall_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + Context *c = ASSERT_PTR(userdata); + + assert(s); + + /* Invoked whenever the overall time-out event elapses, and we just give up */ + + context_fail_full(c, context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(ETIMEDOUT), "Overall timeout reached."), "io.systemd.InstanceMetadata.Timeout"); + return 0; +} + +static int context_start_overall_timeout(Context *c, usec_t usec) { + int r; + + assert(c); + + r = event_reset_time_relative( + c->event, + &c->overall_timeout_source, + CLOCK_BOOTTIME, + usec, + /* accuracy= */ 0, + on_overall_timeout, + c, + /* priority= */ 0, + "imds-overall-timeout", + /* force_reset= */ true); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to reset retry timer event source: %m"); + + return 0; +} + +static int cmdline_run(void) { + int r; + + /* Process the request when invoked via the command line (i.e. not via Varlink) */ + + r = imds_configured(LOG_ERR); + if (r < 0) + return r; + + if (arg_setup_network) { + r = setup_network(); + return RET_GATHER(r, setup_address_rrs()); + } + + assert(arg_key || arg_well_known >= 0); + + _cleanup_(context_done) Context c = CONTEXT_NULL; + c.write_stdout = true; + context_new_request(&c); + + c.well_known = arg_well_known; + if (arg_key) { + c.key = strdup(arg_key); + if (!c.key) + return context_log_oom(&c); + } + + if (arg_ifname) { + c.ifindex = rtnl_resolve_interface_or_warn(&c.rtnl, arg_ifname); + if (c.ifindex < 0) + return c.ifindex; + } else { + /* Try to load the previously cached interface */ + r = context_load_ifname(&c); + if (r < 0) + return r; + } + + r = sd_event_default(&c.event); + if (r < 0) + return context_log_errno(&c, LOG_ERR, r, "Failed to allocate event loop: %m"); + + if (c.ifindex > 0) { + CacheResult cr = context_process_cache(&c); + if (cr < 0) + return cr; + if (cr == CACHE_RESULT_HIT) + return 0; + if (cr == CACHE_RESULT_KEY_NOT_FOUND) + return context_log_errno(&c, LOG_ERR, SYNTHETIC_ERRNO(ENOENT), "Cache reports: key not found"); + + r = context_acquire_token(&c); + if (r < 0) + return r; + + r = context_acquire_data(&c); + if (r < 0) + return r; + + r = context_start_overall_timeout(&c, DIRECT_OVERALL_TIMEOUT_USEC); + if (r < 0) + return r; + } else { + /* Couldn't find anything, let's spawn off parallel clients for all interfaces */ + r = context_spawn_children(&c); + if (r < 0) + return r; + + r = context_start_overall_timeout(&c, INDIRECT_OVERALL_TIMEOUT_USEC); + if (r < 0) + return r; + } + + r = sd_event_loop(c.event); + if (r < 0) + return r; + + return 0; +} + +static int context_acquire_system_bus(Context *c) { + int r; + + assert(c); + + /* Connect to the bus if we haven't yet */ + + if (c->system_bus) + return 0; + + r = sd_bus_default_system(&c->system_bus); + if (r < 0) + return r; + + r = sd_bus_attach_event(c->system_bus, c->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return r; + + return 0; +} + +static JSON_DISPATCH_ENUM_DEFINE(dispatch_well_known, ImdsWellKnown, imds_well_known_from_string); + +static int dispatch_fwmark(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + Context *c = ASSERT_PTR(userdata); + int r; + + /* Parses a firewall mark passed via Varlink/JSON. Note that any 32bit fwmark is valid, hence we keep + * track if it is set or not in a separate boolean. */ + + if (sd_json_variant_is_null(variant)) { + c->fwmark_set = false; + return 0; + } + + r = sd_json_dispatch_uint32(name, variant, flags, &c->fwmark); + if (r < 0) + return r; + + c->fwmark_set = true; + return 0; +} + +static int vl_method_get(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(link); + + if (!c->event) + c->event = sd_event_ref(sd_varlink_get_event(link)); + + context_new_request(c); + + static const sd_json_dispatch_field dispatch_table[] = { + { "wellKnown", SD_JSON_VARIANT_STRING, dispatch_well_known, offsetof(Context, well_known), 0 }, + { "key", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Context, key), 0 }, + { "interface", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(Context, ifindex), 0 }, + { "refreshUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(Context, refresh_usec), 0 }, + { "firewallMark", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_fwmark, 0, 0 }, + { "cache", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Context, cache), 0 }, + { "wait", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Context, wait), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, c); + if (r != 0) + return r; + + if (c->key) { + if (!imds_key_is_valid(c->key)) + return sd_varlink_error_invalid_parameter_name(link, "key"); + + if (c->well_known < 0) + c->well_known = IMDS_BASE; + else if (!imds_well_known_can_suffix(c->well_known)) + return sd_varlink_error_invalid_parameter_name(link, "key"); + } else if (c->well_known < 0) + return sd_varlink_error_invalid_parameter_name(link, "key"); + + if (c->refresh_usec < REFRESH_USEC_MIN) + c->refresh_usec = REFRESH_USEC_MIN; + + uid_t peer_uid; + r = sd_varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + + if (peer_uid != 0 && peer_uid != getuid()) { + /* Ask polkit if client is not privileged */ + + r = context_acquire_system_bus(c); + if (r < 0) + return r; + + const char* l[5]; + size_t k = 0; + if (c->well_known >= 0) { + l[k++] = "wellKnown"; + l[k++] = imds_well_known_to_string(c->well_known); + } + if (c->key) { + l[k++] = "key"; + l[k++] = c->key; + } + l[k] = NULL; + + r = varlink_verify_polkit_async( + link, + c->system_bus, + "io.systemd.imds.get", + l, + &c->polkit_registry); + if (r <= 0) + return r; + } + + if (imds_configured(LOG_DEBUG) < 0) + return sd_varlink_error(link, "io.systemd.InstanceMetadata.NotSupported", NULL); + + /* Up to this point we only validated/parsed stuff. Now we actually execute stuff, hence from now on + * we need to go through context_fail() when failing (context_success() if we succeed early), to + * release resources we might have allocated. */ + assert(!c->current_link); + c->current_link = sd_varlink_ref(link); + + _cleanup_free_ char *k = NULL; /* initialize here, to avoid that this remains uninitialized due to the gotos below */ + + if (c->ifindex <= 0) { + /* Try to load the previously used network interface */ + r = context_load_ifname(c); + if (r < 0) + goto fail; + } + + r = context_combine_key(c, &k); + if (r == -ENODATA) { + context_fail_full(c, r, "io.systemd.InstanceMetadata.WellKnownKeyUnset"); + return r; + } + if (r < 0) + goto fail; + + context_log(c, LOG_DEBUG, "Will request '%s' now.", k); + + if (c->ifindex > 0) { + CacheResult cr = context_process_cache(c); + if (cr < 0) { + r = cr; + goto fail; + } + if (cr == CACHE_RESULT_HIT) { + context_success(c); + return 0; + } + if (cr == CACHE_RESULT_KEY_NOT_FOUND) { + r = context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(ENOENT), "Cache reports: key not found"); + context_fail_full(c, r, "io.systemd.InstanceMetadata.KeyNotFound"); + return r; + } + + r = context_acquire_token(c); + if (r < 0) + goto fail; + + r = context_acquire_data(c); + if (r < 0) + goto fail; + + r = context_start_overall_timeout(c, DIRECT_OVERALL_TIMEOUT_USEC); + if (r < 0) + goto fail; + } else { + r = context_spawn_children(c); + if (r < 0) + goto fail; + + r = context_start_overall_timeout(c, INDIRECT_OVERALL_TIMEOUT_USEC); + if (r < 0) + goto fail; + } + + context_log(c, LOG_DEBUG, "Incoming method call is now pending"); + return 1; + +fail: + context_fail(c, r); + return r; +} + +static int vl_method_get_vendor_info(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(link); + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, c); + if (r != 0) + return r; + + /* NB! We allow access to this call without Polkit */ + + if (imds_configured(LOG_DEBUG) < 0) + return sd_varlink_error(link, "io.systemd.InstanceMetadata.NotSupported", NULL); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *wkj = NULL; + for (ImdsWellKnown i = 0; i < _IMDS_WELL_KNOWN_MAX; i++) { + if (!arg_well_known_key[i]) + continue; + + r = sd_json_variant_set_field_string(&wkj, imds_well_known_to_string(i), arg_well_known_key[i]); + if (r < 0) + return r; + } + + return sd_varlink_replybo( + link, + JSON_BUILD_PAIR_STRING_NON_EMPTY("vendor", arg_vendor), + JSON_BUILD_PAIR_STRING_NON_EMPTY("tokenUrl", arg_token_url), + JSON_BUILD_PAIR_STRING_NON_EMPTY("refreshHeaderName", arg_refresh_header_name), + JSON_BUILD_PAIR_STRING_NON_EMPTY("dataUrl", arg_data_url), + JSON_BUILD_PAIR_STRING_NON_EMPTY("dataUrlSuffix", arg_data_url_suffix), + JSON_BUILD_PAIR_STRING_NON_EMPTY("tokenHeaderName", arg_token_header_name), + JSON_BUILD_PAIR_STRV_NON_EMPTY("extraHeader", arg_extra_header), + JSON_BUILD_PAIR_IN4_ADDR_NON_NULL("addressIPv4", &arg_address_ipv4), + JSON_BUILD_PAIR_IN6_ADDR_NON_NULL("addressIPv6", &arg_address_ipv6), + JSON_BUILD_PAIR_VARIANT_NON_EMPTY("wellKnown", wkj)); +} + +static int vl_server(void) { + _cleanup_(context_done) Context c = CONTEXT_NULL; + int r; + + /* Invocation as Varlink service */ + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; + r = varlink_server_new( + &varlink_server, + SD_VARLINK_SERVER_INHERIT_USERDATA, + &c); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_InstanceMetadata); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = sd_varlink_server_bind_method_many( + varlink_server, + "io.systemd.InstanceMetadata.Get", vl_method_get, + "io.systemd.InstanceMetadata.GetVendorInfo", vl_method_get_vendor_info); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %m"); + + r = sd_varlink_server_loop_auto(varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return 0; +} + +static int help(void) { + _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *endpoint_options = NULL; + int r; + + r = terminal_urlify_man("systemd-imdsd@.service", "8", &link); + if (r < 0) + return log_oom(); + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("Manual Endpoint Configuration", &endpoint_options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options, endpoint_options); + + printf("%1$s [OPTIONS...] KEY\n" + "\n%2$sLow-level IMDS data acquisition.%3$s\n" + "\n%4$sOptions:%5$s\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\n%sManual Endpoint Configuration:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(endpoint_options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); + return 0; +} + +static bool http_header_name_valid(const char *a) { + return a && ascii_is_valid(a) && !string_has_cc(a, /* ok= */ NULL) && !strchr(a, ':'); +} + +static int parse_argv(int argc, char *argv[]) { + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('i', "interface", "INTERFACE", "Use the specified interface"): + if (isempty(arg)) { + arg_ifname = mfree(arg_ifname); + break; + } + + if (!ifname_valid_full(arg, IFNAME_VALID_ALTERNATIVE|IFNAME_VALID_NUMERIC)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Interface name '%s' is not valid.", arg); + + r = free_and_strdup_warn(&arg_ifname, arg); + if (r < 0) + return r; + + break; + + OPTION_LONG("refresh", "SEC", "Set token refresh time"): { + if (isempty(arg)) { + arg_refresh_usec = REFRESH_USEC_DEFAULT; + break; + } + + usec_t t; + r = parse_sec(arg, &t); + if (r < 0) + return log_error_errno(r, "Failed to parse refresh timeout: %s", arg); + if (t < REFRESH_USEC_MIN) { + log_warning("Increasing specified refresh time to %s, lower values are not supported.", FORMAT_TIMESPAN(REFRESH_USEC_MIN, 0)); + arg_refresh_usec = REFRESH_USEC_MIN; + } else + arg_refresh_usec = t; + break; + } + + OPTION_LONG("fwmark", "INTEGER", "Choose firewall mark for HTTP traffic"): + if (isempty(arg)) { + arg_fwmark_set = false; + break; + } + + if (streq(arg, "default")) { + arg_fwmark = FWMARK_DEFAULT; + arg_fwmark_set = true; + break; + } + + r = safe_atou32(arg, &arg_fwmark); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse --fwmark= parameter: %s", arg); + + arg_fwmark_set = true; + break; + + OPTION_LONG("cache", "BOOL", "Enable/disable cache use"): + r = parse_boolean_argument("--cache", arg, &arg_cache); + if (r < 0) + return r; + break; + + OPTION_LONG("wait", "BOOL", "Whether to wait for connectivity"): + r = parse_boolean_argument("--wait", arg, &arg_wait); + if (r < 0) + return r; + break; + + OPTION_SHORT('w', NULL, "Same as --wait=yes"): + arg_wait = true; + break; + + OPTION('K', "well-known", "KEY", "Select well-known key"): { + if (isempty(arg)) { + arg_well_known = _IMDS_WELL_KNOWN_INVALID; + break; + } + + ImdsWellKnown wk = imds_well_known_from_string(arg); + if (wk < 0) + return log_error_errno(wk, "Failed to parse --well-known= parameter: %m"); + + arg_well_known = wk; + break; + } + + OPTION_LONG("setup-network", NULL, "Generate .network and .rr files"): + arg_setup_network = true; + break; + + /* The following all configure endpoint information explicitly */ + OPTION_GROUP("Manual Endpoint Configuration"): {} + + OPTION_LONG("vendor", "VENDOR", "Specify IMDS vendor literally"): + if (isempty(arg)) { + arg_vendor = mfree(arg_vendor); + break; + } + + r = free_and_strdup_warn(&arg_vendor, arg); + if (r < 0) + return r; + break; + + OPTION_LONG("token-url", "URL", "URL for acquiring token"): + if (isempty(arg)) { + arg_token_url = mfree(arg_token_url); + break; + } + + if (!http_url_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL: %s", arg); + + r = free_and_strdup_warn(&arg_token_url, arg); + if (r < 0) + return r; + break; + + OPTION_LONG("refresh-header-name", "NAME", "Header name for passing refresh time"): + if (isempty(arg)) { + arg_refresh_header_name = mfree(arg_refresh_header_name); + break; + } + + if (!http_header_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header name: %s", arg); + + r = free_and_strdup_warn(&arg_refresh_header_name, arg); + if (r < 0) + return r; + break; + + OPTION_LONG("data-url", "URL", "Base URL for acquiring data"): + if (isempty(arg)) { + arg_data_url = mfree(arg_data_url); + break; + } + + if (!http_url_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL: %s", arg); + + r = free_and_strdup_warn(&arg_data_url, arg); + if (r < 0) + return r; + break; + + OPTION_LONG("data-url-suffix", "STRING", "Suffix to append to data URL"): + if (isempty(arg)) { + arg_data_url_suffix = mfree(arg_data_url_suffix); + break; + } + + if (!ascii_is_valid(arg) || string_has_cc(arg, /* ok= */ NULL)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL suffix: %s", arg); + + r = free_and_strdup_warn(&arg_data_url_suffix, arg); + if (r < 0) + return r; + break; + + OPTION_LONG("token-header-name", "NAME", "Header name for passing token string"): + if (isempty(arg)) { + arg_token_header_name = mfree(arg_token_header_name); + break; + } + + if (!http_header_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header name: %s", arg); + + r = free_and_strdup_warn(&arg_token_header_name, arg); + if (r < 0) + return r; + break; + + OPTION_LONG("extra-header", "NAME: VALUE", "Additional header to pass to data transfer"): + if (isempty(arg)) { + arg_extra_header = strv_free(arg_extra_header); + break; + } + + if (!http_header_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header: %s", arg); + + if (strv_extend(&arg_extra_header, arg) < 0) + return log_oom(); + break; + + OPTION_LONG("address-ipv4", "ADDRESS", "Configure IPv4 address of the IMDS server"): { + if (isempty(arg)) { + arg_address_ipv4 = (struct in_addr) {}; + break; + } + + union in_addr_union u; + r = in_addr_from_string(AF_INET, arg, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse IPv4 address: %s", arg); + arg_address_ipv4 = u.in; + break; + } + + OPTION_LONG("address-ipv6", "ADDRESS", "Configure IPv6 address of the IMDS server"): { + if (isempty(arg)) { + arg_address_ipv6 = (struct in6_addr) {}; + break; + } + + union in_addr_union u; + r = in_addr_from_string(AF_INET6, arg, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse IPv6 address: %s", arg); + arg_address_ipv6 = u.in6; + break; + } + + OPTION_LONG("well-known-key", "NAME:KEY", "Configure the location of well-known keys"): { + if (isempty(arg)) { + for (ImdsWellKnown wk = 0; wk < _IMDS_WELL_KNOWN_MAX; wk++) + arg_well_known_key[wk] = mfree(arg_well_known_key[wk]); + break; + } + + const char *e = strchr(arg, ':'); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--well-known-key= expects colon separated name and key pairs."); + + _cleanup_free_ char *name = strndup(arg, e - arg); + if (!name) + return log_oom(); + + ImdsWellKnown wk = imds_well_known_from_string(name); + if (wk < 0) + return log_error_errno(wk, "Failed to parse --well-known-key= argument: %m"); + + e++; + if (!imds_key_is_valid(e)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Well known key '%s' is not valid.", e); + + r = free_and_strdup_warn(arg_well_known_key + wk, e); + if (r < 0) + return r; + break; + } + } + + if (arg_vendor || arg_token_url || arg_refresh_header_name || arg_data_url || arg_data_url_suffix || arg_token_header_name || arg_extra_header) + arg_endpoint_source = ENDPOINT_USER; + + r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); + if (r < 0) + return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); + + arg_varlink = r; + + if (!arg_varlink) { + char **args = option_parser_get_args(&state); + size_t n_args = option_parser_get_n_args(&state); + + if (arg_setup_network) { + if (n_args != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No argument expected."); + } else { + if (arg_well_known < 0) { + /* if no --well-known= parameter was specified we require an argument */ + if (n_args != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "A single argument expected."); + } else if (n_args > 1) /* if not, then the additional parameter is optional */ + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At most a single argument expected."); + + if (n_args > 0) { + if (!imds_key_is_valid(args[0])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified IMDS key is not valid, refusing: %s", args[0]); + + r = free_and_strdup_warn(&arg_key, args[0]); + if (r < 0) + return r; + } + } + } + + return 1; +} + +static int device_get_property_ip_address( + sd_device *d, + const char *name, + int family, + union in_addr_union *ret) { + + int r; + + /* Parses an IP address stored in the udev database for a device */ + + assert(d); + assert(name); + assert(IN_SET(family, AF_INET, AF_INET6)); + + const char *v = NULL; + r = sd_device_get_property_value(d, name, &v); + if (r < 0) + return r; + + return in_addr_from_string(family, v, ret); +} + +static const char * const imds_well_known_udev_table[_IMDS_WELL_KNOWN_MAX] = { + [IMDS_HOSTNAME] = "IMDS_KEY_HOSTNAME", + [IMDS_REGION] = "IMDS_KEY_REGION", + [IMDS_ZONE] = "IMDS_KEY_ZONE", + [IMDS_IPV4_PUBLIC] = "IMDS_KEY_IPV4_PUBLIC", + [IMDS_IPV6_PUBLIC] = "IMDS_KEY_IPV6_PUBLIC", + [IMDS_SSH_KEY] = "IMDS_KEY_SSH_KEY", + [IMDS_USERDATA] = "IMDS_KEY_USERDATA", + [IMDS_USERDATA_BASE] = "IMDS_KEY_USERDATA_BASE", + [IMDS_USERDATA_BASE64] = "IMDS_KEY_USERDATA_BASE64", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(imds_well_known_udev, ImdsWellKnown); + +static int smbios_server_info(void) { + int r; + + /* Acquires IMDS server information from udev/hwdb */ + + if (arg_endpoint_source >= 0) + return 0; + + _cleanup_(sd_device_unrefp) sd_device *d = NULL; + r = sd_device_new_from_syspath(&d, "/sys/class/dmi/id/"); + if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) { + log_debug_errno(r, "Failed to open /sys/class/dmi/id/ device, ignoring: %m"); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to open /sys/class/dmi/id/ device: %m"); + + const char *vendor; + r = sd_device_get_property_value(d, "IMDS_VENDOR", &vendor); + if (r == -ENOENT) { + log_debug_errno(r, "IMDS_VENDOR= property not set on DMI device, skipping."); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to read IMDS_VENDOR= property of DMI device: %m"); + + log_debug("Detected IMDS vendor support '%s'.", vendor); + + r = free_and_strdup_warn(&arg_vendor, vendor); + if (r < 0) + return r; + + struct { + const char *property; + char **variable; + } table[] = { + { "IMDS_TOKEN_URL", &arg_token_url }, + { "IMDS_REFRESH_HEADER_NAME", &arg_refresh_header_name }, + { "IMDS_DATA_URL", &arg_data_url }, + { "IMDS_DATA_URL_SUFFIX", &arg_data_url_suffix }, + { "IMDS_TOKEN_HEADER_NAME", &arg_token_header_name }, + }; + + FOREACH_ELEMENT(i, table) { + const char *v = NULL; + + r = sd_device_get_property_value(d, i->property, &v); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to read property '%s' of DMI: %m", i->property); + + r = free_and_strdup_warn(i->variable, v); + if (r < 0) + return r; + } + + for (size_t i = 0; i < 64U; i++) { + _cleanup_free_ char *property = NULL; + const char *p = NULL; + if (i > 0) { + if (asprintf(&property, "IMDS_EXTRA_HEADER%zu", i + 1) < 0) + return log_oom(); + p = property; + } else + p = "IMDS_EXTRA_HEADER"; + + const char *v = NULL; + r = sd_device_get_property_value(d, p, &v); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to read property '%s' of DMI: %m", p); + + if (v) + if (strv_extend(&arg_extra_header, v) < 0) + return log_oom(); + } + + union in_addr_union u; + r = device_get_property_ip_address(d, "IMDS_ADDRESS_IPV4", AF_INET, &u); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to read property 'IMDS_ADDRESS_IPV4' of DMI: %m"); + else if (r >= 0) + arg_address_ipv4 = u.in; + + r = device_get_property_ip_address(d, "IMDS_ADDRESS_IPV6", AF_INET6, &u); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to read property 'IMDS_ADDRESS_IPV6' of DMI: %m"); + else if (r >= 0) + arg_address_ipv6 = u.in6; + + for (ImdsWellKnown k = 0; k < _IMDS_WELL_KNOWN_MAX; k++) { + const char *p = imds_well_known_udev_to_string(k); + if (!p) + continue; + + const char *v = NULL; + r = sd_device_get_property_value(d, p, &v); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to read property '%s' of DMI: %m", p); + + r = free_and_strdup_warn(arg_well_known_key + k, v); + if (r < 0) + return r; + } + + log_debug("IMDS endpoint data set from SMBIOS device."); + arg_endpoint_source = ENDPOINT_UDEV; + return 0; +} + +static int secure_getenv_ip_address( + const char *name, + int family, + union in_addr_union *ret) { + + assert(name); + assert(IN_SET(family, AF_INET, AF_INET6)); + + /* Parses an IP address specified in an environment variable */ + + const char *e = secure_getenv(name); + if (!e) + return -ENXIO; + + return in_addr_from_string(family, e, ret); +} + +static const char * const imds_well_known_environment_table[_IMDS_WELL_KNOWN_MAX] = { + [IMDS_HOSTNAME] = "SYSTEMD_IMDS_KEY_HOSTNAME", + [IMDS_REGION] = "SYSTEMD_IMDS_KEY_REGION", + [IMDS_ZONE] = "SYSTEMD_IMDS_KEY_ZONE", + [IMDS_IPV4_PUBLIC] = "SYSTEMD_IMDS_KEY_IPV4_PUBLIC", + [IMDS_IPV6_PUBLIC] = "SYSTEMD_IMDS_KEY_IPV6_PUBLIC", + [IMDS_SSH_KEY] = "SYSTEMD_IMDS_KEY_SSH_KEY", + [IMDS_USERDATA] = "SYSTEMD_IMDS_KEY_USERDATA", + [IMDS_USERDATA_BASE] = "SYSTEMD_IMDS_KEY_USERDATA_BASE", + [IMDS_USERDATA_BASE64] = "SYSTEMD_IMDS_KEY_USERDATA_BASE64", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(imds_well_known_environment, ImdsWellKnown); + +static int environment_server_info(void) { + int r; + + /* Acquires IMDS endpoint info from environment variables */ + + if (arg_endpoint_source >= 0) + return 0; + + static const struct { + const char *name; + char **variable; + } table[] = { + { "SYSTEMD_IMDS_VENDOR", &arg_vendor }, + { "SYSTEMD_IMDS_TOKEN_URL", &arg_token_url }, + { "SYSTEMD_IMDS_REFRESH_HEADER_NAME", &arg_refresh_header_name }, + { "SYSTEMD_IMDS_DATA_URL", &arg_data_url }, + { "SYSTEMD_IMDS_DATA_URL_SUFFIX", &arg_data_url_suffix }, + { "SYSTEMD_IMDS_TOKEN_HEADER_NAME", &arg_token_header_name }, + }; + + FOREACH_ELEMENT(i, table) { + const char *e = secure_getenv(i->name); + if (!e) + continue; + + r = free_and_strdup_warn(i->variable, e); + if (r < 0) + return r; + + arg_endpoint_source = ENDPOINT_ENVIRONMENT; + } + + for (unsigned u = 1; u < 64; u++) { + _cleanup_free_ char *name = NULL; + + if (u > 1 && asprintf(&name, "SYSTEMD_IMDS_EXTRA_HEADER%u", u) < 0) + return log_oom(); + + const char *e = secure_getenv(name ?: "SYSTEMD_IMDS_EXTRA_HEADER"); + if (!e) + break; + + if (strv_extend(&arg_extra_header, e) < 0) + return log_oom(); + + arg_endpoint_source = ENDPOINT_ENVIRONMENT; + } + + union in_addr_union u; + r = secure_getenv_ip_address("SYSTEMD_IMDS_ADDRESS_IPV4", AF_INET, &u); + if (r < 0 && r != -ENXIO) + return log_error_errno(r, "Failed read IPv4 address from environment variable 'SYSTEMD_IMDS_ADDRESS_IPV4': %m"); + if (r >= 0) { + arg_address_ipv4 = u.in; + arg_endpoint_source = ENDPOINT_ENVIRONMENT; + } + + r = secure_getenv_ip_address("SYSTEMD_IMDS_ADDRESS_IPV6", AF_INET6, &u); + if (r < 0 && r != -ENXIO) + return log_error_errno(r, "Failed read IPv6 address from environment variable 'SYSTEMD_IMDS_ADDRESS_IPV6': %m"); + if (r >= 0) { + arg_address_ipv6 = u.in6; + arg_endpoint_source = ENDPOINT_ENVIRONMENT; + } + + for (ImdsWellKnown k = 0; k < _IMDS_WELL_KNOWN_MAX; k++) { + const char *n = imds_well_known_environment_to_string(k); + if (!n) + continue; + + const char *e = secure_getenv(n); + if (!e) + continue; + + r = free_and_strdup_warn(arg_well_known_key + k, e); + if (r < 0) + return r; + + arg_endpoint_source = ENDPOINT_ENVIRONMENT; + } + + if (arg_endpoint_source >= 0) + log_debug("IMDS endpoint data set from environment."); + + return 0; +} + +static int read_credential_ip_address( + const char *name, + int family, + union in_addr_union *ret) { + + int r; + + assert(name); + assert(IN_SET(family, AF_INET, AF_INET6)); + + /* Parses an IP address specified in a credential */ + + _cleanup_free_ char *s = NULL; + r = read_credential(name, (void**) &s, /* ret_size= */ NULL); + if (r < 0) + return r; + + return in_addr_from_string(family, s, ret); +} + +static const char * const imds_well_known_credential_table[_IMDS_WELL_KNOWN_MAX] = { + [IMDS_HOSTNAME] = "imds.key_hostname", + [IMDS_REGION] = "imds.key_region", + [IMDS_ZONE] = "imds.key_zone", + [IMDS_IPV4_PUBLIC] = "imds.key_ipv4_public", + [IMDS_IPV6_PUBLIC] = "imds.key_ipv6_public", + [IMDS_SSH_KEY] = "imds.key_ssh_key", + [IMDS_USERDATA] = "imds.key_userdata", + [IMDS_USERDATA_BASE] = "imds.key_userdata_base", + [IMDS_USERDATA_BASE64] = "imds.key_userdata_base64", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(imds_well_known_credential, ImdsWellKnown); + +static int credential_server_info(void) { + int r; + + /* Acquires IMDS endpoint info from credentials */ + + if (arg_endpoint_source >= 0) + return 0; + + static const struct { + const char *name; + char **variable; + } table[] = { + { "imds.vendor", &arg_vendor }, + { "imds.vendor_token", &arg_token_url }, + { "imds.refresh_header_name", &arg_refresh_header_name }, + { "imds.data_url", &arg_data_url }, + { "imds.data_url_suffix", &arg_data_url_suffix }, + { "imds.token_header_name", &arg_token_header_name }, + }; + + FOREACH_ELEMENT(i, table) { + _cleanup_free_ char *s = NULL; + + r = read_credential(i->name, (void**) &s, /* ret_size= */ NULL); + if (r == -ENOENT) + continue; + if (r < 0) { + log_warning_errno(r, "Failed to read credential '%s', ignoring: %m", i->name); + continue; + } + + r = free_and_strdup_warn(i->variable, s); + if (r < 0) + return r; + + arg_endpoint_source = ENDPOINT_CREDENTIALS; + } + + for (unsigned u = 1; u < 64; u++) { + _cleanup_free_ char *name = NULL; + if (u > 1 && asprintf(&name, "imds.extra_header%u", u) < 0) + return log_oom(); + + const char *n = name ?: "imds.extra_header"; + + _cleanup_free_ char *s = NULL; + r = read_credential(n, (void**) &s, /* ret_size= */ NULL); + if (r == -ENOENT) + continue; + if (r < 0) { + log_warning_errno(r, "Failed to read credential '%s', ignoring: %m", n); + continue; + } + + if (strv_extend(&arg_extra_header, s) < 0) + return log_oom(); + + arg_endpoint_source = ENDPOINT_CREDENTIALS; + } + + union in_addr_union u; + r = read_credential_ip_address("imds.address_ipv4", AF_INET, &u); + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed read IPv4 address from credential 'imds.address_ipv4', ignoring: %m"); + if (r >= 0) { + arg_address_ipv4 = u.in; + arg_endpoint_source = ENDPOINT_CREDENTIALS; + } + + r = read_credential_ip_address("imds.address_ipv6", AF_INET6, &u); + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed read IPv6 address from credential 'imds.address_ipv6', ignoring: %m"); + if (r >= 0) { + arg_address_ipv6 = u.in6; + arg_endpoint_source = ENDPOINT_CREDENTIALS; + } + + for (ImdsWellKnown k = 0; k < _IMDS_WELL_KNOWN_MAX; k++) { + const char *n = imds_well_known_credential_to_string(k); + if (!n) + continue; + + _cleanup_free_ char *s = NULL; + r = read_credential(n, (void**) &s, /* ret_size= */ NULL); + if (r == -ENOENT) + continue; + if (r < 0) { + log_warning_errno(r, "Failed to read credential '%s', ignoring: %m", n); + continue; + } + + free_and_replace(arg_well_known_key[k], s); + arg_endpoint_source = ENDPOINT_CREDENTIALS; + } + + if (arg_endpoint_source >= 0) + log_debug("IMDS endpoint data set from credentials."); + + return 0; +} + +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + int r; + + assert(key); + + /* Called for each kernel command line option. */ + + if (proc_cmdline_key_streq(key, "systemd.imds.network")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + ImdsNetworkMode m = imds_network_mode_from_string(value); + if (m < 0) + return log_warning_errno(m, "Failed to parse systemd.imds.network= value: %m"); + + arg_network_mode = m; + return 0; + } + + /* The other kernel command line options configured IMDS endpoint data. We'll only check it if no + * other configuration source for it has been used */ + if (arg_endpoint_source >= 0 && arg_endpoint_source != ENDPOINT_PROC_CMDLINE) + return 0; + + static const struct { + const char *key; + char **variable; + } table[] = { + { "systemd.imds.vendor", &arg_vendor }, + { "systemd.imds.token_url", &arg_token_url }, + { "systemd.imds.refresh_header_name", &arg_refresh_header_name }, + { "systemd.imds.data_url", &arg_data_url }, + { "systemd.imds.data_url_suffix", &arg_data_url_suffix }, + { "systemd.imds.token_header_name", &arg_token_header_name }, + }; + + FOREACH_ELEMENT(i, table) { + if (!proc_cmdline_key_streq(key, i->key)) + continue; + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup_warn(i->variable, value); + if (r < 0) + return r; + + arg_endpoint_source = ENDPOINT_PROC_CMDLINE; + return 0; + } + + if (proc_cmdline_key_streq(key, "systemd.imds.extra_header")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + if (isempty(value)) + arg_extra_header = strv_free(arg_extra_header); + else if (strv_extend(&arg_extra_header, value) < 0) + return log_oom(); + + arg_endpoint_source = ENDPOINT_PROC_CMDLINE; + return 0; + } + + if (proc_cmdline_key_streq(key, "systemd.imds.address_ipv4")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + union in_addr_union u; + r = in_addr_from_string(AF_INET, value, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse 'systemd.imds.address_ipv4=' parameter: %s", value); + + arg_address_ipv4 = u.in; + arg_endpoint_source = ENDPOINT_PROC_CMDLINE; + return 0; + } + + if (proc_cmdline_key_streq(key, "systemd.imds.address_ipv6")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + union in_addr_union u; + r = in_addr_from_string(AF_INET6, value, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse 'systemd.imds.address_ipv6=' parameter: %s", value); + + arg_address_ipv6 = u.in6; + arg_endpoint_source = ENDPOINT_PROC_CMDLINE; + return 0; + } + + static const char * const well_known_table[_IMDS_WELL_KNOWN_MAX] = { + [IMDS_HOSTNAME] = "systemd.imds.key.hostname", + [IMDS_REGION] = "systemd.imds.key.region", + [IMDS_ZONE] = "systemd.imds.key.zone", + [IMDS_IPV4_PUBLIC] = "systemd.imds.key.ipv4_public", + [IMDS_IPV6_PUBLIC] = "systemd.imds.key.ipv6_public", + [IMDS_SSH_KEY] = "systemd.imds.key.ssh_key", + [IMDS_USERDATA] = "systemd.imds.key.userdata", + [IMDS_USERDATA_BASE] = "systemd.imds.key.userdata_base", + [IMDS_USERDATA_BASE64] = "systemd.imds.key.userdata_base64", + }; + + for (ImdsWellKnown wk = 0; wk < _IMDS_WELL_KNOWN_MAX; wk++) { + const char *k = well_known_table[wk]; + if (!k) + continue; + + if (!proc_cmdline_key_streq(key, k)) + continue; + + r = free_and_strdup_warn(arg_well_known_key + wk, value); + if (r < 0) + return r; + + arg_endpoint_source = ENDPOINT_PROC_CMDLINE; + return 0; + } + + return 0; +} + +static int run(int argc, char* argv[]) { + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + r = dlopen_curl(LOG_DEBUG); + if (r < 0) + return r; + + r = environment_server_info(); + if (r < 0) + return r; + + r = proc_cmdline_parse(parse_proc_cmdline_item, /* userdata= */ NULL, PROC_CMDLINE_STRIP_RD_PREFIX); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + r = credential_server_info(); + if (r < 0) + return r; + + r = smbios_server_info(); + if (r < 0) + return r; + + if (arg_varlink) + return vl_server(); + + return cmdline_run(); +} + +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/imds/io.systemd.imds.policy b/src/imds/io.systemd.imds.policy new file mode 100644 index 0000000000000..e844f60b600bc --- /dev/null +++ b/src/imds/io.systemd.imds.policy @@ -0,0 +1,30 @@ + + + + + + + + The systemd Project + https://systemd.io + + + Acquire IMDS instance metadata. + Authentication is required for an application to acquire IMDS instance metadata. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + diff --git a/src/imds/meson.build b/src/imds/meson.build new file mode 100644 index 0000000000000..9295c8cf620c9 --- /dev/null +++ b/src/imds/meson.build @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +if conf.get('ENABLE_IMDS') != 1 + subdir_done() +endif + +executables += [ + libexec_template + { + 'name' : 'systemd-imdsd', + 'public' : true, + 'sources' : files('imdsd.c'), + 'extract' : files('imds-util.c'), + }, + libexec_template + { + 'name' : 'systemd-imds', + 'public' : true, + 'sources' : files('imds-tool.c'), + 'objects' : ['systemd-imdsd'], + }, + generator_template + { + 'name' : 'systemd-imds-generator', + 'sources' : files('imds-generator.c'), + 'objects' : ['systemd-imdsd'], + }, +] + +install_data( + 'io.systemd.imds.policy', + install_dir : polkitpolicydir) diff --git a/src/import/curl-util.h b/src/import/curl-util.h deleted file mode 100644 index b48eeb9c43682..0000000000000 --- a/src/import/curl-util.h +++ /dev/null @@ -1,36 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include - -#include "shared-forward.h" - -typedef struct CurlGlue CurlGlue; - -typedef struct CurlGlue { - sd_event *event; - CURLM *curl; - sd_event_source *timer; - Hashmap *ios; - sd_event_source *defer; - - void (*on_finished)(CurlGlue *g, CURL *curl, CURLcode code); - void *userdata; -} CurlGlue; - -int curl_glue_new(CurlGlue **glue, sd_event *event); -CurlGlue* curl_glue_unref(CurlGlue *glue); - -DEFINE_TRIVIAL_CLEANUP_FUNC(CurlGlue*, curl_glue_unref); - -int curl_glue_make(CURL **ret, const char *url, void *userdata); -int curl_glue_add(CurlGlue *g, CURL *c); -void curl_glue_remove_and_free(CurlGlue *g, CURL *c); - -struct curl_slist *curl_slist_new(const char *first, ...) _sentinel_; -int curl_header_strdup(const void *contents, size_t sz, const char *field, char **value); -int curl_parse_http_time(const char *t, usec_t *ret); - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURL*, curl_easy_cleanup, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURLM*, curl_multi_cleanup, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct curl_slist*, curl_slist_free_all, NULL); diff --git a/src/import/export-raw.c b/src/import/export-raw.c index 767e10f3ce318..31524b15747c3 100644 --- a/src/import/export-raw.c +++ b/src/import/export-raw.c @@ -11,7 +11,6 @@ #include "fd-util.h" #include "format-util.h" #include "fs-util.h" -#include "import-common.h" #include "log.h" #include "pretty-print.h" #include "ratelimit.h" @@ -32,7 +31,7 @@ typedef struct RawExport { int input_fd; int output_fd; - ImportCompress compress; + Compressor *compress; sd_event_source *output_event_source; @@ -59,7 +58,7 @@ RawExport *raw_export_unref(RawExport *e) { sd_event_source_unref(e->output_event_source); - import_compress_free(&e->compress); + e->compress = compressor_free(e->compress); sd_event_unref(e->event); @@ -143,7 +142,7 @@ static int raw_export_process(RawExport *e) { assert(e); - if (!e->tried_reflink && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) { + if (!e->tried_reflink && compressor_type(e->compress) == COMPRESSION_NONE) { /* If we shall take an uncompressed snapshot we can * reflink source to destination directly. Let's see @@ -158,9 +157,9 @@ static int raw_export_process(RawExport *e) { e->tried_reflink = true; } - if (!e->tried_sendfile && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) { + if (!e->tried_sendfile && compressor_type(e->compress) == COMPRESSION_NONE) { - l = sendfile(e->output_fd, e->input_fd, NULL, IMPORT_BUFFER_SIZE); + l = sendfile(e->output_fd, e->input_fd, NULL, COMPRESS_PIPE_BUFFER_SIZE); if (l < 0) { if (errno == EAGAIN) return 0; @@ -180,7 +179,7 @@ static int raw_export_process(RawExport *e) { } while (e->buffer_size <= 0) { - uint8_t input[IMPORT_BUFFER_SIZE]; + uint8_t input[COMPRESS_PIPE_BUFFER_SIZE]; if (e->eof) { r = 0; @@ -195,10 +194,10 @@ static int raw_export_process(RawExport *e) { if (l == 0) { e->eof = true; - r = import_compress_finish(&e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated); + r = compressor_finish(e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated); } else { e->written_uncompressed += l; - r = import_compress(&e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated); + r = compressor_start(e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated); } if (r < 0) { r = log_error_errno(r, "Failed to encode: %m"); @@ -280,15 +279,15 @@ static int reflink_snapshot(int fd, const char *path) { return new_fd; } -int raw_export_start(RawExport *e, const char *path, int fd, ImportCompressType compress) { +int raw_export_start(RawExport *e, const char *path, int fd, Compression compress) { _cleanup_close_ int sfd = -EBADF, tfd = -EBADF; int r; assert(e); assert(path); assert(fd >= 0); - assert(compress < _IMPORT_COMPRESS_TYPE_MAX); - assert(compress != IMPORT_COMPRESS_UNKNOWN); + assert(compress >= 0); + assert(compress < _COMPRESSION_MAX); if (e->output_fd >= 0) return -EBUSY; @@ -318,7 +317,7 @@ int raw_export_start(RawExport *e, const char *path, int fd, ImportCompressType else e->input_fd = TAKE_FD(sfd); - r = import_compress_init(&e->compress, compress); + r = compressor_new(&e->compress, compress); if (r < 0) return r; diff --git a/src/import/export-raw.h b/src/import/export-raw.h index 664bdfc8e7e50..f1f17c2c6d896 100644 --- a/src/import/export-raw.h +++ b/src/import/export-raw.h @@ -1,8 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "compress.h" #include "shared-forward.h" -#include "import-compress.h" typedef struct RawExport RawExport; @@ -13,4 +13,4 @@ RawExport* raw_export_unref(RawExport *e); DEFINE_TRIVIAL_CLEANUP_FUNC(RawExport*, raw_export_unref); -int raw_export_start(RawExport *e, const char *path, int fd, ImportCompressType compress); +int raw_export_start(RawExport *e, const char *path, int fd, Compression compress); diff --git a/src/import/export-tar.c b/src/import/export-tar.c index 22f731de5742a..93d163adda63d 100644 --- a/src/import/export-tar.c +++ b/src/import/export-tar.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "sd-daemon.h" #include "sd-event.h" @@ -38,7 +39,7 @@ typedef struct TarExport { int tree_fd; /* directory fd of the tree to set up */ int userns_fd; - ImportCompress compress; + Compressor *compress; sd_event_source *output_event_source; @@ -74,7 +75,7 @@ TarExport *tar_export_unref(TarExport *e) { free(e->temp_path); } - import_compress_free(&e->compress); + e->compress = compressor_free(e->compress); sd_event_unref(e->event); @@ -188,9 +189,9 @@ static int tar_export_process(TarExport *e) { assert(e); - if (!e->tried_splice && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) { + if (!e->tried_splice && compressor_type(e->compress) == COMPRESSION_NONE) { - l = splice(e->tar_fd, NULL, e->output_fd, NULL, IMPORT_BUFFER_SIZE, 0); + l = splice(e->tar_fd, NULL, e->output_fd, NULL, COMPRESS_PIPE_BUFFER_SIZE, 0); if (l < 0) { if (errno == EAGAIN) return 0; @@ -210,7 +211,7 @@ static int tar_export_process(TarExport *e) { } while (e->buffer_size <= 0) { - uint8_t input[IMPORT_BUFFER_SIZE]; + uint8_t input[COMPRESS_PIPE_BUFFER_SIZE]; if (e->eof) { r = tar_export_finish(e); @@ -225,10 +226,10 @@ static int tar_export_process(TarExport *e) { if (l == 0) { e->eof = true; - r = import_compress_finish(&e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated); + r = compressor_finish(e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated); } else { e->written_uncompressed += l; - r = import_compress(&e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated); + r = compressor_start(e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated); } if (r < 0) { r = log_error_errno(r, "Failed to encode: %m"); @@ -282,7 +283,7 @@ int tar_export_start( TarExport *e, const char *path, int fd, - ImportCompressType compress, + Compression compress, ImportFlags flags) { _cleanup_close_ int sfd = -EBADF; @@ -291,8 +292,8 @@ int tar_export_start( assert(e); assert(path); assert(fd >= 0); - assert(compress < _IMPORT_COMPRESS_TYPE_MAX); - assert(compress != IMPORT_COMPRESS_UNKNOWN); + assert(compress >= 0); + assert(compress < _COMPRESSION_MAX); if (e->output_fd >= 0) return -EBUSY; @@ -336,7 +337,7 @@ int tar_export_start( } } - r = import_compress_init(&e->compress, compress); + r = compressor_new(&e->compress, compress); if (r < 0) return r; diff --git a/src/import/export-tar.h b/src/import/export-tar.h index c5006d42319b1..be039b6b41a56 100644 --- a/src/import/export-tar.h +++ b/src/import/export-tar.h @@ -1,8 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "compress.h" #include "import-common.h" -#include "import-compress.h" #include "shared-forward.h" typedef struct TarExport TarExport; @@ -14,4 +14,4 @@ TarExport* tar_export_unref(TarExport *e); DEFINE_TRIVIAL_CLEANUP_FUNC(TarExport*, tar_export_unref); -int tar_export_start(TarExport *e, const char *path, int fd, ImportCompressType compress, ImportFlags flags); +int tar_export_start(TarExport *e, const char *path, int fd, Compression compress, ImportFlags flags); diff --git a/src/import/export.c b/src/import/export.c index af9e8c15ec959..71f892feb001f 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include +#include #include "sd-event.h" @@ -12,9 +12,11 @@ #include "export-raw.h" #include "export-tar.h" #include "fd-util.h" +#include "format-table.h" #include "import-common.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "runtime-scope.h" #include "signal-util.h" #include "string-util.h" @@ -22,30 +24,15 @@ #include "verbs.h" static ImportFlags arg_import_flags = 0; -static ImportCompressType arg_compress = IMPORT_COMPRESS_UNKNOWN; +static Compression arg_compress = _COMPRESSION_INVALID; static ImageClass arg_class = IMAGE_MACHINE; static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID; static void determine_compression_from_filename(const char *p) { - - if (arg_compress != IMPORT_COMPRESS_UNKNOWN) + if (arg_compress >= 0) return; - if (!p) { - arg_compress = IMPORT_COMPRESS_UNCOMPRESSED; - return; - } - - if (endswith(p, ".xz")) - arg_compress = IMPORT_COMPRESS_XZ; - else if (endswith(p, ".gz")) - arg_compress = IMPORT_COMPRESS_GZIP; - else if (endswith(p, ".bz2")) - arg_compress = IMPORT_COMPRESS_BZIP2; - else if (endswith(p, ".zst")) - arg_compress = IMPORT_COMPRESS_ZSTD; - else - arg_compress = IMPORT_COMPRESS_UNCOMPRESSED; + arg_compress = p ? compression_from_filename(p) : COMPRESSION_NONE; } static void on_tar_finished(TarExport *export, int error, void *userdata) { @@ -58,7 +45,9 @@ static void on_tar_finished(TarExport *export, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int export_tar(int argc, char *argv[], void *userdata) { +VERB(verb_export_tar, "tar", "NAME [FILE]", 2, 3, 0, + "Export a TAR image"); +static int verb_export_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(tar_export_unrefp) TarExport *export = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(image_unrefp) Image *image = NULL; @@ -91,7 +80,7 @@ static int export_tar(int argc, char *argv[], void *userdata) { fd = open_fd; - log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, import_compress_type_to_string(arg_compress)); + log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, compression_to_string(arg_compress)); } else { _cleanup_free_ char *pretty = NULL; @@ -101,7 +90,7 @@ static int export_tar(int argc, char *argv[], void *userdata) { fd = STDOUT_FILENO; (void) fd_get_path(fd, &pretty); - log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress)); + log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), compression_to_string(arg_compress)); } r = import_allocate_event_with_signals(&event); @@ -139,7 +128,9 @@ static void on_raw_finished(RawExport *export, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int export_raw(int argc, char *argv[], void *userdata) { +VERB(verb_export_raw, "raw", "NAME [FILE]", 2, 3, 0, + "Export a RAW image"); +static int verb_export_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(raw_export_unrefp) RawExport *export = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(image_unrefp) Image *image = NULL; @@ -172,14 +163,14 @@ static int export_raw(int argc, char *argv[], void *userdata) { fd = open_fd; - log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, import_compress_type_to_string(arg_compress)); + log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, compression_to_string(arg_compress)); } else { _cleanup_free_ char *pretty = NULL; fd = STDOUT_FILENO; (void) fd_get_path(fd, &pretty); - log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress)); + log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), compression_to_string(arg_compress)); } r = import_allocate_event_with_signals(&event); @@ -202,123 +193,107 @@ static int export_raw(int argc, char *argv[], void *userdata) { return -r; } -static int help(int argc, char *argv[], void *userdata) { - printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sExport disk images.%5$s\n" - "\n%2$sCommands:%3$s\n" - " tar NAME [FILE] Export a TAR image\n" - " raw NAME [FILE] Export a RAW image\n" - "\n%2$sOptions:%3$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --format=FORMAT Select format\n" - " --class=CLASS Select image class (machine, sysext, confext,\n" - " portable)\n" - " --system Operate in per-system mode\n" - " --user Operate in per-user mode\n", +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; + int r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "%sExport disk images.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); - return 0; -} + r = table_print_or_warn(verbs); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); - enum { - ARG_VERSION = 0x100, - ARG_FORMAT, - ARG_CLASS, - ARG_SYSTEM, - ARG_USER, - }; + r = table_print_or_warn(options); + if (r < 0) + return r; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "format", required_argument, NULL, ARG_FORMAT }, - { "class", required_argument, NULL, ARG_CLASS }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; + return 0; +} - int c; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(0, NULL, NULL); + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_FORMAT: - arg_compress = import_compress_type_from_string(optarg); - if (arg_compress < 0 || arg_compress == IMPORT_COMPRESS_UNKNOWN) + OPTION_LONG("format", "FORMAT", "Select format"): + arg_compress = compression_from_string_harder(arg); + if (arg_compress < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown format: %s", optarg); + "Unknown format: %s", arg); break; - case ARG_CLASS: - arg_class = image_class_from_string(optarg); + OPTION_LONG("class", "CLASS", + "Select image class (machine, sysext, confext, portable)"): + arg_class = image_class_from_string(arg); if (arg_class < 0) - return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); - + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg); break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Operate in per-system mode"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Operate in per-user mode"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_runtime_scope == RUNTIME_SCOPE_USER) arg_import_flags |= IMPORT_FOREIGN_UID; + *ret_args = option_parser_get_args(&state); return 1; } -static int export_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "tar", 2, 3, 0, export_tar }, - { "raw", 2, 3, 0, export_raw }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; (void) ignore_signals(SIGPIPE); - return export_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/import/import-common.c b/src/import/import-common.c index 0a5144f94ecd6..5f17084f9fd94 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -7,6 +7,7 @@ #include "sd-event.h" #include "capability-util.h" +#include "compress.h" #include "dirent-util.h" #include "dissect-image.h" #include "fd-util.h" @@ -31,7 +32,7 @@ int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) { assert(tree_fd >= 0); assert(ret_pid); - r = dlopen_libarchive(); + r = dlopen_libarchive(LOG_DEBUG); if (r < 0) return r; @@ -43,7 +44,7 @@ int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) { if (pipe2(pipefd, O_CLOEXEC) < 0) return log_error_errno(errno, "Failed to create pipe for tar: %m"); - (void) fcntl(pipefd[0], F_SETPIPE_SZ, IMPORT_BUFFER_SIZE); + (void) fcntl(pipefd[0], F_SETPIPE_SZ, COMPRESS_PIPE_BUFFER_SIZE); r = pidref_safe_fork_full( "tar-x", @@ -98,7 +99,7 @@ int import_fork_tar_c(int tree_fd, int userns_fd, PidRef *ret_pid) { assert(tree_fd >= 0); assert(ret_pid); - r = dlopen_libarchive(); + r = dlopen_libarchive(LOG_DEBUG); if (r < 0) return r; @@ -110,7 +111,7 @@ int import_fork_tar_c(int tree_fd, int userns_fd, PidRef *ret_pid) { if (pipe2(pipefd, O_CLOEXEC) < 0) return log_error_errno(errno, "Failed to create pipe for tar: %m"); - (void) fcntl(pipefd[0], F_SETPIPE_SZ, IMPORT_BUFFER_SIZE); + (void) fcntl(pipefd[0], F_SETPIPE_SZ, COMPRESS_PIPE_BUFFER_SIZE); r = pidref_safe_fork_full( "tar-c", diff --git a/src/import/import-common.h b/src/import/import-common.h index 6b10f8c29db87..69bdb335285df 100644 --- a/src/import/import-common.h +++ b/src/import/import-common.h @@ -49,5 +49,3 @@ int import_allocate_event_with_signals(sd_event **ret); int import_make_foreign_userns(int *userns_fd); int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags); - -#define IMPORT_BUFFER_SIZE (128U*1024U) diff --git a/src/import/import-compress.c b/src/import/import-compress.c deleted file mode 100644 index f893abc43e648..0000000000000 --- a/src/import/import-compress.c +++ /dev/null @@ -1,607 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include - -#include "import-common.h" -#include "import-compress.h" -#include "log.h" -#include "string-table.h" - -void import_compress_free(ImportCompress *c) { - assert(c); - - if (c->type == IMPORT_COMPRESS_XZ) - lzma_end(&c->xz); - else if (c->type == IMPORT_COMPRESS_GZIP) { - if (c->encoding) - deflateEnd(&c->gzip); - else - inflateEnd(&c->gzip); -#if HAVE_BZIP2 - } else if (c->type == IMPORT_COMPRESS_BZIP2) { - if (c->encoding) - BZ2_bzCompressEnd(&c->bzip2); - else - BZ2_bzDecompressEnd(&c->bzip2); -#endif -#if HAVE_ZSTD - } else if (c->type == IMPORT_COMPRESS_ZSTD) { - if (c->encoding) { - ZSTD_freeCCtx(c->c_zstd); - c->c_zstd = NULL; - } else { - ZSTD_freeDCtx(c->d_zstd); - c->d_zstd = NULL; - } -#endif - } - - c->type = IMPORT_COMPRESS_UNKNOWN; -} - -int import_uncompress_detect(ImportCompress *c, const void *data, size_t size) { - static const uint8_t xz_signature[] = { - 0xfd, '7', 'z', 'X', 'Z', 0x00 - }; - static const uint8_t gzip_signature[] = { - 0x1f, 0x8b - }; - static const uint8_t bzip2_signature[] = { - 'B', 'Z', 'h' - }; - static const uint8_t zstd_signature[] = { - 0x28, 0xb5, 0x2f, 0xfd - }; - - int r; - - assert(c); - - if (c->type != IMPORT_COMPRESS_UNKNOWN) - return 1; - - if (size < MAX4(sizeof(xz_signature), - sizeof(gzip_signature), - sizeof(zstd_signature), - sizeof(bzip2_signature))) - return 0; - - assert(data); - - if (memcmp(data, xz_signature, sizeof(xz_signature)) == 0) { - lzma_ret xzr; - - xzr = lzma_stream_decoder(&c->xz, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK | LZMA_CONCATENATED); - if (xzr != LZMA_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_XZ; - - } else if (memcmp(data, gzip_signature, sizeof(gzip_signature)) == 0) { - r = inflateInit2(&c->gzip, 15+16); - if (r != Z_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_GZIP; - -#if HAVE_BZIP2 - } else if (memcmp(data, bzip2_signature, sizeof(bzip2_signature)) == 0) { - r = BZ2_bzDecompressInit(&c->bzip2, 0, 0); - if (r != BZ_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_BZIP2; -#endif -#if HAVE_ZSTD - } else if (memcmp(data, zstd_signature, sizeof(zstd_signature)) == 0) { - c->d_zstd = ZSTD_createDCtx(); - if (!c->d_zstd) - return -ENOMEM; - - c->type = IMPORT_COMPRESS_ZSTD; -#endif - } else - c->type = IMPORT_COMPRESS_UNCOMPRESSED; - - c->encoding = false; - - log_debug("Detected compression type: %s", import_compress_type_to_string(c->type)); - return 1; -} - -void import_uncompress_force_off(ImportCompress *c) { - assert(c); - - c->type = IMPORT_COMPRESS_UNCOMPRESSED; - c->encoding = false; -} - -int import_uncompress(ImportCompress *c, const void *data, size_t size, ImportCompressCallback callback, void *userdata) { - int r; - - assert(c); - assert(callback); - - r = import_uncompress_detect(c, data, size); - if (r <= 0) - return r; - - if (c->encoding) - return -EINVAL; - - if (size <= 0) - return 1; - - assert(data); - - switch (c->type) { - - case IMPORT_COMPRESS_UNCOMPRESSED: - r = callback(data, size, userdata); - if (r < 0) - return r; - - break; - - case IMPORT_COMPRESS_XZ: - c->xz.next_in = data; - c->xz.avail_in = size; - - while (c->xz.avail_in > 0) { - uint8_t buffer[IMPORT_BUFFER_SIZE]; - lzma_ret lzr; - - c->xz.next_out = buffer; - c->xz.avail_out = sizeof(buffer); - - lzr = lzma_code(&c->xz, LZMA_RUN); - if (!IN_SET(lzr, LZMA_OK, LZMA_STREAM_END)) - return -EIO; - - if (c->xz.avail_out < sizeof(buffer)) { - r = callback(buffer, sizeof(buffer) - c->xz.avail_out, userdata); - if (r < 0) - return r; - } - } - - break; - - case IMPORT_COMPRESS_GZIP: - c->gzip.next_in = (void*) data; - c->gzip.avail_in = size; - - while (c->gzip.avail_in > 0) { - uint8_t buffer[IMPORT_BUFFER_SIZE]; - - c->gzip.next_out = buffer; - c->gzip.avail_out = sizeof(buffer); - - r = inflate(&c->gzip, Z_NO_FLUSH); - if (!IN_SET(r, Z_OK, Z_STREAM_END)) - return -EIO; - - if (c->gzip.avail_out < sizeof(buffer)) { - r = callback(buffer, sizeof(buffer) - c->gzip.avail_out, userdata); - if (r < 0) - return r; - } - } - - break; - -#if HAVE_BZIP2 - case IMPORT_COMPRESS_BZIP2: - c->bzip2.next_in = (void*) data; - c->bzip2.avail_in = size; - - while (c->bzip2.avail_in > 0) { - uint8_t buffer[IMPORT_BUFFER_SIZE]; - - c->bzip2.next_out = (char*) buffer; - c->bzip2.avail_out = sizeof(buffer); - - r = BZ2_bzDecompress(&c->bzip2); - if (!IN_SET(r, BZ_OK, BZ_STREAM_END)) - return -EIO; - - if (c->bzip2.avail_out < sizeof(buffer)) { - r = callback(buffer, sizeof(buffer) - c->bzip2.avail_out, userdata); - if (r < 0) - return r; - } - } - - break; -#endif -#if HAVE_ZSTD - case IMPORT_COMPRESS_ZSTD: { - ZSTD_inBuffer input = { - .src = (void*) data, - .size = size, - }; - - while (input.pos < input.size) { - uint8_t buffer[IMPORT_BUFFER_SIZE]; - ZSTD_outBuffer output = { - .dst = buffer, - .size = sizeof(buffer), - }; - size_t res; - - res = ZSTD_decompressStream(c->d_zstd, &output, &input); - if (ZSTD_isError(res)) - return -EIO; - - if (output.pos > 0) { - r = callback(output.dst, output.pos, userdata); - if (r < 0) - return r; - } - } - - break; - } -#endif - - default: - assert_not_reached(); - } - - return 1; -} - -int import_compress_init(ImportCompress *c, ImportCompressType t) { - int r; - - assert(c); - - switch (t) { - - case IMPORT_COMPRESS_XZ: { - lzma_ret xzr; - - xzr = lzma_easy_encoder(&c->xz, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); - if (xzr != LZMA_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_XZ; - break; - } - - case IMPORT_COMPRESS_GZIP: - r = deflateInit2(&c->gzip, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); - if (r != Z_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_GZIP; - break; - -#if HAVE_BZIP2 - case IMPORT_COMPRESS_BZIP2: - r = BZ2_bzCompressInit(&c->bzip2, 9, 0, 0); - if (r != BZ_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_BZIP2; - break; -#endif - -#if HAVE_ZSTD - case IMPORT_COMPRESS_ZSTD: - c->c_zstd = ZSTD_createCCtx(); - if (!c->c_zstd) - return -ENOMEM; - - r = ZSTD_CCtx_setParameter(c->c_zstd, ZSTD_c_compressionLevel, ZSTD_CLEVEL_DEFAULT); - if (ZSTD_isError(r)) - return -EIO; - - c->type = IMPORT_COMPRESS_ZSTD; - break; -#endif - - case IMPORT_COMPRESS_UNCOMPRESSED: - c->type = IMPORT_COMPRESS_UNCOMPRESSED; - break; - - default: - return -EOPNOTSUPP; - } - - c->encoding = true; - return 0; -} - -static int enlarge_buffer(void **buffer, size_t *buffer_size, size_t *buffer_allocated) { - size_t l; - void *p; - - if (*buffer_allocated > *buffer_size) - return 0; - - l = MAX(IMPORT_BUFFER_SIZE, (*buffer_size * 2)); - p = realloc(*buffer, l); - if (!p) - return -ENOMEM; - - *buffer = p; - *buffer_allocated = l; - - return 1; -} - -int import_compress(ImportCompress *c, const void *data, size_t size, void **buffer, size_t *buffer_size, size_t *buffer_allocated) { - int r; - - assert(c); - assert(buffer); - assert(buffer_size); - assert(buffer_allocated); - - if (!c->encoding) - return -EINVAL; - - if (size <= 0) - return 0; - - assert(data); - - *buffer_size = 0; - - switch (c->type) { - - case IMPORT_COMPRESS_XZ: - - c->xz.next_in = data; - c->xz.avail_in = size; - - while (c->xz.avail_in > 0) { - lzma_ret lzr; - - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->xz.next_out = (uint8_t*) *buffer + *buffer_size; - c->xz.avail_out = *buffer_allocated - *buffer_size; - - lzr = lzma_code(&c->xz, LZMA_RUN); - if (lzr != LZMA_OK) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out; - } - - break; - - case IMPORT_COMPRESS_GZIP: - - c->gzip.next_in = (void*) data; - c->gzip.avail_in = size; - - while (c->gzip.avail_in > 0) { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->gzip.next_out = (uint8_t*) *buffer + *buffer_size; - c->gzip.avail_out = *buffer_allocated - *buffer_size; - - r = deflate(&c->gzip, Z_NO_FLUSH); - if (r != Z_OK) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->gzip.avail_out; - } - - break; - -#if HAVE_BZIP2 - case IMPORT_COMPRESS_BZIP2: - - c->bzip2.next_in = (void*) data; - c->bzip2.avail_in = size; - - while (c->bzip2.avail_in > 0) { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size); - c->bzip2.avail_out = *buffer_allocated - *buffer_size; - - r = BZ2_bzCompress(&c->bzip2, BZ_RUN); - if (r != BZ_RUN_OK) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->bzip2.avail_out; - } - - break; -#endif - -#if HAVE_ZSTD - case IMPORT_COMPRESS_ZSTD: { - ZSTD_inBuffer input = { - .src = data, - .size = size, - }; - - while (input.pos < input.size) { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - ZSTD_outBuffer output = { - .dst = ((uint8_t *) *buffer + *buffer_size), - .size = *buffer_allocated - *buffer_size, - }; - size_t res; - - res = ZSTD_compressStream2(c->c_zstd, &output, &input, ZSTD_e_continue); - if (ZSTD_isError(res)) - return -EIO; - - *buffer_size += output.pos; - } - - break; - } -#endif - - case IMPORT_COMPRESS_UNCOMPRESSED: - - if (*buffer_allocated < size) { - void *p; - - p = realloc(*buffer, size); - if (!p) - return -ENOMEM; - - *buffer = p; - *buffer_allocated = size; - } - - memcpy(*buffer, data, size); - *buffer_size = size; - break; - - default: - return -EOPNOTSUPP; - } - - return 0; -} - -int import_compress_finish(ImportCompress *c, void **buffer, size_t *buffer_size, size_t *buffer_allocated) { - int r; - - assert(c); - assert(buffer); - assert(buffer_size); - assert(buffer_allocated); - - if (!c->encoding) - return -EINVAL; - - *buffer_size = 0; - - switch (c->type) { - - case IMPORT_COMPRESS_XZ: { - lzma_ret lzr; - - c->xz.avail_in = 0; - - do { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->xz.next_out = (uint8_t*) *buffer + *buffer_size; - c->xz.avail_out = *buffer_allocated - *buffer_size; - - lzr = lzma_code(&c->xz, LZMA_FINISH); - if (!IN_SET(lzr, LZMA_OK, LZMA_STREAM_END)) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out; - } while (lzr != LZMA_STREAM_END); - - break; - } - - case IMPORT_COMPRESS_GZIP: - c->gzip.avail_in = 0; - - do { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->gzip.next_out = (uint8_t*) *buffer + *buffer_size; - c->gzip.avail_out = *buffer_allocated - *buffer_size; - - r = deflate(&c->gzip, Z_FINISH); - if (!IN_SET(r, Z_OK, Z_STREAM_END)) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->gzip.avail_out; - } while (r != Z_STREAM_END); - - break; - -#if HAVE_BZIP2 - case IMPORT_COMPRESS_BZIP2: - c->bzip2.avail_in = 0; - - do { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size); - c->bzip2.avail_out = *buffer_allocated - *buffer_size; - - r = BZ2_bzCompress(&c->bzip2, BZ_FINISH); - if (!IN_SET(r, BZ_FINISH_OK, BZ_STREAM_END)) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->bzip2.avail_out; - } while (r != BZ_STREAM_END); - - break; -#endif - -#if HAVE_ZSTD - case IMPORT_COMPRESS_ZSTD: { - ZSTD_inBuffer input = {}; - size_t res; - - do { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - ZSTD_outBuffer output = { - .dst = ((uint8_t *) *buffer + *buffer_size), - .size = *buffer_allocated - *buffer_size, - }; - - res = ZSTD_compressStream2(c->c_zstd, &output, &input, ZSTD_e_end); - if (ZSTD_isError(res)) - return -EIO; - - *buffer_size += output.pos; - } while (res != 0); - - break; - } -#endif - - case IMPORT_COMPRESS_UNCOMPRESSED: - break; - - default: - return -EOPNOTSUPP; - } - - return 0; -} - -static const char* const import_compress_type_table[_IMPORT_COMPRESS_TYPE_MAX] = { - [IMPORT_COMPRESS_UNKNOWN] = "unknown", - [IMPORT_COMPRESS_UNCOMPRESSED] = "uncompressed", - [IMPORT_COMPRESS_XZ] = "xz", - [IMPORT_COMPRESS_GZIP] = "gzip", -#if HAVE_BZIP2 - [IMPORT_COMPRESS_BZIP2] = "bzip2", -#endif -#if HAVE_ZSTD - [IMPORT_COMPRESS_ZSTD] = "zstd", -#endif -}; - -DEFINE_STRING_TABLE_LOOKUP(import_compress_type, ImportCompressType); diff --git a/src/import/import-compress.h b/src/import/import-compress.h deleted file mode 100644 index 647e623266787..0000000000000 --- a/src/import/import-compress.h +++ /dev/null @@ -1,54 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#if HAVE_BZIP2 -#include -#endif -#include -#include -#if HAVE_ZSTD -#include -#endif - -#include "shared-forward.h" - -typedef enum ImportCompressType { - IMPORT_COMPRESS_UNKNOWN, - IMPORT_COMPRESS_UNCOMPRESSED, - IMPORT_COMPRESS_XZ, - IMPORT_COMPRESS_GZIP, - IMPORT_COMPRESS_BZIP2, - IMPORT_COMPRESS_ZSTD, - _IMPORT_COMPRESS_TYPE_MAX, - _IMPORT_COMPRESS_TYPE_INVALID = -EINVAL, -} ImportCompressType; - -typedef struct ImportCompress { - ImportCompressType type; - bool encoding; - union { - lzma_stream xz; - z_stream gzip; -#if HAVE_BZIP2 - bz_stream bzip2; -#endif -#if HAVE_ZSTD - ZSTD_CCtx *c_zstd; - ZSTD_DCtx *d_zstd; -#endif - }; -} ImportCompress; - -typedef int (*ImportCompressCallback)(const void *data, size_t size, void *userdata); - -void import_compress_free(ImportCompress *c); - -int import_uncompress_detect(ImportCompress *c, const void *data, size_t size); -void import_uncompress_force_off(ImportCompress *c); -int import_uncompress(ImportCompress *c, const void *data, size_t size, ImportCompressCallback callback, void *userdata); - -int import_compress_init(ImportCompress *c, ImportCompressType t); -int import_compress(ImportCompress *c, const void *data, size_t size, void **buffer, size_t *buffer_size, size_t *buffer_allocated); -int import_compress_finish(ImportCompress *c, void **buffer, size_t *buffer_size, size_t *buffer_allocated); - -DECLARE_STRING_TABLE_LOOKUP(import_compress_type, ImportCompressType); diff --git a/src/import/import-fs.c b/src/import/import-fs.c index 46daf3acc05f8..3605320300d3a 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -11,6 +10,7 @@ #include "copy.h" #include "discover-image.h" #include "fd-util.h" +#include "format-table.h" #include "format-util.h" #include "import-common.h" #include "import-util.h" @@ -18,6 +18,7 @@ #include "log.h" #include "main-func.h" #include "mkdir-label.h" +#include "options.h" #include "parse-argument.h" #include "path-util.h" #include "ratelimit.h" @@ -107,7 +108,9 @@ static int progress_bytes(uint64_t nbytes, uint64_t bps, void *userdata) { return 0; } -static int import_fs(int argc, char *argv[], void *userdata) { +VERB(verb_import_fs, "run", "DIRECTORY [NAME]", 2, 3, 0, + "Import a directory"); +static int verb_import_fs(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(rm_rf_subvolume_and_freep) char *temp_path = NULL; _cleanup_(progress_info_free) ProgressInfo progress = { .bps = UINT64_MAX }; _cleanup_free_ char *l = NULL, *final_path = NULL; @@ -265,144 +268,116 @@ static int import_fs(int argc, char *argv[], void *userdata) { return 0; } -static int help(int argc, char *argv[], void *userdata) { - - printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sImport container images from a file system directories.%5$s\n" - "\n%2$sCommands:%3$s\n" - " run DIRECTORY [NAME] Import a directory\n" - "\n%2$sOptions:%3$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --force Force creation of image\n" - " --image-root=PATH Image root directory\n" - " --read-only Create a read-only image\n" - " --direct Import directly to specified directory\n" - " --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n" - " instead of a directory\n" - " --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n" - " subvolume\n" - " --sync=BOOL Controls whether to sync() before completing\n" - " --class=CLASS Select image class (machine, sysext, confext,\n" - " portable)\n" - " --system Operate in per-system mode\n" - " --user Operate in per-user mode\n", +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; + int r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "%sImport container images from file system directories.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", + ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_FORCE, - ARG_IMAGE_ROOT, - ARG_READ_ONLY, - ARG_DIRECT, - ARG_BTRFS_SUBVOL, - ARG_BTRFS_QUOTA, - ARG_SYNC, - ARG_CLASS, - ARG_SYSTEM, - ARG_USER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "force", no_argument, NULL, ARG_FORCE }, - { "image-root", required_argument, NULL, ARG_IMAGE_ROOT }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "direct", no_argument, NULL, ARG_DIRECT }, - { "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL }, - { "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA }, - { "sync", required_argument, NULL, ARG_SYNC }, - { "class", required_argument, NULL, ARG_CLASS }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - int c, r; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(0, NULL, NULL); + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_FORCE: + OPTION_LONG("force", NULL, "Force creation of image"): arg_force = true; break; - case ARG_IMAGE_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root); + OPTION_LONG("image-root", "PATH", "Image root directory"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image_root); if (r < 0) return r; - break; - case ARG_READ_ONLY: + OPTION_LONG("read-only", NULL, "Create a read-only image"): arg_read_only = true; break; - case ARG_DIRECT: + OPTION_LONG("direct", NULL, "Import directly to specified directory"): arg_direct = true; break; - case ARG_BTRFS_SUBVOL: - r = parse_boolean_argument("--btrfs-subvol=", optarg, &arg_btrfs_subvol); + OPTION_LONG("btrfs-subvol", "BOOL", + "Controls whether to create a btrfs subvolume instead of a directory"): + r = parse_boolean_argument("--btrfs-subvol=", arg, &arg_btrfs_subvol); if (r < 0) return r; - break; - case ARG_BTRFS_QUOTA: - r = parse_boolean_argument("--btrfs-quota=", optarg, &arg_btrfs_quota); + OPTION_LONG("btrfs-quota", "BOOL", + "Controls whether to set up quota for btrfs subvolume"): + r = parse_boolean_argument("--btrfs-quota=", arg, &arg_btrfs_quota); if (r < 0) return r; - break; - case ARG_SYNC: - r = parse_boolean_argument("--sync=", optarg, &arg_sync); + OPTION_LONG("sync", "BOOL", "Controls whether to sync() before completing"): + r = parse_boolean_argument("--sync=", arg, &arg_sync); if (r < 0) return r; - break; - case ARG_CLASS: - arg_class = image_class_from_string(optarg); + OPTION_LONG("class", "CLASS", + "Select image class (machine, sysext, confext, portable)"): + arg_class = image_class_from_string(arg); if (arg_class < 0) - return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); - + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg); break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Operate in per-system mode"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Operate in per-user mode"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_image_root) { @@ -411,31 +386,22 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to pick image root: %m"); } + *ret_args = option_parser_get_args(&state); return 1; } -static int import_fs_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "run", 2, 3, 0, import_fs }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return import_fs_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/import/import-generator.c b/src/import/import-generator.c index f5c39774050db..f176492a54c93 100644 --- a/src/import/import-generator.c +++ b/src/import/import-generator.c @@ -209,13 +209,13 @@ static int parse_pull_expression(const char *v) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; r = sd_json_buildo( &j, - SD_JSON_BUILD_PAIR("remote", SD_JSON_BUILD_STRING(remote)), - SD_JSON_BUILD_PAIR("local", SD_JSON_BUILD_STRING(local)), - SD_JSON_BUILD_PAIR("class", JSON_BUILD_STRING_UNDERSCORIFY(image_class_to_string(class))), - SD_JSON_BUILD_PAIR("type", JSON_BUILD_STRING_UNDERSCORIFY(import_type_to_string(type))), - SD_JSON_BUILD_PAIR("readOnly", SD_JSON_BUILD_BOOLEAN(ro)), - SD_JSON_BUILD_PAIR("verify", JSON_BUILD_STRING_UNDERSCORIFY(import_verify_to_string(verify))), - SD_JSON_BUILD_PAIR("imageRoot", SD_JSON_BUILD_STRING(image_root))); + SD_JSON_BUILD_PAIR_STRING("remote", remote), + SD_JSON_BUILD_PAIR_STRING("local", local), + JSON_BUILD_PAIR_ENUM("class", image_class_to_string(class)), + JSON_BUILD_PAIR_ENUM("type", import_type_to_string(type)), + SD_JSON_BUILD_PAIR_BOOLEAN("readOnly", ro), + JSON_BUILD_PAIR_ENUM("verify", import_verify_to_string(verify)), + SD_JSON_BUILD_PAIR_STRING("imageRoot", image_root)); if (r < 0) return log_error_errno(r, "Failed to build import JSON object: %m"); diff --git a/src/import/import-raw.c b/src/import/import-raw.c index 1d7302cd88243..2095a0dde57af 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -1,17 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "sd-daemon.h" #include "sd-event.h" #include "alloc-util.h" +#include "compress.h" #include "copy.h" #include "fd-util.h" #include "format-util.h" #include "fs-util.h" #include "import-common.h" -#include "import-compress.h" #include "import-raw.h" #include "import-util.h" #include "install-file.h" @@ -21,6 +22,7 @@ #include "pretty-print.h" #include "qcow2-util.h" #include "ratelimit.h" +#include "stat-util.h" #include "string-util.h" #include "terminal-util.h" #include "time-util.h" @@ -43,11 +45,11 @@ typedef struct RawImport { int input_fd; int output_fd; - ImportCompress compress; + Compressor *compress; sd_event_source *input_event_source; - uint8_t buffer[IMPORT_BUFFER_SIZE]; + uint8_t buffer[COMPRESS_PIPE_BUFFER_SIZE]; size_t buffer_size; uint64_t written_compressed; @@ -71,7 +73,7 @@ RawImport* raw_import_unref(RawImport *i) { unlink_and_free(i->temp_path); - import_compress_free(&i->compress); + i->compress = compressor_free(i->compress); sd_event_unref(i->event); @@ -308,9 +310,9 @@ static int raw_import_open_disk(RawImport *i) { if (fstat(i->output_fd, &i->output_stat) < 0) return log_error_errno(errno, "Failed to stat() output file: %m"); - if (!S_ISREG(i->output_stat.st_mode) && !S_ISBLK(i->output_stat.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(EBADFD), - "Target file is not a regular file or block device"); + r = stat_verify_regular_or_block(&i->output_stat); + if (r < 0) + return log_error_errno(r, "Target file is not a regular file or block device."); if (i->offset != UINT64_MAX) { if (lseek(i->output_fd, i->offset, SEEK_SET) < 0) @@ -328,7 +330,7 @@ static int raw_import_try_reflink(RawImport *i) { assert(i->input_fd >= 0); assert(i->output_fd >= 0); - if (i->compress.type != IMPORT_COMPRESS_UNCOMPRESSED) + if (compressor_type(i->compress) != COMPRESSION_NONE) return 0; if (i->offset != UINT64_MAX || i->size_max != UINT64_MAX) @@ -425,13 +427,13 @@ static int raw_import_process(RawImport *i) { i->buffer_size += l; - if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) { + if (!i->compress) { if (l == 0) { /* EOF */ log_debug("File too short to be compressed, as no compression signature fits in, thus assuming uncompressed."); - import_uncompress_force_off(&i->compress); + decompressor_force_off(&i->compress); } else { - r = import_uncompress_detect(&i->compress, i->buffer, i->buffer_size); + r = decompressor_detect(&i->compress, i->buffer, i->buffer_size); if (r < 0) { log_error_errno(r, "Failed to detect file compression: %m"); goto finish; @@ -451,7 +453,7 @@ static int raw_import_process(RawImport *i) { goto complete; } - r = import_uncompress(&i->compress, i->buffer, i->buffer_size, raw_import_write, i); + r = decompressor_push(i->compress, i->buffer, i->buffer_size, raw_import_write, i); if (r < 0) { log_error_errno(r, "Failed to decode and write: %m"); goto finish; diff --git a/src/import/import-tar.c b/src/import/import-tar.c index 5e74de896e99c..4bd59788008e9 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "sd-daemon.h" #include "sd-event.h" @@ -8,12 +9,12 @@ #include "alloc-util.h" #include "btrfs-util.h" +#include "compress.h" #include "dissect-image.h" #include "errno-util.h" #include "fd-util.h" #include "format-util.h" #include "import-common.h" -#include "import-compress.h" #include "import-tar.h" #include "import-util.h" #include "install-file.h" @@ -50,11 +51,11 @@ typedef struct TarImport { int tree_fd; int userns_fd; - ImportCompress compress; + Compressor *compress; sd_event_source *input_event_source; - uint8_t buffer[IMPORT_BUFFER_SIZE]; + uint8_t buffer[COMPRESS_PIPE_BUFFER_SIZE]; size_t buffer_size; uint64_t written_compressed; @@ -81,7 +82,7 @@ TarImport* tar_import_unref(TarImport *i) { free(i->temp_path); } - import_compress_free(&i->compress); + i->compress = compressor_free(i->compress); sd_event_unref(i->event); @@ -344,13 +345,13 @@ static int tar_import_process(TarImport *i) { i->buffer_size += l; - if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) { + if (!i->compress) { if (l == 0) { /* EOF */ log_debug("File too short to be compressed, as no compression signature fits in, thus assuming uncompressed."); - import_uncompress_force_off(&i->compress); + decompressor_force_off(&i->compress); } else { - r = import_uncompress_detect(&i->compress, i->buffer, i->buffer_size); + r = decompressor_detect(&i->compress, i->buffer, i->buffer_size); if (r < 0) { log_error_errno(r, "Failed to detect file compression: %m"); goto finish; @@ -364,7 +365,7 @@ static int tar_import_process(TarImport *i) { goto finish; } - r = import_uncompress(&i->compress, i->buffer, i->buffer_size, tar_import_write, i); + r = decompressor_push(i->compress, i->buffer, i->buffer_size, tar_import_write, i); if (r < 0) { log_error_errno(r, "Failed to decode and write: %m"); goto finish; diff --git a/src/import/import.c b/src/import/import.c index 5276f26977a3c..c678c68f11565 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -12,12 +11,14 @@ #include "discover-image.h" #include "env-util.h" #include "fd-util.h" +#include "format-table.h" #include "import-raw.h" #include "import-tar.h" #include "import-util.h" #include "io-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" @@ -138,7 +139,8 @@ static void on_tar_finished(TarImport *import, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int import_tar(int argc, char *argv[], void *userdata) { +VERB(verb_tar, "tar", "FILE [NAME]", 2, 3, 0, "Import a TAR image"); +static int verb_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(tar_import_unrefp) TarImport *import = NULL; _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -207,7 +209,8 @@ static void on_raw_finished(RawImport *import, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int import_raw(int argc, char *argv[], void *userdata) { +VERB(verb_raw, "raw", "FILE [NAME]", 2, 3, 0, "Import a RAW image"); +static int verb_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(raw_import_unrefp) RawImport *import = NULL; _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -268,192 +271,153 @@ static int import_raw(int argc, char *argv[], void *userdata) { return -r; } -static int help(int argc, char *argv[], void *userdata) { - - printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sImport disk images.%5$s\n" - "\n%2$sCommands:%3$s\n" - " tar FILE [NAME] Import a TAR image\n" - " raw FILE [NAME] Import a RAW image\n" - "\n%2$sOptions:%3$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --force Force creation of image\n" - " --image-root=PATH Image root directory\n" - " --read-only Create a read-only image\n" - " --direct Import directly to specified file\n" - " --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n" - " instead of a directory\n" - " --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n" - " subvolume\n" - " --convert-qcow2=BOOL Controls whether to convert QCOW2 images to\n" - " regular disk images\n" - " --sync=BOOL Controls whether to sync() before completing\n" - " --offset=BYTES Offset to seek to in destination\n" - " --size-max=BYTES Maximum number of bytes to write to destination\n" - " --class=CLASS Select image class (machine, sysext, confext,\n" - " portable)\n" - " --system Operate in per-system mode\n" - " --user Operate in per-user mode\n", +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; + int r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "%sImport disk images.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_FORCE, - ARG_IMAGE_ROOT, - ARG_READ_ONLY, - ARG_DIRECT, - ARG_BTRFS_SUBVOL, - ARG_BTRFS_QUOTA, - ARG_CONVERT_QCOW2, - ARG_SYNC, - ARG_OFFSET, - ARG_SIZE_MAX, - ARG_CLASS, - ARG_SYSTEM, - ARG_USER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "force", no_argument, NULL, ARG_FORCE }, - { "image-root", required_argument, NULL, ARG_IMAGE_ROOT }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "direct", no_argument, NULL, ARG_DIRECT }, - { "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL }, - { "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA }, - { "convert-qcow2", required_argument, NULL, ARG_CONVERT_QCOW2 }, - { "sync", required_argument, NULL, ARG_SYNC }, - { "offset", required_argument, NULL, ARG_OFFSET }, - { "size-max", required_argument, NULL, ARG_SIZE_MAX }, - { "class", required_argument, NULL, ARG_CLASS }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - int r, c; +VERB_COMMON_HELP_HIDDEN(help); + +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(0, NULL, NULL); + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_FORCE: + OPTION_LONG("force", NULL, "Force creation of image"): arg_import_flags |= IMPORT_FORCE; break; - case ARG_IMAGE_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root); + OPTION_LONG("image-root", "PATH", "Image root directory"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image_root); if (r < 0) return r; - break; - case ARG_READ_ONLY: + OPTION_LONG("read-only", NULL, "Create a read-only image"): arg_import_flags |= IMPORT_READ_ONLY; break; - case ARG_DIRECT: + OPTION_LONG("direct", NULL, "Import directly to specified file"): arg_import_flags |= IMPORT_DIRECT; break; - case ARG_BTRFS_SUBVOL: - r = parse_boolean_argument("--btrfs-subvol=", optarg, NULL); + OPTION_LONG("btrfs-subvol", "BOOL", + "Controls whether to create a btrfs subvolume instead of a directory"): + r = parse_boolean_argument("--btrfs-subvol=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_BTRFS_SUBVOL, r); break; - case ARG_BTRFS_QUOTA: - r = parse_boolean_argument("--btrfs-quota=", optarg, NULL); + OPTION_LONG("btrfs-quota", "BOOL", + "Controls whether to set up quota for btrfs subvolume"): + r = parse_boolean_argument("--btrfs-quota=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_BTRFS_QUOTA, r); break; - case ARG_CONVERT_QCOW2: - r = parse_boolean_argument("--convert-qcow2=", optarg, NULL); + OPTION_LONG("convert-qcow2", "BOOL", + "Controls whether to convert QCOW2 images to regular disk images"): + r = parse_boolean_argument("--convert-qcow2=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_CONVERT_QCOW2, r); break; - case ARG_SYNC: - r = parse_boolean_argument("--sync=", optarg, NULL); + OPTION_LONG("sync", "BOOL", "Controls whether to sync() before completing"): + r = parse_boolean_argument("--sync=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_SYNC, r); break; - case ARG_OFFSET: { + OPTION_LONG("offset", "BYTES", "Offset to seek to in destination"): { uint64_t u; - r = safe_atou64(optarg, &u); + r = safe_atou64(arg, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --offset= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --offset= argument: %s", arg); if (!FILE_SIZE_VALID(u)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", arg); arg_offset = u; break; } - case ARG_SIZE_MAX: { + OPTION_LONG("size-max", "BYTES", "Maximum number of bytes to write to destination"): { uint64_t u; - r = parse_size(optarg, 1024, &u); + r = parse_size(arg, 1024, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --size-max= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --size-max= argument: %s", arg); if (!FILE_SIZE_VALID(u)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", arg); arg_size_max = u; break; } - case ARG_CLASS: - arg_class = image_class_from_string(optarg); + OPTION_LONG("class", "CLASS", + "Select image class (machine, sysext, confext, portable)"): + arg_class = image_class_from_string(arg); if (arg_class < 0) - return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); - + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg); break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Operate in per-system mode"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Operate in per-user mode"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } /* Make sure offset+size is still in the valid range if both set */ @@ -474,20 +438,10 @@ static int parse_argv(int argc, char *argv[]) { if (arg_runtime_scope == RUNTIME_SCOPE_USER) arg_import_flags |= IMPORT_FOREIGN_UID; + *ret_args = option_parser_get_args(&state); return 1; } -static int import_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "tar", 2, 3, 0, import_tar }, - { "raw", 2, 3, 0, import_raw }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static void parse_env(void) { int r; @@ -521,13 +475,14 @@ static int run(int argc, char *argv[]) { parse_env(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; (void) ignore_signals(SIGPIPE); - return import_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/import/importctl.c b/src/import/importctl.c index c2ddd08f27624..dceb03a6d6da5 100644 --- a/src/import/importctl.c +++ b/src/import/importctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -20,6 +19,7 @@ #include "log.h" #include "main-func.h" #include "oci-util.h" +#include "options.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" @@ -261,7 +261,212 @@ static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { return -r; } -static int import_tar(int argc, char *argv[], void *userdata) { +VERB(verb_pull_tar, "pull-tar", "URL [NAME]", 2, 3, 0, "Download a TAR container image"); +static int verb_pull_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + r = settle_image_class(); + if (r < 0) + return r; + + remote = argv[1]; + if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "URL '%s' is not valid.", remote); + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + local = empty_or_dash_to_null(local); + + if (local) { + r = tar_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!image_name_is_valid(local)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Local name %s is not a suitable image name.", + local); + } + + if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + import_verify_to_string(arg_verify), + FLAGS_SET(arg_import_flags, IMPORT_FORCE)); + } else { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTarEx"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssst", + remote, + local, + image_class_to_string(arg_image_class), + import_verify_to_string(arg_verify), + (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD)); + } + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +VERB(verb_pull_raw, "pull-raw", "URL [NAME]", 2, 3, 0, "Download a RAW container or VM image"); +static int verb_pull_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + r = settle_image_class(); + if (r < 0) + return r; + + remote = argv[1]; + if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "URL '%s' is not valid.", remote); + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + local = empty_or_dash_to_null(local); + + if (local) { + r = raw_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!image_name_is_valid(local)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Local name %s is not a suitable image name.", + local); + } + + if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + import_verify_to_string(arg_verify), + FLAGS_SET(arg_import_flags, IMPORT_FORCE)); + } else { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRawEx"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssst", + remote, + local, + image_class_to_string(arg_image_class), + import_verify_to_string(arg_verify), + (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD)); + } + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +VERB(verb_pull_oci, "pull-oci", "REF [NAME]", 2, 3, 0, "Download an OCI container image"); +static int verb_pull_oci(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL; + const char *local, *remote; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + r = settle_image_class(); + if (r < 0) + return r; + + remote = argv[1]; + _cleanup_free_ char *image = NULL; + r = oci_ref_parse(remote, /* ret_registry= */ NULL, &image, /* ret_tag= */ NULL); + if (r == -EINVAL) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Ref '%s' is not valid.", remote); + if (r < 0) + return log_error_errno(r, "Failed to determine if ref '%s' is valid.", remote); + + if (argc >= 3) + local = argv[2]; + else { + r = path_extract_filename(image, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of reference: %m"); + + local = l; + } + + local = empty_or_dash_to_null(local); + + if (local) { + if (!image_name_is_valid(local)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Local name %s is not a suitable image name.", + local); + } + + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullOci"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "ssst", + remote, + local, + image_class_to_string(arg_image_class), + (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY)); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +VERB(verb_import_tar, "import-tar", "FILE [NAME]", 2, 3, 0, "Import a local TAR container image"); +static int verb_import_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *ll = NULL, *fn = NULL; const char *local = NULL, *path = NULL; @@ -340,7 +545,8 @@ static int import_tar(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int import_raw(int argc, char *argv[], void *userdata) { +VERB(verb_import_raw, "import-raw", "FILE [NAME]", 2, 3, 0, "Import a local RAW container or VM image"); +static int verb_import_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *ll = NULL, *fn = NULL; const char *local = NULL, *path = NULL; @@ -419,7 +625,8 @@ static int import_raw(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int import_fs(int argc, char *argv[], void *userdata) { +VERB(verb_import_fs, "import-fs", "DIRECTORY [NAME]", 2, 3, 0, "Import a local directory container image"); +static int verb_import_fs(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; const char *local = NULL, *path = NULL; _cleanup_free_ char *fn = NULL; @@ -506,7 +713,8 @@ static void determine_compression_from_filename(const char *p) { arg_format = "zstd"; } -static int export_tar(int argc, char *argv[], void *userdata) { +VERB(verb_export_tar, "export-tar", "NAME [FILE]", 2, 3, 0, "Export a TAR container image locally"); +static int verb_export_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_close_ int fd = -EBADF; const char *local = NULL, *path = NULL; @@ -565,7 +773,8 @@ static int export_tar(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int export_raw(int argc, char *argv[], void *userdata) { +VERB(verb_export_raw, "export-raw", "NAME [FILE]", 2, 3, 0, "Export a RAW container or VM image locally"); +static int verb_export_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_close_ int fd = -EBADF; const char *local = NULL, *path = NULL; @@ -624,208 +833,8 @@ static int export_raw(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int pull_tar(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *l = NULL, *ll = NULL; - const char *local, *remote; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - r = settle_image_class(); - if (r < 0) - return r; - - remote = argv[1]; - if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "URL '%s' is not valid.", remote); - - if (argc >= 3) - local = argv[2]; - else { - r = import_url_last_component(remote, &l); - if (r < 0) - return log_error_errno(r, "Failed to get final component of URL: %m"); - - local = l; - } - - local = empty_or_dash_to_null(local); - - if (local) { - r = tar_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!image_name_is_valid(local)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Local name %s is not a suitable image name.", - local); - } - - if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) { - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTar"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssb", - remote, - local, - import_verify_to_string(arg_verify), - FLAGS_SET(arg_import_flags, IMPORT_FORCE)); - } else { - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTarEx"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssst", - remote, - local, - image_class_to_string(arg_image_class), - import_verify_to_string(arg_verify), - (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD)); - } - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int pull_raw(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *l = NULL, *ll = NULL; - const char *local, *remote; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - r = settle_image_class(); - if (r < 0) - return r; - - remote = argv[1]; - if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "URL '%s' is not valid.", remote); - - if (argc >= 3) - local = argv[2]; - else { - r = import_url_last_component(remote, &l); - if (r < 0) - return log_error_errno(r, "Failed to get final component of URL: %m"); - - local = l; - } - - local = empty_or_dash_to_null(local); - - if (local) { - r = raw_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!image_name_is_valid(local)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Local name %s is not a suitable image name.", - local); - } - - if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) { - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRaw"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssb", - remote, - local, - import_verify_to_string(arg_verify), - FLAGS_SET(arg_import_flags, IMPORT_FORCE)); - } else { - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRawEx"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssst", - remote, - local, - image_class_to_string(arg_image_class), - import_verify_to_string(arg_verify), - (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD)); - } - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int pull_oci(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *l = NULL; - const char *local, *remote; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - r = settle_image_class(); - if (r < 0) - return r; - - remote = argv[1]; - _cleanup_free_ char *image = NULL; - r = oci_ref_parse(remote, /* ret_registry= */ NULL, &image, /* ret_tag= */ NULL); - if (r == -EINVAL) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Ref '%s' is not valid.", remote); - if (r < 0) - return log_error_errno(r, "Failed to determine if ref '%s' is valid.", remote); - - if (argc >= 3) - local = argv[2]; - else { - r = path_extract_filename(image, &l); - if (r < 0) - return log_error_errno(r, "Failed to get final component of reference: %m"); - - local = l; - } - - local = empty_or_dash_to_null(local); - - if (local) { - if (!image_name_is_valid(local)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Local name %s is not a suitable image name.", - local); - } - - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullOci"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "ssst", - remote, - local, - image_class_to_string(arg_image_class), - (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY)); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int list_transfers(int argc, char *argv[], void *userdata) { +VERB(verb_list_transfers, "list-transfers", NULL, VERB_ANY, 1, VERB_DEFAULT, "Show list of transfers in progress"); +static int verb_list_transfers(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(table_unrefp) Table *t = NULL; @@ -929,7 +938,8 @@ static int list_transfers(int argc, char *argv[], void *userdata) { return 0; } -static int cancel_transfer(int argc, char *argv[], void *userdata) { +VERB(verb_cancel_transfer, "cancel-transfer", "[ID...]", 2, VERB_ANY, 0, "Cancel a transfer"); +static int verb_cancel_transfer(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -951,7 +961,8 @@ static int cancel_transfer(int argc, char *argv[], void *userdata) { return 0; } -static int list_images(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_list_images, "list-images", "Show list of installed images"); +static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(table_unrefp) Table *t = NULL; @@ -1048,8 +1059,9 @@ static int list_images(int argc, char *argv[], void *userdata) { return 0; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; pager_open(arg_pager_flags); @@ -1058,261 +1070,179 @@ static int help(int argc, char *argv[], void *userdata) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sDownload, import or export disk images%6$s\n" - "\n%3$sCommands:%4$s\n" - " pull-tar URL [NAME] Download a TAR container image\n" - " pull-raw URL [NAME] Download a RAW container or VM image\n" - " pull-oci REF [NAME] Download an OCI container image\n" - " import-tar FILE [NAME] Import a local TAR container image\n" - " import-raw FILE [NAME] Import a local RAW container or VM image\n" - " import-fs DIRECTORY [NAME] Import a local directory container image\n" - " export-tar NAME [FILE] Export a TAR container image locally\n" - " export-raw NAME [FILE] Export a RAW container or VM image locally\n" - " list-transfers Show list of transfers in progress\n" - " cancel-transfer [ID...] Cancel a transfer\n" - " list-images Show list of installed images\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --no-ask-password Do not ask for system passwords\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --system Connect to system machine manager\n" - " --user Connect to user machine manager\n" - " --read-only Create read-only image\n" - " -q --quiet Suppress output\n" - " --json=pretty|short|off Generate JSON output\n" - " -j Equvilant to --json=pretty on TTY, --json=short\n" - " otherwise\n" - " --verify=MODE Verification mode for downloaded images (no,\n" - " checksum, signature)\n" - " --format=xz|gzip|bzip2|zstd\n" - " Desired output format for export\n" - " --force Install image even if already exists\n" - " --class=TYPE Install as the specified TYPE\n" - " -m Install as --class=machine, machine image\n" - " -P Install as --class=portable,\n" - " portable service image\n" - " -S Install as --class=sysext, system extension image\n" - " -C Install as --class=confext,\n" - " configuration extension image\n" - " --keep-download=BOOL Control whether to keep pristine copy of download\n" - " -N Same as --keep-download=no\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sDownload, import or export disk images%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_NO_ASK_PASSWORD, - ARG_READ_ONLY, - ARG_JSON, - ARG_VERIFY, - ARG_FORCE, - ARG_FORMAT, - ARG_CLASS, - ARG_KEEP_DOWNLOAD, - ARG_SYSTEM, - ARG_USER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "json", required_argument, NULL, ARG_JSON }, - { "quiet", no_argument, NULL, 'q' }, - { "verify", required_argument, NULL, ARG_VERIFY }, - { "force", no_argument, NULL, ARG_FORCE }, - { "format", required_argument, NULL, ARG_FORMAT }, - { "class", required_argument, NULL, ARG_CLASS }, - { "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - int c, r; +VERB_COMMON_HELP(help); + +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - for (;;) { - c = getopt_long(argc, argv, "hH:M:jqmPSCN", options, NULL); - if (c < 0) - break; + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(0, NULL, NULL); + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case ARG_READ_ONLY: + OPTION_LONG("system", NULL, "Connect to system machine manager"): + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + OPTION_LONG("user", NULL, "Connect to user machine manager"): + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + + OPTION_LONG("read-only", NULL, "Create read-only image"): arg_import_flags |= IMPORT_READ_ONLY; arg_import_flags_mask |= IMPORT_READ_ONLY; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress output"): arg_quiet = true; break; - case ARG_VERIFY: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(import_verify, ImportVerify, _IMPORT_VERIFY_MAX); - - r = import_verify_from_string(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --verify= setting: %s", optarg); - arg_verify = r; + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); + if (r <= 0) + return r; + arg_legend = false; break; - case ARG_FORCE: - arg_import_flags |= IMPORT_FORCE; - arg_import_flags_mask |= IMPORT_FORCE; + OPTION_COMMON_LOWERCASE_J: + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + arg_legend = false; break; - case ARG_FORMAT: - if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2", "zstd")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown format: %s", optarg); + OPTION_LONG("verify", "MODE", + "Verification mode for downloaded images (no, checksum, signature)"): + if (streq(arg, "help")) + return DUMP_STRING_TABLE(import_verify, ImportVerify, _IMPORT_VERIFY_MAX); - arg_format = optarg; + r = import_verify_from_string(arg); + if (r < 0) + return log_error_errno(r, "Failed to parse --verify= setting: %s", arg); + arg_verify = r; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; - - arg_legend = false; + OPTION_LONG("format", "FORMAT", + "Desired output format for export (zstd, xz, gzip, bzip2)"): + if (!STR_IN_SET(arg, "uncompressed", "xz", "gzip", "bzip2", "zstd")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown format: %s", arg); + arg_format = arg; break; - case 'j': - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; - arg_legend = false; + OPTION_LONG("force", NULL, "Install image even if already exists"): + arg_import_flags |= IMPORT_FORCE; + arg_import_flags_mask |= IMPORT_FORCE; break; - case ARG_CLASS: - arg_image_class = image_class_from_string(optarg); + OPTION_LONG("class", "TYPE", "Install as the specified TYPE"): + arg_image_class = image_class_from_string(arg); if (arg_image_class < 0) - return log_error_errno(arg_image_class, "Failed to parse --class= parameter: %s", optarg); + return log_error_errno(arg_image_class, "Failed to parse --class= parameter: %s", arg); break; - case 'm': + OPTION_SHORT('m', NULL, "Install as --class=machine, machine image"): arg_image_class = IMAGE_MACHINE; break; - case 'P': + OPTION_SHORT('P', NULL, "Install as --class=portable, portable service image"): arg_image_class = IMAGE_PORTABLE; break; - case 'S': + OPTION_SHORT('S', NULL, "Install as --class=sysext, system extension image"): arg_image_class = IMAGE_SYSEXT; break; - case 'C': + OPTION_SHORT('C', NULL, "Install as --class=confext, configuration extension image"): arg_image_class = IMAGE_CONFEXT; break; - case ARG_KEEP_DOWNLOAD: - r = parse_boolean(optarg); + OPTION_LONG("keep-download", "BOOL", + "Control whether to keep pristine copy of download"): + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --keep-download= value: %s", optarg); + return log_error_errno(r, "Failed to parse --keep-download= value: %s", arg); SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, r); arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD; break; - case 'N': - arg_import_flags_mask &= ~IMPORT_PULL_KEEP_DOWNLOAD; + OPTION_SHORT('N', NULL, "Same as --keep-download=no"): + arg_import_flags &= ~IMPORT_PULL_KEEP_DOWNLOAD; arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD; break; - - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; - break; - - case ARG_SYSTEM: - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } + *ret_args = option_parser_get_args(&state); return 1; } -static int importctl_main(int argc, char *argv[], sd_bus *bus) { - - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "import-tar", 2, 3, 0, import_tar }, - { "import-raw", 2, 3, 0, import_raw }, - { "import-fs", 2, 3, 0, import_fs }, - { "export-tar", 2, 3, 0, export_tar }, - { "export-raw", 2, 3, 0, export_raw }, - { "pull-tar", 2, 3, 0, pull_tar }, - { "pull-oci", 2, 3, 0, pull_oci }, - { "pull-raw", 2, 3, 0, pull_raw }, - { "list-transfers", VERB_ANY, 1, VERB_DEFAULT, list_transfers }, - { "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer }, - { "list-images", VERB_ANY, 1, 0, list_images }, - {} - }; - - return dispatch_verb(argc, argv, verbs, bus); -} - static int run(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -1320,7 +1250,8 @@ static int run(int argc, char *argv[]) { setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -1330,7 +1261,7 @@ static int run(int argc, char *argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - return importctl_main(argc, argv, bus); + return dispatch_verb_with_args(args, bus); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/import/importd.c b/src/import/importd.c index d3363d446cb67..c329491386cf0 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -418,6 +418,8 @@ static int transfer_on_log(sd_event_source *s, int fd, uint32_t revents, void *u return 0; } + /* Silence static analyzers, l is bounded by read() count: sizeof - log_message_size */ + assert((size_t) l <= sizeof(t->log_message) - t->log_message_size); t->log_message_size += l; transfer_send_logs(t, false); @@ -1802,10 +1804,10 @@ static int make_transfer_json(Transfer *t, sd_json_variant **ret) { r = sd_json_buildo(ret, SD_JSON_BUILD_PAIR_UNSIGNED("id", t->id), - SD_JSON_BUILD_PAIR("type", JSON_BUILD_STRING_UNDERSCORIFY(transfer_type_to_string(t->type))), + JSON_BUILD_PAIR_ENUM("type", transfer_type_to_string(t->type)), SD_JSON_BUILD_PAIR_STRING("remote", t->remote), SD_JSON_BUILD_PAIR_STRING("local", t->local), - SD_JSON_BUILD_PAIR("class", JSON_BUILD_STRING_UNDERSCORIFY(image_class_to_string(t->class))), + JSON_BUILD_PAIR_ENUM("class", image_class_to_string(t->class)), SD_JSON_BUILD_PAIR_REAL("percent", transfer_percent_as_double(t))); if (r < 0) return log_error_errno(r, "Failed to build transfer JSON data: %m"); @@ -1837,7 +1839,7 @@ static int vl_method_list_transfers(sd_varlink *link, sd_json_variant *parameter if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.Import.NoTransfers"); + r = sd_varlink_set_sentinel(link, "io.systemd.Import.NoTransfers"); if (r < 0) return r; diff --git a/src/import/meson.build b/src/import/meson.build index c0589ddd4ed57..c2879c5d843cf 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -4,14 +4,6 @@ if conf.get('ENABLE_IMPORTD') != 1 subdir_done() endif -common_deps = [ - libbzip2, - libcurl, - libxz, - libz, - libzstd, -] - executables += [ libexec_template + { 'name' : 'systemd-importd', @@ -23,16 +15,14 @@ executables += [ 'extract' : files( 'oci-util.c', 'import-common.c', - 'import-compress.c', 'qcow2-util.c', ), - 'dependencies' : [common_deps, threads], + 'dependencies' : threads, }, libexec_template + { 'name' : 'systemd-pull', 'public' : true, 'sources' : files( - 'curl-util.c', 'pull.c', 'pull-common.c', 'pull-job.c', @@ -41,9 +31,7 @@ executables += [ 'pull-tar.c', ), 'objects' : ['systemd-importd'], - 'dependencies' : common_deps + [ - libopenssl, - ], + 'dependencies' : libopenssl_cflags, }, libexec_template + { 'name' : 'systemd-import', @@ -54,7 +42,6 @@ executables += [ 'import-tar.c', ), 'objects' : ['systemd-importd'], - 'dependencies' : common_deps, }, libexec_template + { 'name' : 'systemd-import-fs', @@ -63,7 +50,6 @@ executables += [ 'import-fs.c', ), 'objects' : ['systemd-importd'], - 'dependencies' : common_deps, }, libexec_template + { 'name' : 'systemd-export', @@ -74,33 +60,29 @@ executables += [ 'export-raw.c', ), 'objects' : ['systemd-importd'], - 'dependencies' : common_deps, }, executable_template + { 'name' : 'importctl', 'public' : true, 'sources' : files('importctl.c'), 'objects': ['systemd-importd'], - 'dependencies' : common_deps, }, generator_template + { 'name' : 'systemd-import-generator', 'sources' : files('import-generator.c'), }, test_template + { - 'sources' : files('test-tar-extract.c'), + 'sources' : files('test-tar.c'), 'type' : 'manual', }, test_template + { 'sources' : files('test-qcow2.c'), 'objects' : ['systemd-importd'], - 'dependencies' : common_deps, 'type' : 'manual', }, test_template + { 'sources' : files('test-oci-util.c'), 'objects': ['systemd-importd'], - 'dependencies' : common_deps, }, ] diff --git a/src/import/oci-registry/registry.fedora.oci-registry b/src/import/oci-registry/registry.fedora.oci-registry index e416bf2853523..e7d2ccdb2a062 100644 --- a/src/import/oci-registry/registry.fedora.oci-registry +++ b/src/import/oci-registry/registry.fedora.oci-registry @@ -1,3 +1,3 @@ { - "defaultRegistry" : "registry.fedoraproject.org" + "overrideRegistry" : "registry.fedoraproject.org" } diff --git a/src/import/oci-util.c b/src/import/oci-util.c index 37a617b28ce1a..d8c16bc3d9785 100644 --- a/src/import/oci-util.c +++ b/src/import/oci-util.c @@ -79,10 +79,10 @@ bool oci_tag_is_valid(const char *n) { * [a-zA-Z0-9_][a-zA-Z0-9._-]{0,127} */ - if (!strchr(LETTERS DIGITS "_", n[0])) + if (!strchr(ALPHANUMERICAL "_", n[0])) return false; - size_t l = strspn(n + 1, LETTERS DIGITS "._-"); + size_t l = strspn(n + 1, ALPHANUMERICAL "._-"); if (l > 126) return false; if (n[1+l] != 0) @@ -153,7 +153,8 @@ int oci_ref_normalize(char **protocol, char **registry, char **image, char **tag assert(protocol); assert(registry); - assert(image && *image); + assert(image); + assert(*image); assert(tag); /* OCI container reference are supposed to have the form /:. Except that it's @@ -379,6 +380,7 @@ static const char *const go_arch_table[_ARCHITECTURE_MAX] = { DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(go_arch, Architecture); char* urlescape(const char *s) { + POINTER_MAY_BE_NULL(s); size_t l = strlen_ptr(s); _cleanup_free_ char *t = new(char, l * 3 + 1); @@ -387,7 +389,7 @@ char* urlescape(const char *s) { char *p = t; for (; s && *s; s++) { - if (strchr(LETTERS DIGITS ".-_", *s)) + if (strchr(ALPHANUMERICAL ".-_", *s)) *(p++) = *s; else { *(p++) = '%'; diff --git a/src/import/pull-common.c b/src/import/pull-common.c index cc06fe4f1db0a..49f87bcac44e2 100644 --- a/src/import/pull-common.c +++ b/src/import/pull-common.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "sd-id128.h" #include "alloc-util.h" @@ -8,6 +10,7 @@ #include "fd-util.h" #include "hexdecoct.h" #include "io-util.h" +#include "iovec-util.h" #include "log.h" #include "memory-util.h" #include "os-util.h" @@ -622,6 +625,7 @@ int pull_job_restart_with_sha256sum(PullJob *j, char **ret) { int r; assert(j); + assert(ret); /* Generic implementation of a PullJobNotFound handler, that restarts the job requesting SHA256SUMS */ @@ -675,6 +679,7 @@ int pull_job_restart_with_signature(PullJob *j, char **ret) { int r; assert(j); + assert(ret); /* Generic implementation of a PullJobNotFound handler, that restarts the job requesting a different * signature file. After the initial file, additional *.sha256.gpg, SHA256SUMS.gpg and SHA256SUMS.asc diff --git a/src/import/pull-job.c b/src/import/pull-job.c index dcdb1fe8fa7b3..4c3fb05dd3533 100644 --- a/src/import/pull-job.c +++ b/src/import/pull-job.c @@ -3,8 +3,11 @@ #include #include #include +#include #include "alloc-util.h" +#include "compress.h" +#include "crypto-util.h" #include "curl-util.h" #include "fd-util.h" #include "format-util.h" @@ -51,12 +54,12 @@ PullJob* pull_job_unref(PullJob *j) { pull_job_close_disk_fd(j); curl_glue_remove_and_free(j->glue, j->curl); - curl_slist_free_all(j->request_header); + sym_curl_slist_free_all(j->request_header); - import_compress_free(&j->compress); + j->compress = compressor_free(j->compress); if (j->checksum_ctx) - EVP_MD_CTX_free(j->checksum_ctx); + sym_EVP_MD_CTX_free(j->checksum_ctx); free(j->url); free(j->etag); @@ -134,10 +137,10 @@ int pull_job_restart(PullJob *j, const char *new_url) { curl_glue_remove_and_free(j->glue, j->curl); j->curl = NULL; - import_compress_free(&j->compress); + j->compress = compressor_free(j->compress); if (j->checksum_ctx) { - EVP_MD_CTX_free(j->checksum_ctx); + sym_EVP_MD_CTX_free(j->checksum_ctx); j->checksum_ctx = NULL; } @@ -163,13 +166,13 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { CURLcode code; int r; - if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char **)&j) != CURLE_OK) + if (sym_curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char **)&j) != CURLE_OK) return; if (!j || IN_SET(j->state, PULL_JOB_DONE, PULL_JOB_FAILED)) return; - code = curl_easy_getinfo(curl, CURLINFO_SCHEME, &scheme); + code = sym_curl_easy_getinfo(curl, CURLINFO_SCHEME, &scheme); if (code != CURLE_OK || !scheme) { r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve URL scheme."); goto finish; @@ -196,16 +199,16 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { } if (result != CURLE_OK) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Transfer failed: %s", curl_easy_strerror(result)); + r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Transfer failed: %s", sym_curl_easy_strerror(result)); goto finish; } if (STRCASE_IN_SET(scheme, "HTTP", "HTTPS")) { long status; - code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + code = sym_curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); if (code != CURLE_OK) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", curl_easy_strerror(code)); + r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", sym_curl_easy_strerror(code)); goto finish; } @@ -235,9 +238,9 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { if (r < 0) goto finish; - code = curl_easy_getinfo(j->curl, CURLINFO_RESPONSE_CODE, &status); + code = sym_curl_easy_getinfo(j->curl, CURLINFO_RESPONSE_CODE, &status); if (code != CURLE_OK) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", curl_easy_strerror(code)); + r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", sym_curl_easy_strerror(code)); goto finish; } @@ -278,7 +281,7 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { goto finish; } - r = EVP_DigestFinal_ex(j->checksum_ctx, j->checksum.iov_base, &checksum_len); + r = sym_EVP_DigestFinal_ex(j->checksum_ctx, j->checksum.iov_base, &checksum_len); if (r == 0) { r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get checksum."); goto finish; @@ -293,11 +296,11 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { goto finish; } - log_debug("%s of %s is %s.", EVP_MD_CTX_get0_name(j->checksum_ctx), pull_job_description(j), h); + log_debug("%s of %s is %s.", sym_EVP_MD_CTX_get0_name(j->checksum_ctx), pull_job_description(j), h); } if (iovec_is_set(&j->expected_checksum) && - iovec_memcmp(&j->checksum, &j->expected_checksum) != 0) { + !iovec_equal(&j->checksum, &j->expected_checksum)) { r = log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Checksum of downloaded resource does not match expected checksum, yikes."); goto finish; } @@ -447,13 +450,13 @@ static int pull_job_write_compressed(PullJob *j, const struct iovec *data) { "Content length incorrect."); if (j->checksum_ctx) { - r = EVP_DigestUpdate(j->checksum_ctx, data->iov_base, data->iov_len); + r = sym_EVP_DigestUpdate(j->checksum_ctx, data->iov_base, data->iov_len); if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Could not hash chunk."); } - r = import_uncompress(&j->compress, data->iov_base, data->iov_len, pull_job_write_uncompressed, j); + r = decompressor_push(j->compress, data->iov_base, data->iov_len, pull_job_write_uncompressed, j); if (r < 0) return r; @@ -484,11 +487,15 @@ static int pull_job_open_disk(PullJob *j) { } if (j->calc_checksum) { - j->checksum_ctx = EVP_MD_CTX_new(); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + + j->checksum_ctx = sym_EVP_MD_CTX_new(); if (!j->checksum_ctx) return log_oom(); - r = EVP_DigestInit_ex(j->checksum_ctx, EVP_sha256(), NULL); + r = sym_EVP_DigestInit_ex(j->checksum_ctx, sym_EVP_sha256(), NULL); if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize hash context."); @@ -502,13 +509,13 @@ static int pull_job_detect_compression(PullJob *j) { assert(j); - r = import_uncompress_detect(&j->compress, j->payload.iov_base, j->payload.iov_len); + r = decompressor_detect(&j->compress, j->payload.iov_base, j->payload.iov_len); if (r < 0) return log_error_errno(r, "Failed to initialize compressor: %m"); if (r == 0) return 0; - log_debug("Stream is compressed: %s", import_compress_type_to_string(j->compress.type)); + log_debug("Stream is compressed: %s", compression_to_string(compressor_type(j->compress))); r = pull_job_open_disk(j); if (r < 0) @@ -588,9 +595,9 @@ static size_t pull_job_header_callback(void *contents, size_t size, size_t nmemb assert(j->state == PULL_JOB_ANALYZING); - code = curl_easy_getinfo(j->curl, CURLINFO_RESPONSE_CODE, &status); + code = sym_curl_easy_getinfo(j->curl, CURLINFO_RESPONSE_CODE, &status); if (code != CURLE_OK) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", curl_easy_strerror(code)); + r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", sym_curl_easy_strerror(code)); goto fail; } @@ -780,7 +787,7 @@ int pull_job_add_request_header(PullJob *j, const char *hdr) { if (j->request_header) { struct curl_slist *l; - l = curl_slist_append(j->request_header, hdr); + l = sym_curl_slist_append(j->request_header, hdr); if (!l) return -ENOMEM; @@ -823,29 +830,29 @@ int pull_job_begin(PullJob *j) { } if (j->request_header) { - if (curl_easy_setopt(j->curl, CURLOPT_HTTPHEADER, j->request_header) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_HTTPHEADER, j->request_header) != CURLE_OK) return -EIO; } - if (curl_easy_setopt(j->curl, CURLOPT_WRITEFUNCTION, pull_job_write_callback) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_WRITEFUNCTION, pull_job_write_callback) != CURLE_OK) return -EIO; - if (curl_easy_setopt(j->curl, CURLOPT_WRITEDATA, j) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_WRITEDATA, j) != CURLE_OK) return -EIO; - if (curl_easy_setopt(j->curl, CURLOPT_HEADERFUNCTION, pull_job_header_callback) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_HEADERFUNCTION, pull_job_header_callback) != CURLE_OK) return -EIO; - if (curl_easy_setopt(j->curl, CURLOPT_HEADERDATA, j) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_HEADERDATA, j) != CURLE_OK) return -EIO; - if (curl_easy_setopt(j->curl, CURLOPT_XFERINFOFUNCTION, pull_job_progress_callback) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_XFERINFOFUNCTION, pull_job_progress_callback) != CURLE_OK) return -EIO; - if (curl_easy_setopt(j->curl, CURLOPT_XFERINFODATA, j) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_XFERINFODATA, j) != CURLE_OK) return -EIO; - if (curl_easy_setopt(j->curl, CURLOPT_NOPROGRESS, 0L) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_NOPROGRESS, 0L) != CURLE_OK) return -EIO; r = curl_glue_add(j->glue, j->curl); diff --git a/src/import/pull-job.h b/src/import/pull-job.h index ea58b62f3bfa9..0b878292f096b 100644 --- a/src/import/pull-job.h +++ b/src/import/pull-job.h @@ -3,10 +3,9 @@ #include #include +#include #include "shared-forward.h" -#include "import-compress.h" -#include "openssl-util.h" typedef struct CurlGlue CurlGlue; typedef struct PullJob PullJob; @@ -73,7 +72,7 @@ typedef struct PullJob { usec_t mtime; char *content_type; - ImportCompress compress; + Compressor *compress; unsigned progress_percent; usec_t start_usec; diff --git a/src/import/pull-oci.c b/src/import/pull-oci.c index e9e0a1c6b3419..acea93b09de9a 100644 --- a/src/import/pull-oci.c +++ b/src/import/pull-oci.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "sd-event.h" #include "sd-json.h" #include "sd-varlink.h" @@ -29,6 +31,7 @@ #include "pull-oci.h" #include "rm-rf.h" #include "set.h" +#include "sha256-fundamental.h" #include "signal-util.h" #include "stat-util.h" #include "string-util.h" @@ -200,7 +203,7 @@ int oci_pull_new( return 0; } -static int pull_job_payload_as_json(PullJob *j, sd_json_variant **ret) { +static int pull_job_payload_as_json_object(PullJob *j, sd_json_variant **ret) { int r; assert(j); @@ -214,7 +217,7 @@ static int pull_job_payload_as_json(PullJob *j, sd_json_variant **ret) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; unsigned line = 0, column = 0; - r = sd_json_parse((char*) j->payload.iov_base, /* flags= */ 0, &v, &line, &column); + r = sd_json_parse((char*) j->payload.iov_base, SD_JSON_PARSE_MUST_BE_OBJECT, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse JSON at position %u:%u: %m", line, column); @@ -391,7 +394,7 @@ static int oci_pull_process_index(OciPull *i, PullJob *j) { * https://github.com/opencontainers/image-spec/blob/main/image-index.md */ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - r = pull_job_payload_as_json(j, &v); + r = pull_job_payload_as_json_object(j, &v); if (r < 0) return r; @@ -489,7 +492,7 @@ static int oci_pull_job_on_open_disk(PullJob *j) { DISSECT_IMAGE_FOREIGN_UID, &st->tree_fd); if (r < 0) - return log_error_errno(r, "Failed to mount directory via mountsd: %m"); + return log_error_errno(r, "Failed to mount directory via mountfsd: %m"); } else { if (i->flags & IMPORT_BTRFS_SUBVOL) r = btrfs_subvol_make_fallback(AT_FDCWD, st->temp_path, 0755); @@ -783,7 +786,7 @@ static int oci_pull_process_manifest(OciPull *i, PullJob *j) { * https://github.com/opencontainers/image-spec/blob/main/manifest.md */ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - r = pull_job_payload_as_json(j, &v); + r = pull_job_payload_as_json_object(j, &v); if (r < 0) return r; @@ -958,7 +961,7 @@ static int oci_pull_save_nspawn_settings(OciPull *i) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; unsigned line = 0, column = 0; - r = sd_json_parse((char*) i->config.iov_base, /* flags= */ 0, &v, &line, &column); + r = sd_json_parse((char*) i->config.iov_base, SD_JSON_PARSE_MUST_BE_OBJECT, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse JSON config data at position %u:%u: %m", line, column); @@ -1193,7 +1196,7 @@ static int oci_pull_make_local(OciPull *i) { return r; if (!iovec_is_set(&i->config) || - iovec_memcmp(&i->config, &IOVEC_MAKE_STRING("{}")) == 0) + iovec_equal(&i->config, &IOVEC_MAKE_STRING("{}"))) log_info("Image has no configuration, not saving."); else { r = oci_pull_save_nspawn_settings(i); @@ -1340,7 +1343,7 @@ static void oci_pull_job_on_finished_bearer_token(PullJob *j) { goto finish; } - r = pull_job_payload_as_json(j, &v); + r = pull_job_payload_as_json_object(j, &v); if (r < 0) goto finish; diff --git a/src/import/pull-raw.c b/src/import/pull-raw.c index 31a08eb24ae6d..0ddde7c091962 100644 --- a/src/import/pull-raw.c +++ b/src/import/pull-raw.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "sd-daemon.h" #include "sd-event.h" @@ -11,6 +13,7 @@ #include "import-common.h" #include "import-util.h" #include "install-file.h" +#include "iovec-util.h" #include "log.h" #include "mkdir-label.h" #include "pull-common.h" diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index bfc218b6bcbd0..fe18636eb7d9d 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "sd-daemon.h" #include "sd-event.h" #include "sd-varlink.h" @@ -280,7 +282,7 @@ static int tar_pull_make_local_copy(TarPull *p) { _cleanup_(sd_varlink_unrefp) sd_varlink *mountfsd_link = NULL; r = mountfsd_connect(&mountfsd_link); if (r < 0) - return log_error_errno(r, "Failed to connect to mountsd: %m"); + return log_error_errno(r, "Failed to connect to mountfsd: %m"); /* Usually, tar_pull_job_on_open_disk_tar() would allocate ->tree_fd for us, but if * already downloaded the image before, and are just making a copy of the original @@ -443,7 +445,7 @@ static void tar_pull_job_on_finished(PullJob *j) { else if (j == p->settings_job) log_info_errno(j->error, "Settings file could not be retrieved, proceeding without."); else - assert("unexpected job"); + assert_not_reached(); } /* This is invoked if either the download completed successfully, or the download was skipped because diff --git a/src/import/pull.c b/src/import/pull.c index e2f816152698f..651e6b5ff5b60 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -11,6 +10,7 @@ #include "build.h" #include "discover-image.h" #include "env-util.h" +#include "format-table.h" #include "hexdecoct.h" #include "import-common.h" #include "import-util.h" @@ -19,6 +19,7 @@ #include "log.h" #include "main-func.h" #include "oci-util.h" +#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" @@ -46,6 +47,8 @@ static int normalize_local(const char *local, const char *url, char **ret) { _cleanup_free_ char *ll = NULL; int r; + assert(ret); + if (arg_import_flags & IMPORT_DIRECT) { if (!local) @@ -117,7 +120,8 @@ static void on_tar_finished(TarPull *pull, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int pull_tar(int argc, char *argv[], void *userdata) { +VERB(verb_tar, "tar", "URL [NAME]", 2, 3, 0, "Download a TAR image"); +static int verb_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(tar_pull_unrefp) TarPull *pull = NULL; @@ -187,7 +191,8 @@ static void on_raw_finished(RawPull *pull, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int pull_raw(int argc, char *argv[], void *userdata) { +VERB(verb_raw, "raw", "URL [NAME]", 2, 3, 0, "Download a RAW image"); +static int verb_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(raw_pull_unrefp) RawPull *pull = NULL; @@ -256,7 +261,8 @@ static void on_oci_finished(OciPull *pull, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int pull_oci(int argc, char *argv[], void *userdata) { +VERB(verb_oci, "oci", "REF [NAME]", 2, 3, 0, "Download an OCI image"); +static int verb_oci(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; const char *ref = argv[1]; @@ -311,133 +317,80 @@ static int pull_oci(int argc, char *argv[], void *userdata) { return -r; } -static int help(int argc, char *argv[], void *userdata) { - - printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sDownload disk images.%5$s\n" - "\n%2$sCommands:%3$s\n" - " tar URL [NAME] Download a TAR image\n" - " raw URL [NAME] Download a RAW image\n" - " oci REF [NAME] Download an OCI image\n" - "\n%2$sOptions:%3$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --force Force creation of image\n" - " --verify=MODE Verify downloaded image, one of: 'no',\n" - " 'checksum', 'signature' or literal SHA256 hash\n" - " --settings=BOOL Download settings file with image\n" - " --roothash=BOOL Download root hash file with image\n" - " --roothash-signature=BOOL\n" - " Download root hash signature file with image\n" - " --verity=BOOL Download verity file with image\n" - " --image-root=PATH Image root directory\n" - " --read-only Create a read-only image\n" - " --direct Download directly to specified file\n" - " --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n" - " instead of a directory\n" - " --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n" - " subvolume\n" - " --convert-qcow2=BOOL Controls whether to convert QCOW2 images to\n" - " regular disk images\n" - " --sync=BOOL Controls whether to sync() before completing\n" - " --offset=BYTES Offset to seek to in destination\n" - " --size-max=BYTES Maximum number of bytes to write to destination\n" - " --class=CLASS Select image class (machine, sysext, confext,\n" - " portable)\n" - " --keep-download=BOOL Keep a copy pristine copy of the downloaded file\n" - " around\n" - " --system Operate in per-system mode\n" - " --user Operate in per-user mode\n", +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; + int r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "%sDownload disk images.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", + ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_FORCE, - ARG_IMAGE_ROOT, - ARG_VERIFY, - ARG_SETTINGS, - ARG_ROOTHASH, - ARG_ROOTHASH_SIGNATURE, - ARG_VERITY, - ARG_READ_ONLY, - ARG_DIRECT, - ARG_BTRFS_SUBVOL, - ARG_BTRFS_QUOTA, - ARG_CONVERT_QCOW2, - ARG_SYNC, - ARG_OFFSET, - ARG_SIZE_MAX, - ARG_CLASS, - ARG_KEEP_DOWNLOAD, - ARG_SYSTEM, - ARG_USER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "force", no_argument, NULL, ARG_FORCE }, - { "image-root", required_argument, NULL, ARG_IMAGE_ROOT }, - { "verify", required_argument, NULL, ARG_VERIFY }, - { "settings", required_argument, NULL, ARG_SETTINGS }, - { "roothash", required_argument, NULL, ARG_ROOTHASH }, - { "roothash-signature", required_argument, NULL, ARG_ROOTHASH_SIGNATURE }, - { "verity", required_argument, NULL, ARG_VERITY }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "direct", no_argument, NULL, ARG_DIRECT }, - { "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL }, - { "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA }, - { "convert-qcow2", required_argument, NULL, ARG_CONVERT_QCOW2 }, - { "sync", required_argument, NULL, ARG_SYNC }, - { "offset", required_argument, NULL, ARG_OFFSET }, - { "size-max", required_argument, NULL, ARG_SIZE_MAX }, - { "class", required_argument, NULL, ARG_CLASS }, - { "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - int c, r; +VERB_COMMON_HELP_HIDDEN(help); + +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; bool auto_settings = true, auto_keep_download = true; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(0, NULL, NULL); + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_FORCE: + OPTION_LONG("force", NULL, "Force creation of image"): arg_import_flags |= IMPORT_FORCE; break; - case ARG_IMAGE_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root); + OPTION_LONG("image-root", "PATH", "Image root directory"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image_root); if (r < 0) return r; - break; - case ARG_VERIFY: { + OPTION_LONG("verify", "MODE", + "Verify downloaded image, one of: 'no', 'checksum', 'signature' or literal SHA256 hash"): { ImportVerify v; - v = import_verify_from_string(optarg); + v = import_verify_from_string(arg); if (v < 0) { _cleanup_free_ void *h = NULL; size_t n; @@ -445,17 +398,16 @@ static int parse_argv(int argc, char *argv[]) { /* If this is not a valid verification mode, maybe it's a literally specified * SHA256 hash? We can handle that too... */ - r = unhexmem(optarg, &h, &n); + r = unhexmem(arg, &h, &n); if (r < 0 || n == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid verification setting: %s", optarg); + "Invalid verification setting: %s", arg); if (n != 32) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "64 hex character SHA256 hash required when specifying explicit checksum, %zu specified", n * 2); iovec_done(&arg_checksum); - arg_checksum.iov_base = TAKE_PTR(h); - arg_checksum.iov_len = n; + arg_checksum = IOVEC_MAKE(TAKE_PTR(h), n); arg_import_flags &= ~(IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY); arg_verify = _IMPORT_VERIFY_INVALID; @@ -465,8 +417,8 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_SETTINGS: - r = parse_boolean_argument("--settings=", optarg, NULL); + OPTION_LONG("settings", "BOOL", "Download settings file with image"): + r = parse_boolean_argument("--settings=", arg, NULL); if (r < 0) return r; @@ -474,8 +426,8 @@ static int parse_argv(int argc, char *argv[]) { auto_settings = false; break; - case ARG_ROOTHASH: - r = parse_boolean_argument("--roothash=", optarg, NULL); + OPTION_LONG("roothash", "BOOL", "Download root hash file with image"): + r = parse_boolean_argument("--roothash=", arg, NULL); if (r < 0) return r; @@ -486,118 +438,113 @@ static int parse_argv(int argc, char *argv[]) { SET_FLAG(arg_import_flags, IMPORT_PULL_ROOTHASH_SIGNATURE, false); break; - case ARG_ROOTHASH_SIGNATURE: - r = parse_boolean_argument("--roothash-signature=", optarg, NULL); + OPTION_LONG("roothash-signature", "BOOL", + "Download root hash signature file with image"): + r = parse_boolean_argument("--roothash-signature=", arg, NULL); if (r < 0) return r; SET_FLAG(arg_import_flags, IMPORT_PULL_ROOTHASH_SIGNATURE, r); break; - case ARG_VERITY: - r = parse_boolean_argument("--verity=", optarg, NULL); + OPTION_LONG("verity", "BOOL", "Download verity file with image"): + r = parse_boolean_argument("--verity=", arg, NULL); if (r < 0) return r; SET_FLAG(arg_import_flags, IMPORT_PULL_VERITY, r); break; - case ARG_READ_ONLY: + OPTION_LONG("read-only", NULL, "Create a read-only image"): arg_import_flags |= IMPORT_READ_ONLY; break; - case ARG_DIRECT: + OPTION_LONG("direct", NULL, "Download directly to specified file"): arg_import_flags |= IMPORT_DIRECT; arg_import_flags &= ~(IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY); break; - case ARG_BTRFS_SUBVOL: - r = parse_boolean_argument("--btrfs-subvol=", optarg, NULL); + OPTION_LONG("btrfs-subvol", "BOOL", + "Controls whether to create a btrfs subvolume instead of a directory"): + r = parse_boolean_argument("--btrfs-subvol=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_BTRFS_SUBVOL, r); break; - case ARG_BTRFS_QUOTA: - r = parse_boolean_argument("--btrfs-quota=", optarg, NULL); + OPTION_LONG("btrfs-quota", "BOOL", + "Controls whether to set up quota for btrfs subvolume"): + r = parse_boolean_argument("--btrfs-quota=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_BTRFS_QUOTA, r); break; - case ARG_CONVERT_QCOW2: - r = parse_boolean_argument("--convert-qcow2=", optarg, NULL); + OPTION_LONG("convert-qcow2", "BOOL", + "Controls whether to convert QCOW2 images to regular disk images"): + r = parse_boolean_argument("--convert-qcow2=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_CONVERT_QCOW2, r); break; - case ARG_SYNC: - r = parse_boolean_argument("--sync=", optarg, NULL); + OPTION_LONG("sync", "BOOL", "Controls whether to sync() before completing"): + r = parse_boolean_argument("--sync=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_SYNC, r); break; - case ARG_OFFSET: { + OPTION_LONG("offset", "BYTES", "Offset to seek to in destination"): { uint64_t u; - r = safe_atou64(optarg, &u); + r = safe_atou64(arg, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --offset= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --offset= argument: %s", arg); if (!FILE_SIZE_VALID(u)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", arg); arg_offset = u; break; } - case ARG_SIZE_MAX: { + OPTION_LONG("size-max", "BYTES", "Maximum number of bytes to write to destination"): { uint64_t u; - r = parse_size(optarg, 1024, &u); + r = parse_size(arg, 1024, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --size-max= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --size-max= argument: %s", arg); if (!FILE_SIZE_VALID(u)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", arg); arg_size_max = u; break; } - case ARG_CLASS: - arg_class = image_class_from_string(optarg); + OPTION_LONG("class", "CLASS", + "Select image class (machine, sysext, confext, portable)"): + arg_class = image_class_from_string(arg); if (arg_class < 0) - return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); - + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg); break; - case ARG_KEEP_DOWNLOAD: - r = parse_boolean(optarg); + OPTION_LONG("keep-download", "BOOL", + "Keep a pristine copy of the downloaded file around"): + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --keep-download= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --keep-download= argument: %s", arg); SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, r); auto_keep_download = false; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Operate in per-system mode"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Operate in per-user mode"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } /* Make sure offset+size is still in the valid range if both set */ @@ -630,6 +577,7 @@ static int parse_argv(int argc, char *argv[]) { if (arg_runtime_scope == RUNTIME_SCOPE_USER) arg_import_flags |= IMPORT_FOREIGN_UID; + *ret_args = option_parser_get_args(&state); return 1; } @@ -658,18 +606,6 @@ static void parse_env(void) { log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_SYNC: %m"); } -static int pull_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "tar", 2, 3, 0, pull_tar }, - { "raw", 2, 3, 0, pull_raw }, - { "oci", 2, 3, 0, pull_oci }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; @@ -678,13 +614,14 @@ static int run(int argc, char *argv[]) { parse_env(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; (void) ignore_signals(SIGPIPE); - return pull_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/import/qcow2-util.c b/src/import/qcow2-util.c index 77298bcbe2979..2b219b04a1f63 100644 --- a/src/import/qcow2-util.c +++ b/src/import/qcow2-util.c @@ -1,8 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include +#include #include "alloc-util.h" +#include "compress.h" #include "copy.h" #include "qcow2-util.h" #include "sparse-endian.h" @@ -97,8 +98,6 @@ static int decompress_cluster( void *buffer2) { _cleanup_free_ void *large_buffer = NULL; - z_stream s = {}; - uint64_t sz; ssize_t l; int r; @@ -119,20 +118,9 @@ static int decompress_cluster( if ((uint64_t) l != compressed_size) return -EIO; - s.next_in = buffer1; - s.avail_in = compressed_size; - s.next_out = buffer2; - s.avail_out = cluster_size; - - r = inflateInit2(&s, -12); - if (r != Z_OK) - return -EIO; - - r = inflate(&s, Z_FINISH); - sz = (uint8_t*) s.next_out - (uint8_t*) buffer2; - inflateEnd(&s); - if (r != Z_STREAM_END || sz != cluster_size) - return -EIO; + r = decompress_zlib_raw(buffer1, compressed_size, buffer2, cluster_size, /* wbits= */ -12); + if (r < 0) + return r; l = pwrite(dfd, buffer2, cluster_size, doffset); if (l < 0) @@ -150,9 +138,11 @@ static int normalize_offset( bool *compressed, uint64_t *compressed_size) { - uint64_t q; + assert(ret); + POINTER_MAY_BE_NULL(compressed); + POINTER_MAY_BE_NULL(compressed_size); - q = be64toh(p); + uint64_t q = be64toh(p); if (q & QCOW2_COMPRESSED) { uint64_t sz, csize_shift, csize_mask; diff --git a/src/import/test-tar-extract.c b/src/import/test-tar-extract.c deleted file mode 100644 index f7fa06f044f15..0000000000000 --- a/src/import/test-tar-extract.c +++ /dev/null @@ -1,37 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "fd-util.h" -#include "libarchive-util.h" -#include "main-func.h" -#include "tar-util.h" -#include "tests.h" - -static int run(int argc, char **argv) { - int r; - - test_setup_logging(LOG_DEBUG); - - if (argc != 3) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Need two arguments exactly: "); - - r = dlopen_libarchive(); - if (r < 0) - return r; - - _cleanup_close_ int input_fd = open(argv[1], O_RDONLY | O_CLOEXEC); - if (input_fd < 0) - return log_error_errno(input_fd, "Cannot open %s: %m", argv[1]); - - _cleanup_close_ int output_fd = open(argv[2], O_DIRECTORY | O_CLOEXEC); - if (output_fd < 0) - return log_error_errno(output_fd, "Cannot open %s: %m", argv[2]); - - r = tar_x(input_fd, output_fd, /* flags= */ TAR_SELINUX); - if (r < 0) - return log_error_errno(r, "tar_x failed: %m"); - - return 0; -} - -DEFINE_MAIN_FUNCTION(run); diff --git a/src/import/test-tar.c b/src/import/test-tar.c new file mode 100644 index 0000000000000..b7cfed9bf3c16 --- /dev/null +++ b/src/import/test-tar.c @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "errno-util.h" +#include "fd-util.h" +#include "libarchive-util.h" +#include "main-func.h" +#include "tar-util.h" +#include "tests.h" + +static int run(int argc, char **argv) { + int r; + + test_setup_logging(LOG_DEBUG); + + if (argc != 4) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Need three arguments exactly: -c | -x "); + + bool create; + if (streq(argv[1], "-c")) + create = true; + else if (streq(argv[1], "-x")) + create = false; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown operation '%s'.", argv[1]); + + r = dlopen_libarchive(LOG_DEBUG); + if (r < 0) + return r; + + int flags = create ? O_CREAT | O_WRONLY | O_TRUNC : O_RDONLY; + _cleanup_close_ int fd1 = RET_NERRNO(open(argv[2], flags | O_CLOEXEC, 0666)); + if (fd1 < 0) + return log_error_errno(fd1, "Cannot open %s: %m", argv[2]); + + if (!create) { + r = RET_NERRNO(mkdir(argv[3], 0777)); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Failed to mkdir %s: %m", argv[3]); + } + + _cleanup_close_ int fd2 = RET_NERRNO(open(argv[3], O_DIRECTORY | O_CLOEXEC)); + if (fd2 < 0) + return log_error_errno(fd2, "Cannot open %s: %m", argv[3]); + + if (create) + r = tar_c(fd2, fd1, argv[2], /* flags= */ TAR_SELINUX); + else + r = tar_x(fd1, fd2, /* flags= */ TAR_SELINUX); + if (r < 0) + return log_error_errno(r, "tar %s failed: %m", argv[1]); + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/include/override/sys/generate-syscall.py b/src/include/override/sys/generate-syscall.py index 6f449f9dc1330..1c90ad0e38402 100755 --- a/src/include/override/sys/generate-syscall.py +++ b/src/include/override/sys/generate-syscall.py @@ -44,7 +44,7 @@ def parse_syscall_tables(filenames): #include_next /* IWYU pragma: export */ -#ifdef ARCH_MIPS +#ifdef __mips__ #include #endif diff --git a/src/include/override/sys/kexec.h b/src/include/override/sys/kexec.h new file mode 100644 index 0000000000000..4e256bdb6ad38 --- /dev/null +++ b/src/include/override/sys/kexec.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include /* IWYU pragma: export */ +#include + +/* Supported since kernel v3.17 (cb1052581e2bddd6096544f3f944f4e7fdad4c4f). + * Not available on all architectures. */ +#if HAVE_KEXEC_FILE_LOAD || defined __NR_kexec_file_load +# if !HAVE_KEXEC_FILE_LOAD +int missing_kexec_file_load(int kernel_fd, int initrd_fd, unsigned long cmdline_len, const char *cmdline, unsigned long flags); +# define kexec_file_load missing_kexec_file_load +# endif +# define HAVE_KEXEC_FILE_LOAD_SYSCALL 1 +#else +# define HAVE_KEXEC_FILE_LOAD_SYSCALL 0 +#endif diff --git a/src/include/override/sys/syscall.h b/src/include/override/sys/syscall.h index da2f780bed39c..0233f254b421c 100644 --- a/src/include/override/sys/syscall.h +++ b/src/include/override/sys/syscall.h @@ -11,7 +11,7 @@ #include_next /* IWYU pragma: export */ -#ifdef ARCH_MIPS +#ifdef __mips__ #include #endif diff --git a/src/include/uapi/linux/kexec.h b/src/include/uapi/linux/kexec.h new file mode 100644 index 0000000000000..e26e2110ce5d8 --- /dev/null +++ b/src/include/uapi/linux/kexec.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef LINUX_KEXEC_H +#define LINUX_KEXEC_H + +/* kexec system call - It loads the new kernel to boot into. + * kexec does not sync, or unmount filesystems so if you need + * that to happen you need to do that yourself. + */ + +#include + +/* kexec flags for different usage scenarios */ +#define KEXEC_ON_CRASH 0x00000001 +#define KEXEC_PRESERVE_CONTEXT 0x00000002 +#define KEXEC_UPDATE_ELFCOREHDR 0x00000004 +#define KEXEC_CRASH_HOTPLUG_SUPPORT 0x00000008 +#define KEXEC_ARCH_MASK 0xffff0000 + +/* + * Kexec file load interface flags. + * KEXEC_FILE_UNLOAD : Unload already loaded kexec/kdump image. + * KEXEC_FILE_ON_CRASH : Load/unload operation belongs to kdump image. + * KEXEC_FILE_NO_INITRAMFS : No initramfs is being loaded. Ignore the initrd + * fd field. + * KEXEC_FILE_FORCE_DTB : Force carrying over the current boot's DTB to the new + * kernel on x86. This is already the default behavior on + * some other architectures, like ARM64 and PowerPC. + */ +#define KEXEC_FILE_UNLOAD 0x00000001 +#define KEXEC_FILE_ON_CRASH 0x00000002 +#define KEXEC_FILE_NO_INITRAMFS 0x00000004 +#define KEXEC_FILE_DEBUG 0x00000008 +#define KEXEC_FILE_NO_CMA 0x00000010 +#define KEXEC_FILE_FORCE_DTB 0x00000020 + +/* These values match the ELF architecture values. + * Unless there is a good reason that should continue to be the case. + */ +#define KEXEC_ARCH_DEFAULT ( 0 << 16) +#define KEXEC_ARCH_386 ( 3 << 16) +#define KEXEC_ARCH_68K ( 4 << 16) +#define KEXEC_ARCH_PARISC (15 << 16) +#define KEXEC_ARCH_X86_64 (62 << 16) +#define KEXEC_ARCH_PPC (20 << 16) +#define KEXEC_ARCH_PPC64 (21 << 16) +#define KEXEC_ARCH_IA_64 (50 << 16) +#define KEXEC_ARCH_ARM (40 << 16) +#define KEXEC_ARCH_S390 (22 << 16) +#define KEXEC_ARCH_SH (42 << 16) +#define KEXEC_ARCH_MIPS_LE (10 << 16) +#define KEXEC_ARCH_MIPS ( 8 << 16) +#define KEXEC_ARCH_AARCH64 (183 << 16) +#define KEXEC_ARCH_RISCV (243 << 16) +#define KEXEC_ARCH_LOONGARCH (258 << 16) + +/* The artificial cap on the number of segments passed to kexec_load. */ +#define KEXEC_SEGMENT_MAX 16 + +/* + * This structure is used to hold the arguments that are used when + * loading kernel binaries. + */ +struct kexec_segment { + const void *buf; + __kernel_size_t bufsz; + const void *mem; + __kernel_size_t memsz; +}; + + +#endif /* LINUX_KEXEC_H */ diff --git a/src/integritysetup/integrity-util.c b/src/integritysetup/integrity-util.c index 7e52f5c0dcba4..88236f6323638 100644 --- a/src/integritysetup/integrity-util.c +++ b/src/integritysetup/integrity-util.c @@ -6,15 +6,24 @@ #include "integrity-util.h" #include "log.h" #include "percent-util.h" +#include "string-table.h" #include "string-util.h" -#include "strv.h" #include "time-util.h" -static int supported_integrity_algorithm(char *user_supplied) { - if (!STR_IN_SET(user_supplied, "crc32", "crc32c", "xxhash64", "sha1", "sha256", "hmac-sha256", "hmac-sha512", "phmac-sha256", "phmac-sha512")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported integrity algorithm (%s)", user_supplied); - return 0; -} +/* Integrity algorithm names used by integritysetup/integritytab */ +static const char* const integrity_algorithm_table[_INTEGRITY_ALGORITHM_MAX] = { + [INTEGRITY_ALGORITHM_CRC32] = "crc32", + [INTEGRITY_ALGORITHM_CRC32C] = "crc32c", + [INTEGRITY_ALGORITHM_XXHASH64] = "xxhash64", + [INTEGRITY_ALGORITHM_SHA1] = "sha1", + [INTEGRITY_ALGORITHM_SHA256] = "sha256", + [INTEGRITY_ALGORITHM_HMAC_SHA256] = "hmac-sha256", + [INTEGRITY_ALGORITHM_HMAC_SHA512] = "hmac-sha512", + [INTEGRITY_ALGORITHM_PHMAC_SHA256] = "phmac-sha256", + [INTEGRITY_ALGORITHM_PHMAC_SHA512] = "phmac-sha512", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(integrity_algorithm, IntegrityAlgorithm); int parse_integrity_options( const char *options, @@ -22,7 +31,7 @@ int parse_integrity_options( int *ret_percent, usec_t *ret_commit_time, char **ret_data_device, - char **ret_integrity_alg) { + IntegrityAlgorithm *ret_integrity_alg) { int r; for (;;) { @@ -73,14 +82,12 @@ int parse_integrity_options( return log_oom(); } } else if ((val = startswith(word, "integrity-algorithm="))) { - r = supported_integrity_algorithm(val); - if (r < 0) - return r; - if (ret_integrity_alg) { - r = free_and_strdup(ret_integrity_alg, val); - if (r < 0) - return log_oom(); - } + IntegrityAlgorithm a = integrity_algorithm_from_string(val); + if (a < 0) + return log_error_errno(a, "Unsupported integrity algorithm (%s)", val); + + if (ret_integrity_alg) + *ret_integrity_alg = a; } else log_warning("Encountered unknown option '%s', ignoring.", word); } diff --git a/src/integritysetup/integrity-util.h b/src/integritysetup/integrity-util.h index 5cc7e42de9270..b1fd98c849b6b 100644 --- a/src/integritysetup/integrity-util.h +++ b/src/integritysetup/integrity-util.h @@ -3,16 +3,26 @@ #include "shared-forward.h" +typedef enum { + INTEGRITY_ALGORITHM_CRC32, + INTEGRITY_ALGORITHM_CRC32C, + INTEGRITY_ALGORITHM_XXHASH64, + INTEGRITY_ALGORITHM_SHA1, + INTEGRITY_ALGORITHM_SHA256, + INTEGRITY_ALGORITHM_HMAC_SHA256, + INTEGRITY_ALGORITHM_HMAC_SHA512, + INTEGRITY_ALGORITHM_PHMAC_SHA256, + INTEGRITY_ALGORITHM_PHMAC_SHA512, + _INTEGRITY_ALGORITHM_MAX, + _INTEGRITY_ALGORITHM_INVALID = -EINVAL, +} IntegrityAlgorithm; + int parse_integrity_options( const char *options, uint32_t *ret_activate_flags, int *ret_percent, usec_t *ret_commit_time, char **ret_data_device, - char **ret_integrity_alg); + IntegrityAlgorithm *ret_integrity_alg); -#define DM_HMAC_256 "hmac(sha256)" -#define DM_HMAC_512 "hmac(sha512)" -#define DM_PHMAC_256 "phmac(sha256)" -#define DM_PHMAC_512 "phmac(sha512)" #define DM_MAX_KEY_SIZE 4096 /* Maximum size of key allowed for dm-integrity */ diff --git a/src/integritysetup/integritysetup.c b/src/integritysetup/integritysetup.c index 5eed1b5665d5e..9a373e124be3e 100644 --- a/src/integritysetup/integritysetup.c +++ b/src/integritysetup/integritysetup.c @@ -12,6 +12,7 @@ #include "main-func.h" #include "path-util.h" #include "pretty-print.h" +#include "string-table.h" #include "string-util.h" #include "time-util.h" #include "verbs.h" @@ -20,10 +21,24 @@ static uint32_t arg_activate_flags; static int arg_percent; static usec_t arg_commit_time; static char *arg_existing_data_device; -static char *arg_integrity_algorithm; +static IntegrityAlgorithm arg_integrity_algorithm = _INTEGRITY_ALGORITHM_INVALID; STATIC_DESTRUCTOR_REGISTER(arg_existing_data_device, freep); -STATIC_DESTRUCTOR_REGISTER(arg_integrity_algorithm, freep); + +/* Integrity algorithm names used by dm-integrity */ +static const char* const dm_integrity_algorithm_table[_INTEGRITY_ALGORITHM_MAX] = { + [INTEGRITY_ALGORITHM_CRC32] = "crc32", + [INTEGRITY_ALGORITHM_CRC32C] = "crc32c", + [INTEGRITY_ALGORITHM_XXHASH64] = "xxhash64", + [INTEGRITY_ALGORITHM_SHA1] = "sha1", + [INTEGRITY_ALGORITHM_SHA256] = "sha256", + [INTEGRITY_ALGORITHM_HMAC_SHA256] = "hmac(sha256)", + [INTEGRITY_ALGORITHM_HMAC_SHA512] = "hmac(sha512)", + [INTEGRITY_ALGORITHM_PHMAC_SHA256] = "phmac(sha256)", + [INTEGRITY_ALGORITHM_PHMAC_SHA512] = "phmac(sha512)", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(dm_integrity_algorithm, IntegrityAlgorithm); static int help(void) { _cleanup_free_ char *link = NULL; @@ -72,24 +87,14 @@ static int load_key_file( } static const char *integrity_algorithm_select(const void *key_file_buf) { - /* To keep a bit of sanity for end users, the subset of integrity - * algorithms we support will match what is used in integritysetup */ - if (arg_integrity_algorithm) { - if (streq(arg_integrity_algorithm, "hmac-sha256")) - return DM_HMAC_256; - if (streq(arg_integrity_algorithm, "hmac-sha512")) - return DM_HMAC_512; - if (streq(arg_integrity_algorithm, "phmac-sha256")) - return DM_PHMAC_256; - if (streq(arg_integrity_algorithm, "phmac-sha512")) - return DM_PHMAC_512; - return arg_integrity_algorithm; - } else if (key_file_buf) - return DM_HMAC_256; - return "crc32c"; + IntegrityAlgorithm a = arg_integrity_algorithm >= 0 + ? arg_integrity_algorithm + : (key_file_buf ? INTEGRITY_ALGORITHM_HMAC_SHA256 : INTEGRITY_ALGORITHM_CRC32C); + + return dm_integrity_algorithm_to_string(a); } -static int verb_attach(int argc, char *argv[], void *userdata) { +static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; crypt_status_info status; _cleanup_(erase_and_freep) void *key_buf = NULL; @@ -121,19 +126,19 @@ static int verb_attach(int argc, char *argv[], void *userdata) { return r; } - r = crypt_init(&cd, device); + r = sym_crypt_init(&cd, device); if (r < 0) return log_error_errno(r, "Failed to open integrity device %s: %m", device); cryptsetup_enable_logging(cd); - status = crypt_status(cd, volume); + status = sym_crypt_status(cd, volume); if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { log_info("Volume %s already active.", volume); return 0; } - r = crypt_load(cd, + r = sym_crypt_load(cd, CRYPT_INTEGRITY, &(struct crypt_params_integrity) { .journal_watermark = arg_percent, @@ -144,19 +149,19 @@ static int verb_attach(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Failed to load integrity superblock: %m"); if (!isempty(arg_existing_data_device)) { - r = crypt_set_data_device(cd, arg_existing_data_device); + r = sym_crypt_set_data_device(cd, arg_existing_data_device); if (r < 0) return log_error_errno(r, "Failed to add separate data device: %m"); } - r = crypt_activate_by_volume_key(cd, volume, key_buf, key_buf_size, arg_activate_flags); + r = sym_crypt_activate_by_volume_key(cd, volume, key_buf, key_buf_size, arg_activate_flags); if (r < 0) return log_error_errno(r, "Failed to set up integrity device: %m"); return 0; } -static int verb_detach(int argc, char *argv[], void *userdata) { +static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; int r; @@ -167,7 +172,7 @@ static int verb_detach(int argc, char *argv[], void *userdata) { if (!filename_is_valid(volume)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - r = crypt_init_by_name(&cd, volume); + r = sym_crypt_init_by_name(&cd, volume); if (r == -ENODEV) { log_info("Volume %s already inactive.", volume); return 0; @@ -177,7 +182,7 @@ static int verb_detach(int argc, char *argv[], void *userdata) { cryptsetup_enable_logging(cd); - r = crypt_deactivate(cd, volume); + r = sym_crypt_deactivate(cd, volume); if (r < 0) return log_error_errno(r, "Failed to deactivate: %m"); @@ -185,12 +190,16 @@ static int verb_detach(int argc, char *argv[], void *userdata) { } static int run(int argc, char *argv[]) { + int r; + if (argv_looks_like_help(argc, argv)) return help(); log_setup(); - cryptsetup_enable_logging(NULL); + r = dlopen_cryptsetup(LOG_ERR); + if (r < 0) + return r; umask(0022); diff --git a/src/integritysetup/meson.build b/src/integritysetup/meson.build index dd2eb60cf6973..4f3601e681938 100644 --- a/src/integritysetup/meson.build +++ b/src/integritysetup/meson.build @@ -9,7 +9,7 @@ executables += [ 'name' : 'systemd-integritysetup', 'sources' : files('integritysetup.c'), 'extract' : files('integrity-util.c'), - 'dependencies' : libcryptsetup, + 'dependencies' : libcryptsetup_cflags, }, generator_template + { 'name' : 'systemd-integritysetup-generator', diff --git a/src/journal-remote/journal-compression-util.c b/src/journal-remote/journal-compression-util.c index 00d39358956cf..8e415878b9580 100644 --- a/src/journal-remote/journal-compression-util.c +++ b/src/journal-remote/journal-compression-util.c @@ -130,7 +130,7 @@ int config_parse_compression( } } - Compression c = compression_lowercase_from_string(word); + Compression c = compression_from_string_harder(word); if (c <= 0 || !compression_supported(c)) { log_syntax(unit, LOG_WARNING, filename, line, c, "Compression algorithm '%s' is not supported on the system, ignoring.", word); diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index ee190827615e2..7052fd000bd5e 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -18,6 +17,7 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "glob-util.h" #include "hostname-setup.h" #include "hostname-util.h" @@ -28,6 +28,7 @@ #include "main-func.h" #include "memory-util.h" #include "microhttpd-util.h" +#include "options.h" #include "os-util.h" #include "output-mode.h" #include "parse-util.h" @@ -104,8 +105,10 @@ static void request_meta_free( struct MHD_Connection *connection, void **connection_cls, enum MHD_RequestTerminationCode toe) { + RequestMeta *m; - RequestMeta *m = *connection_cls; + assert(connection_cls); + m = *connection_cls; if (!m) return; @@ -309,7 +312,7 @@ static int request_parse_accept( assert(m); assert(connection); - header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept"); + header = sym_MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept"); if (!header) return 0; @@ -457,7 +460,7 @@ static int request_parse_range( assert(m); assert(connection); - range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range"); + range = sym_MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range"); if (!range) return 0; @@ -564,7 +567,7 @@ static int request_parse_arguments( assert(connection); m->argument_parse_error = 0; - MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m); + sym_MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m); return m->argument_parse_error; } @@ -614,14 +617,14 @@ static int request_handler_entries( if (r < 0) return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal."); - response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL); + response = sym_MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL); if (!response) return respond_oom(connection); - if (MHD_add_response_header(response, "Content-Type", mime_types[m->mode]) == MHD_NO) + if (sym_MHD_add_response_header(response, "Content-Type", mime_types[m->mode]) == MHD_NO) return respond_oom(connection); - return MHD_queue_response(connection, MHD_HTTP_OK, response); + return sym_MHD_queue_response(connection, MHD_HTTP_OK, response); } static int output_field(FILE *f, OutputMode m, const char *d, size_t l) { @@ -742,14 +745,14 @@ static int request_handler_fields( if (r < 0) return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields."); - response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL); + response = sym_MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL); if (!response) return respond_oom(connection); - if (MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]) == MHD_NO) + if (sym_MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]) == MHD_NO) return respond_oom(connection); - return MHD_queue_response(connection, MHD_HTTP_OK, response); + return sym_MHD_queue_response(connection, MHD_HTTP_OK, response); } static int request_handler_redirect( @@ -765,16 +768,16 @@ static int request_handler_redirect( if (asprintf(&page, "Please continue to the journal browser.", target) < 0) return respond_oom(connection); - response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE); + response = sym_MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE); if (!response) return respond_oom(connection); TAKE_PTR(page); - if (MHD_add_response_header(response, "Content-Type", "text/html") == MHD_NO || - MHD_add_response_header(response, "Location", target) == MHD_NO) + if (sym_MHD_add_response_header(response, "Content-Type", "text/html") == MHD_NO || + sym_MHD_add_response_header(response, "Location", target) == MHD_NO) return respond_oom(connection); - return MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response); + return sym_MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response); } static int request_handler_file( @@ -797,15 +800,15 @@ static int request_handler_file( if (fstat(fd, &st) < 0) return mhd_respondf(connection, errno, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m"); - response = MHD_create_response_from_fd_at_offset64(st.st_size, fd, 0); + response = sym_MHD_create_response_from_fd_at_offset64(st.st_size, fd, 0); if (!response) return respond_oom(connection); TAKE_FD(fd); - if (MHD_add_response_header(response, "Content-Type", mime_type) == MHD_NO) + if (sym_MHD_add_response_header(response, "Content-Type", mime_type) == MHD_NO) return respond_oom(connection); - return MHD_queue_response(connection, MHD_HTTP_OK, response); + return sym_MHD_queue_response(connection, MHD_HTTP_OK, response); } static int get_virtualization(char **v) { @@ -813,6 +816,8 @@ static int get_virtualization(char **v) { char *b = NULL; int r; + assert(v); + r = sd_bus_default_system(&bus); if (r < 0) return r; @@ -895,15 +900,15 @@ static int request_handler_machine( if (r < 0) return respond_oom(connection); - response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE); + response = sym_MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE); if (!response) return respond_oom(connection); TAKE_PTR(json); - if (MHD_add_response_header(response, "Content-Type", "application/json") == MHD_NO) + if (sym_MHD_add_response_header(response, "Content-Type", "application/json") == MHD_NO) return respond_oom(connection); - return MHD_queue_response(connection, MHD_HTTP_OK, response); + return sym_MHD_queue_response(connection, MHD_HTTP_OK, response); } static int output_boot(FILE *f, LogId boot, int boot_display_index) { @@ -1022,14 +1027,14 @@ static int request_handler_boots( if (r < 0) return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to seek in journal: %m"); - response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_boots, m, NULL); + response = sym_MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_boots, m, NULL); if (!response) return respond_oom(connection); - if (MHD_add_response_header(response, "Content-Type", "application/json-seq") == MHD_NO) + if (sym_MHD_add_response_header(response, "Content-Type", "application/json-seq") == MHD_NO) return respond_oom(connection); - return MHD_queue_response(connection, MHD_HTTP_OK, response); + return sym_MHD_queue_response(connection, MHD_HTTP_OK, response); } static mhd_result request_handler( @@ -1086,92 +1091,52 @@ static mhd_result request_handler( static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-journal-gatewayd.service", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] ...\n\n" - "HTTP server for journal events.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --cert=CERT.PEM Server certificate in PEM format\n" - " --key=KEY.PEM Server key in PEM format\n" - " --trust=CERT.PEM Certificate authority certificate in PEM format\n" - " --system Serve system journal\n" - " --user Serve the user journal for the current user\n" - " -m --merge Serve all available journals\n" - " -D --directory=PATH Serve journal files in directory\n" - " --file=PATH Serve this journal file\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + "HTTP server for journal events.\n\n", + program_invocation_short_name); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_KEY, - ARG_CERT, - ARG_TRUST, - ARG_USER, - ARG_SYSTEM, - ARG_MERGE, - ARG_FILE, - }; - - int r, c; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "key", required_argument, NULL, ARG_KEY }, - { "cert", required_argument, NULL, ARG_CERT }, - { "trust", required_argument, NULL, ARG_TRUST }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "merge", no_argument, NULL, 'm' }, - { "directory", required_argument, NULL, 'D' }, - { "file", required_argument, NULL, ARG_FILE }, - {} - }; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hD:", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_KEY: - if (arg_key_pem) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Key file specified twice"); - r = read_full_file_full( - AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX, - READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, - NULL, - &arg_key_pem, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read key file: %m"); - assert(arg_key_pem); - break; - - case ARG_CERT: + OPTION_LONG("cert", "CERT.PEM", "Server certificate in PEM format"): if (arg_cert_pem) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Certificate file specified twice"); r = read_full_file_full( - AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX, + AT_FDCWD, arg, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_CONNECT_SOCKET, NULL, &arg_cert_pem, NULL); @@ -1180,13 +1145,27 @@ static int parse_argv(int argc, char *argv[]) { assert(arg_cert_pem); break; - case ARG_TRUST: + OPTION_LONG("key", "KEY.PEM", "Server key in PEM format"): + if (arg_key_pem) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Key file specified twice"); + r = read_full_file_full( + AT_FDCWD, arg, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, + NULL, + &arg_key_pem, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read key file: %m"); + assert(arg_key_pem); + break; + + OPTION_LONG("trust", "CERT.PEM", "Certificate authority certificate in PEM format"): #if HAVE_GNUTLS if (arg_trust_pem) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "CA certificate file specified twice"); r = read_full_file_full( - AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX, + AT_FDCWD, arg, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_CONNECT_SOCKET, NULL, &arg_trust_pem, NULL); @@ -1199,38 +1178,32 @@ static int parse_argv(int argc, char *argv[]) { "Option --trust= is not available."); #endif - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Serve system journal"): arg_journal_type |= SD_JOURNAL_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Serve the user journal for the current user"): arg_journal_type |= SD_JOURNAL_CURRENT_USER; break; - case 'm': + OPTION('m', "merge", NULL, "Serve all available journals"): arg_merge = true; break; - case 'D': - r = free_and_strdup_warn(&arg_directory, optarg); + OPTION('D', "directory", "PATH", "Serve journal files in directory"): + r = free_and_strdup_warn(&arg_directory, arg); if (r < 0) return r; break; - case ARG_FILE: - r = glob_extend(&arg_file, optarg, GLOB_NOCHECK); + OPTION_LONG("file", "PATH", "Serve this journal file"): + r = glob_extend(&arg_file, arg, GLOB_NOCHECK); if (r < 0) return log_error_errno(r, "Failed to add paths: %m"); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program does not take arguments."); @@ -1286,6 +1259,10 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + r = dlopen_microhttpd(LOG_ERR); + if (r < 0) + return r; + journal_browse_prepare(); assert_se(sigaction(SIGTERM, &sigterm, NULL) >= 0); @@ -1319,11 +1296,16 @@ static int run(int argc, char *argv[]) { { MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem }; } - d = MHD_start_daemon(flags, 19531, - NULL, NULL, - request_handler, NULL, - MHD_OPTION_ARRAY, opts, - MHD_OPTION_END); + d = sym_MHD_start_daemon( + flags, + /* port= */ 19531, + /* acp= */ NULL, + /* acp_cls= */ NULL, + request_handler, + /* dh_cls= */ NULL, + MHD_OPTION_ARRAY, + opts, + MHD_OPTION_END); if (!d) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to start daemon!"); diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index 35ab12578b2a0..1fbcc27815210 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-daemon.h" @@ -12,6 +11,7 @@ #include "daemon-util.h" #include "extract-word.h" #include "fd-util.h" +#include "format-table.h" #include "format-util.h" #include "fileio.h" #include "hashmap.h" @@ -21,9 +21,11 @@ #include "logs-show.h" #include "main-func.h" #include "microhttpd-util.h" +#include "options.h" #include "parse-argument.h" #include "parse-helpers.h" #include "parse-util.h" +#include "path-util.h" #include "pretty-print.h" #include "process-util.h" #include "socket-netlink.h" @@ -106,7 +108,7 @@ static MHDDaemonWrapper* MHDDaemonWrapper_free(MHDDaemonWrapper *d) { d->timer_event = sd_event_source_unref(d->timer_event); if (d->daemon) - MHD_stop_daemon(d->daemon); + sym_MHD_stop_daemon(d->daemon); return mfree(d); } @@ -209,7 +211,7 @@ static int build_accept_encoding(char **ret) { const CompressionConfig *cc; ORDERED_HASHMAP_FOREACH(cc, arg_compression) { - const char *c = compression_lowercase_to_string(cc->algorithm); + const char *c = compression_to_string(cc->algorithm); if (strextendf_with_separator(&buf, ",", "%s;q=%.1f", c, q) < 0) return -ENOMEM; q -= step; @@ -286,7 +288,7 @@ static int process_http_upload( _cleanup_free_ char *buf = NULL; size_t buf_size; - r = decompress_blob(source->compression, upload_data, *upload_data_size, (void **) &buf, &buf_size, 0); + r = decompress_blob(source->compression, upload_data, *upload_data_size, (void **) &buf, &buf_size, DATA_SIZE_MAX); if (r < 0) return mhd_respondf(connection, r, MHD_HTTP_BAD_REQUEST, "Decompression of received blob failed."); @@ -359,9 +361,9 @@ static mhd_result request_handler( if (*connection_cls) { RemoteSource *source = *connection_cls; - header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Encoding"); + header = sym_MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Encoding"); if (header) { - Compression c = compression_lowercase_from_string(header); + Compression c = compression_from_string_harder(header); if (c <= 0 || !compression_supported(c)) return mhd_respondf(connection, 0, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE, "Unsupported Content-Encoding type: %s", header); @@ -380,12 +382,12 @@ static mhd_result request_handler( if (!streq(url, "/upload")) return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found."); - header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Type"); + header = sym_MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Type"); if (!header || !streq(header, "application/vnd.fdo.journal")) return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE, "Content-Type: application/vnd.fdo.journal is required."); - header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Transfer-Encoding"); + header = sym_MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Transfer-Encoding"); if (header) { if (!strcaseeq(header, "chunked")) return mhd_respondf(connection, 0, MHD_HTTP_BAD_REQUEST, @@ -394,7 +396,7 @@ static mhd_result request_handler( chunked = true; } - header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Length"); + header = sym_MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Length"); if (header) { size_t len; @@ -418,8 +420,8 @@ static mhd_result request_handler( { const union MHD_ConnectionInfo *ci; - ci = MHD_get_connection_info(connection, - MHD_CONNECTION_INFO_CONNECTION_FD); + ci = sym_MHD_get_connection_info(connection, + MHD_CONNECTION_INFO_CONNECTION_FD); if (!ci) { log_error("MHD_get_connection_info failed: cannot get remote fd"); return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, @@ -462,6 +464,12 @@ static int setup_microhttpd_server(RemoteServer *s, const char *trust) { #if HAVE_MICROHTTPD + int r; + + r = dlopen_microhttpd(LOG_ERR); + if (r < 0) + return r; + struct MHD_OptionItem opts[] = { { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger}, { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free}, @@ -481,7 +489,7 @@ static int setup_microhttpd_server(RemoteServer *s, _cleanup_(MHDDaemonWrapper_freep) MHDDaemonWrapper *d = NULL; const union MHD_DaemonInfo *info; - int r, epoll_fd; + int epoll_fd; assert(fd >= 0); @@ -524,18 +532,23 @@ static int setup_microhttpd_server(RemoteServer *s, d->fd = (uint64_t) fd; - d->daemon = MHD_start_daemon(flags, 0, - NULL, NULL, - request_handler, NULL, - MHD_OPTION_ARRAY, opts, - MHD_OPTION_END); + d->daemon = sym_MHD_start_daemon( + flags, + /* port= */ 0, + /* acp= */ NULL, + /* acp_cls= */ NULL, + request_handler, + /* dh_cls= */ NULL, + MHD_OPTION_ARRAY, + opts, + MHD_OPTION_END); if (!d->daemon) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to start μhttp daemon"); log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)", key ? "HTTPS" : "HTTP", fd, d); - info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY); + info = sym_MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY); if (!info) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "μhttp returned NULL daemon info"); @@ -607,12 +620,12 @@ static int dispatch_http_event(sd_event_source *event, int r; MHD_UNSIGNED_LONG_LONG timeout = ULLONG_MAX; - r = MHD_run(d->daemon); + r = sym_MHD_run(d->daemon); if (r == MHD_NO) // FIXME: unregister daemon return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "MHD_run failed!"); - if (MHD_get_timeout(d->daemon, &timeout) == MHD_NO) + if (sym_MHD_get_timeout(d->daemon, &timeout) == MHD_NO) timeout = ULLONG_MAX; r = sd_event_source_set_time(d->timer_event, timeout); @@ -828,6 +841,22 @@ static int parse_config(void) { {} }; + const char *config_file = secure_getenv("SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE"); + if (config_file) { + if (isempty(config_file) || path_equal(config_file, "/dev/null")) + return 0; + + return config_parse( + /* unit= */ NULL, + config_file, + /* f= */ NULL, + "Remote\0", + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL, + /* ret_stat= */ NULL); + } + return config_parse_standard_file_with_dropins( "systemd/journal-remote.conf", "Remote\0", @@ -838,155 +867,120 @@ static int parse_config(void) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-journal-remote.service", "8", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] {FILE|-}...\n\n" - "Write external journal events to journal file(s).\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --url=URL Read events from systemd-journal-gatewayd at URL\n" - " --getter=COMMAND Read events from the output of COMMAND\n" - " --listen-raw=ADDR Listen for connections at ADDR\n" - " --listen-http=ADDR Listen for HTTP connections at ADDR\n" - " --listen-https=ADDR Listen for HTTPS connections at ADDR\n" - " -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n" - " --compress[=BOOL] Use compression in the output journal (default: yes)\n" - " --seal[=BOOL] Use event sealing (default: no)\n" - " --key=FILENAME SSL key in PEM format (default:\n" - " \"" PRIV_KEY_FILE "\")\n" - " --cert=FILENAME SSL certificate in PEM format (default:\n" - " \"" CERT_FILE "\")\n" - " --trust=FILENAME|all SSL CA certificate or disable checking (default:\n" - " \"" TRUST_FILE "\")\n" - " --gnutls-log=CATEGORY...\n" - " Specify a list of gnutls logging categories\n" - " --split-mode=none|host How many output files to create\n" - "\nNote: file descriptors from sd_listen_fds() will be consumed, too.\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] {FILE|-}...\n" + "\n%sWrite external journal events to journal file(s).%s\n" + "\n%sOptions:%s\n", program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nNote: file descriptors from sd_listen_fds() will be consumed, too.\n" + "\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_URL, - ARG_LISTEN_RAW, - ARG_LISTEN_HTTP, - ARG_LISTEN_HTTPS, - ARG_GETTER, - ARG_SPLIT_MODE, - ARG_COMPRESS, - ARG_SEAL, - ARG_KEY, - ARG_CERT, - ARG_TRUST, - ARG_GNUTLS_LOG, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "url", required_argument, NULL, ARG_URL }, - { "getter", required_argument, NULL, ARG_GETTER }, - { "listen-raw", required_argument, NULL, ARG_LISTEN_RAW }, - { "listen-http", required_argument, NULL, ARG_LISTEN_HTTP }, - { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS }, - { "output", required_argument, NULL, 'o' }, - { "split-mode", required_argument, NULL, ARG_SPLIT_MODE }, - { "compress", optional_argument, NULL, ARG_COMPRESS }, - { "seal", optional_argument, NULL, ARG_SEAL }, - { "key", required_argument, NULL, ARG_KEY }, - { "cert", required_argument, NULL, ARG_CERT }, - { "trust", required_argument, NULL, ARG_TRUST }, - { "gnutls-log", required_argument, NULL, ARG_GNUTLS_LOG }, - {} - }; - - int c, r; + int r; bool type_a, type_b; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_URL: - r = free_and_strdup_warn(&arg_url, optarg); + OPTION_LONG("url", "URL", "Read events from systemd-journal-gatewayd at URL"): + r = free_and_strdup_warn(&arg_url, arg); if (r < 0) return r; break; - case ARG_GETTER: - r = free_and_strdup_warn(&arg_getter, optarg); + OPTION_LONG("getter", "COMMAND", "Read events from the output of COMMAND"): + r = free_and_strdup_warn(&arg_getter, arg); if (r < 0) return r; break; - case ARG_LISTEN_RAW: - r = free_and_strdup_warn(&arg_listen_raw, optarg); + OPTION_LONG("listen-raw", "ADDR", "Listen for connections at ADDR"): + r = free_and_strdup_warn(&arg_listen_raw, arg); if (r < 0) return r; break; - case ARG_LISTEN_HTTP: + OPTION_LONG("listen-http", "ADDR", "Listen for HTTP connections at ADDR"): if (arg_listen_http || http_socket >= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot currently use --listen-http= more than once"); - r = negative_fd(optarg); + r = negative_fd(arg); if (r >= 0) http_socket = r; else { - r = free_and_strdup_warn(&arg_listen_http, optarg); + r = free_and_strdup_warn(&arg_listen_http, arg); if (r < 0) return r; } break; - case ARG_LISTEN_HTTPS: + OPTION_LONG("listen-https", "ADDR", "Listen for HTTPS connections at ADDR"): if (arg_listen_https || https_socket >= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot currently use --listen-https= more than once"); - r = negative_fd(optarg); + r = negative_fd(arg); if (r >= 0) https_socket = r; else { - r = free_and_strdup_warn(&arg_listen_https, optarg); + r = free_and_strdup_warn(&arg_listen_https, arg); if (r < 0) return r; } break; - case ARG_KEY: - r = free_and_strdup_warn(&arg_key, optarg); + OPTION_LONG("key", "FILENAME", "SSL key in PEM format (default: \"" PRIV_KEY_FILE "\")"): + r = free_and_strdup_warn(&arg_key, arg); if (r < 0) return r; break; - case ARG_CERT: - r = free_and_strdup_warn(&arg_cert, optarg); + OPTION_LONG("cert", "FILENAME", "SSL certificate in PEM format (default: \"" CERT_FILE "\")"): + r = free_and_strdup_warn(&arg_cert, arg); if (r < 0) return r; break; - case ARG_TRUST: + OPTION_LONG("trust", "FILENAME|all", + "SSL CA certificate or disable checking (default: \"" TRUST_FILE "\")"): #if HAVE_GNUTLS - r = free_and_strdup_warn(&arg_trust, optarg); + r = free_and_strdup_warn(&arg_trust, arg); if (r < 0) return r; #else @@ -994,33 +988,35 @@ static int parse_argv(int argc, char *argv[]) { #endif break; - case 'o': - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_output); + OPTION('o', "output", "FILE|DIR", "Write output to FILE or DIR/external-*.journal"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_output); if (r < 0) return r; break; - case ARG_SPLIT_MODE: - arg_split_mode = journal_write_split_mode_from_string(optarg); + OPTION_LONG("split-mode", "none|host", "How many output files to create"): + arg_split_mode = journal_write_split_mode_from_string(arg); if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID) - return log_error_errno(arg_split_mode, "Invalid split mode: %s", optarg); + return log_error_errno(arg_split_mode, "Invalid split mode: %s", arg); break; - case ARG_COMPRESS: - r = parse_boolean_argument("--compress", optarg, &arg_compress); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "compress", "BOOL", + "Use compression in the output journal (default: yes)"): + r = parse_boolean_argument("--compress", arg, &arg_compress); if (r < 0) return r; break; - case ARG_SEAL: - r = parse_boolean_argument("--seal", optarg, &arg_seal); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "seal", "BOOL", + "Use event sealing (default: no)"): + r = parse_boolean_argument("--seal", arg, &arg_seal); if (r < 0) return r; break; - case ARG_GNUTLS_LOG: + OPTION_LONG("gnutls-log", "CATEGORY,...", "Specify a list of gnutls logging categories"): #if HAVE_GNUTLS - for (const char *p = optarg;;) { + for (const char *p = arg;;) { _cleanup_free_ char *word = NULL; r = extract_first_word(&p, &word, ",", 0); @@ -1037,14 +1033,32 @@ static int parse_argv(int argc, char *argv[]) { #endif break; - case '?': - return -EINVAL; + OPTION_LONG("max-use", "BYTES", "Maximum disk space to use"): + r = parse_size(arg, 1024, &arg_max_use); + if (r < 0) + return log_error_errno(r, "Failed to parse --max-use= value: %s", arg); + break; - default: - assert_not_reached(); + OPTION_LONG("keep-free", "BYTES", "Minimum disk space to keep free"): + r = parse_size(arg, 1024, &arg_keep_free); + if (r < 0) + return log_error_errno(r, "Failed to parse --keep-free= value: %s", arg); + break; + + OPTION_LONG("max-file-size", "BYTES", "Maximum size of individual journal files"): + r = parse_size(arg, 1024, &arg_max_size); + if (r < 0) + return log_error_errno(r, "Failed to parse --max-file-size= value: %s", arg); + break; + + OPTION_LONG("max-files", "N", "Maximum number of journal files to keep"): + r = safe_atou64(arg, &arg_n_max_files); + if (r < 0) + return log_error_errno(r, "Failed to parse --max-files= value: %s", arg); + break; } - arg_files = strv_copy(strv_skip(argv, optind)); + arg_files = strv_copy(option_parser_get_args(&state)); if (!arg_files) return log_oom(); @@ -1103,6 +1117,8 @@ static int parse_argv(int argc, char *argv[]) { static int load_certificates(char **key, char **cert, char **trust) { int r; + assert(trust); + r = read_full_file_full( AT_FDCWD, arg_key ?: PRIV_KEY_FILE, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, diff --git a/src/journal-remote/journal-upload-journal.c b/src/journal-remote/journal-upload-journal.c index 054451aafc78c..66cc4114f40e4 100644 --- a/src/journal-remote/journal-upload-journal.c +++ b/src/journal-remote/journal-upload-journal.c @@ -354,7 +354,7 @@ static size_t journal_input_callback(void *buf, size_t size, size_t nmemb, void r = compress_blob(u->compression->algorithm, compression_buffer, filled, buf, size * nmemb, &compressed_size, u->compression->level); if (r < 0) { log_error_errno(r, "Failed to compress %zu bytes by %s with level %i: %m", - filled, compression_lowercase_to_string(u->compression->algorithm), u->compression->level); + filled, compression_to_string(u->compression->algorithm), u->compression->level); return CURL_READFUNC_ABORT; } diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index c6123146a5507..23bb48f687620 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -11,6 +11,7 @@ #include "alloc-util.h" #include "build.h" #include "conf-parser.h" +#include "curl-util.h" #include "daemon-util.h" #include "env-file.h" #include "extract-word.h" @@ -81,20 +82,6 @@ static void close_fd_input(Uploader *u); #define STATE_FILE "/var/lib/systemd/journal-upload/state" -#define easy_setopt(curl, opt, value, level, cmd) \ - do { \ - code = curl_easy_setopt(curl, opt, value); \ - if (code) { \ - log_full(level, \ - "curl_easy_setopt " #opt " failed: %s", \ - curl_easy_strerror(code)); \ - cmd; \ - } \ - } while (0) - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURL*, curl_easy_cleanup, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct curl_slist*, curl_slist_free_all, NULL); - static size_t output_callback(char *buf, size_t size, size_t nmemb, @@ -194,8 +181,6 @@ int start_upload(Uploader *u, size_t nmemb, void *userdata), void *data) { - CURLcode code; - assert(u); assert(input_callback); @@ -203,26 +188,26 @@ int start_upload(Uploader *u, _cleanup_(curl_slist_free_allp) struct curl_slist *h = NULL; struct curl_slist *l; - h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal"); + h = sym_curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal"); if (!h) return log_oom(); - l = curl_slist_append(h, "Transfer-Encoding: chunked"); + l = sym_curl_slist_append(h, "Transfer-Encoding: chunked"); if (!l) return log_oom(); h = l; - l = curl_slist_append(h, "Accept: text/plain"); + l = sym_curl_slist_append(h, "Accept: text/plain"); if (!l) return log_oom(); h = l; if (u->compression) { - _cleanup_free_ char *header = strjoin("Content-Encoding: ", compression_lowercase_to_string(u->compression->algorithm)); + _cleanup_free_ char *header = strjoin("Content-Encoding: ", compression_to_string(u->compression->algorithm)); if (!header) return log_oom(); - l = curl_slist_append(h, header); + l = sym_curl_slist_append(h, header); if (!l) return log_oom(); h = l; @@ -244,7 +229,7 @@ int start_upload(Uploader *u, if (!header) return log_oom(); - l = curl_slist_append(h, header); + l = sym_curl_slist_append(h, header); if (!l) return log_oom(); h = l; @@ -256,67 +241,69 @@ int start_upload(Uploader *u, if (!u->easy) { _cleanup_(curl_easy_cleanupp) CURL *curl = NULL; - curl = curl_easy_init(); + curl = sym_curl_easy_init(); if (!curl) return log_error_errno(SYNTHETIC_ERRNO(ENOSR), "Call to curl_easy_init failed."); /* If configured, set a timeout for the curl operation. */ - if (arg_network_timeout_usec != USEC_INFINITY) - easy_setopt(curl, CURLOPT_TIMEOUT, - (long) DIV_ROUND_UP(arg_network_timeout_usec, USEC_PER_SEC), - LOG_ERR, return -EXFULL); + if (arg_network_timeout_usec != USEC_INFINITY && + !easy_setopt(curl, LOG_ERR, CURLOPT_TIMEOUT, + (long) DIV_ROUND_UP(arg_network_timeout_usec, USEC_PER_SEC))) + return -EXFULL; /* tell it to POST to the URL */ - easy_setopt(curl, CURLOPT_POST, 1L, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_POST, 1L)) + return -EXFULL; - easy_setopt(curl, CURLOPT_ERRORBUFFER, u->error, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_ERRORBUFFER, u->error)) + return -EXFULL; /* set where to write to */ - easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_WRITEFUNCTION, output_callback)) + return -EXFULL; - easy_setopt(curl, CURLOPT_WRITEDATA, data, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_WRITEDATA, data)) + return -EXFULL; /* set where to read from */ - easy_setopt(curl, CURLOPT_READFUNCTION, input_callback, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_READFUNCTION, input_callback)) + return -EXFULL; - easy_setopt(curl, CURLOPT_READDATA, data, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_READDATA, data)) + return -EXFULL; /* use our special own mime type and chunked transfer */ - easy_setopt(curl, CURLOPT_HTTPHEADER, u->header, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_HTTPHEADER, u->header)) + return -EXFULL; if (DEBUG_LOGGING) /* enable verbose for easier tracing */ - easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, ); + (void) easy_setopt(curl, LOG_WARNING, CURLOPT_VERBOSE, 1L); - easy_setopt(curl, CURLOPT_USERAGENT, - "systemd-journal-upload " GIT_VERSION, - LOG_WARNING, ); + (void) easy_setopt(curl, LOG_WARNING, + CURLOPT_USERAGENT, "systemd-journal-upload " GIT_VERSION); if (!streq_ptr(arg_key, "-") && (arg_key || startswith(u->url, "https://"))) { - easy_setopt(curl, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE, - LOG_ERR, return -EXFULL); - easy_setopt(curl, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE)) + return -EXFULL; + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE)) + return -EXFULL; } - if (STRPTR_IN_SET(arg_trust, "-", "all")) - easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L, - LOG_ERR, return -EUCLEAN); - else if (arg_trust || startswith(u->url, "https://")) - easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE, - LOG_ERR, return -EXFULL); + if (STRPTR_IN_SET(arg_trust, "-", "all")) { + log_info("Server certificate verification disabled."); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSL_VERIFYPEER, 0L)) + return -EUCLEAN; + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSL_VERIFYHOST, 0L)) + return -EUCLEAN; + } else if (arg_trust || startswith(u->url, "https://")) { + if (!easy_setopt(curl, LOG_ERR, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE)) + return -EXFULL; + } - if (arg_key || arg_trust) - easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1, - LOG_WARNING, ); + if (startswith(u->url, "https://")) + (void) easy_setopt(curl, LOG_WARNING, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); u->easy = TAKE_PTR(curl); } else { @@ -327,11 +314,8 @@ int start_upload(Uploader *u, } /* upload to this place */ - code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url); - if (code) - return log_error_errno(SYNTHETIC_ERRNO(EXFULL), - "curl_easy_setopt CURLOPT_URL failed: %s", - curl_easy_strerror(code)); + if (!easy_setopt(u->easy, LOG_ERR, CURLOPT_URL, u->url)) + return -EXFULL; u->uploading = true; @@ -369,7 +353,7 @@ static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *user r = compress_blob(u->compression->algorithm, compression_buffer, n, buf, size * nmemb, &compressed_size, u->compression->level); if (r < 0) { log_error_errno(r, "Failed to compress %zd bytes by %s with level %i: %m", - n, compression_lowercase_to_string(u->compression->algorithm), u->compression->level); + n, compression_to_string(u->compression->algorithm), u->compression->level); return CURL_READFUNC_ABORT; } assert(compressed_size <= size * nmemb); @@ -501,8 +485,10 @@ static int setup_uploader(Uploader *u, const char *url, const char *state_file) static void destroy_uploader(Uploader *u) { assert(u); - curl_easy_cleanup(u->easy); - curl_slist_free_all(u->header); + if (sym_curl_easy_cleanup) + sym_curl_easy_cleanup(u->easy); + if (sym_curl_slist_free_all) + sym_curl_slist_free_all(u->header); free(u->answer); free(u->last_cursor); @@ -528,7 +514,7 @@ static int update_content_encoding_header(Uploader *u, const CompressionConfig * return 0; /* Already picked the algorithm. Let's shortcut. */ if (cc) { - _cleanup_free_ char *header = strjoin("Content-Encoding: ", compression_lowercase_to_string(cc->algorithm)); + _cleanup_free_ char *header = strjoin("Content-Encoding: ", compression_to_string(cc->algorithm)); if (!header) return log_oom(); @@ -543,7 +529,7 @@ static int update_content_encoding_header(Uploader *u, const CompressionConfig * /* If Content-Encoding header is not found, append new one. */ if (!found) { - struct curl_slist *l = curl_slist_append(u->header, header); + struct curl_slist *l = sym_curl_slist_append(u->header, header); if (!l) return log_oom(); u->header = l; @@ -559,20 +545,20 @@ static int update_content_encoding_header(Uploader *u, const CompressionConfig * else u->header = TAKE_PTR(l->next); - curl_slist_free_all(l); + sym_curl_slist_free_all(l); update_header = true; break; } - if (update_header) { - CURLcode code; - easy_setopt(u->easy, CURLOPT_HTTPHEADER, u->header, LOG_WARNING, return -EXFULL); - } + if (update_header && + !easy_setopt(u->easy, LOG_WARNING, CURLOPT_HTTPHEADER, u->header)) + return -EXFULL; u->compression = cc; if (cc) - log_debug("Using compression algorithm %s with compression level %i.", compression_lowercase_to_string(cc->algorithm), cc->level); + log_debug("Using compression algorithm %s with compression level %i.", + compression_to_string(cc->algorithm), cc->level); else log_debug("Disabled compression algorithm."); return 0; @@ -589,7 +575,7 @@ static int parse_accept_encoding_header(Uploader *u) { return update_content_encoding_header(u, NULL); struct curl_header *header; - CURLHcode hcode = curl_easy_header(u->easy, "Accept-Encoding", 0, CURLH_HEADER, -1, &header); + CURLHcode hcode = sym_curl_easy_header(u->easy, "Accept-Encoding", 0, CURLH_HEADER, -1, &header); if (hcode != CURLHE_OK) goto not_found; @@ -610,7 +596,7 @@ static int parse_accept_encoding_header(Uploader *u) { if (streq(word, "*")) return update_content_encoding_header(u, ordered_hashmap_first(arg_compression)); - Compression c = compression_lowercase_from_string(word); + Compression c = compression_from_string_harder(word); if (c <= 0 || !compression_supported(c)) continue; /* unsupported or invalid algorithm. */ @@ -638,22 +624,23 @@ static int perform_upload(Uploader *u) { assert(u); u->watchdog_timestamp = now(CLOCK_MONOTONIC); - code = curl_easy_perform(u->easy); + code = sym_curl_easy_perform(u->easy); if (code) { if (u->error[0]) - log_error("Upload to %s failed: %.*s", - u->url, (int) sizeof(u->error), u->error); + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Upload to %s failed: %.*s", + u->url, (int) sizeof(u->error), u->error); else - log_error("Upload to %s failed: %s", - u->url, curl_easy_strerror(code)); - return -EIO; + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Upload to %s failed: %s", + u->url, sym_curl_easy_strerror(code)); } - code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status); + code = sym_curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status); if (code) return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "Failed to retrieve response code: %s", - curl_easy_strerror(code)); + sym_curl_easy_strerror(code)); if (status >= 300) return log_error_errno(SYNTHETIC_ERRNO(EIO), @@ -937,6 +924,10 @@ static int run(int argc, char **argv) { if (r <= 0) return r; + r = dlopen_curl(LOG_DEBUG); + if (r < 0) + return r; + r = compression_configs_mangle(&arg_compression); if (r < 0) return r; diff --git a/src/journal-remote/meson.build b/src/journal-remote/meson.build index 51261d000d9e4..f6aa71349c24c 100644 --- a/src/journal-remote/meson.build +++ b/src/journal-remote/meson.build @@ -3,9 +3,6 @@ systemd_journal_gatewayd_sources = files( 'journal-gatewayd.c', ) -systemd_journal_gatewayd_extract_sources = files( - 'microhttpd-util.c', -) systemd_journal_remote_sources = files('journal-remote-main.c') systemd_journal_remote_extract_sources = files( @@ -24,7 +21,7 @@ systemd_journal_upload_extract_sources = files( ) common_deps = [ - libgnutls, + libgnutls_cflags, liblz4_cflags, libxz_cflags, libzstd_cflags, @@ -40,8 +37,7 @@ executables += [ 'HAVE_MICROHTTPD', ], 'sources' : systemd_journal_gatewayd_sources, - 'extract' : systemd_journal_gatewayd_extract_sources, - 'dependencies' : common_deps + [libmicrohttpd], + 'dependencies' : common_deps + [libmicrohttpd_cflags], }, libexec_template + { 'name' : 'systemd-journal-remote', @@ -52,8 +48,7 @@ executables += [ 'install' : conf.get('ENABLE_REMOTE') == 1, 'sources' : systemd_journal_remote_sources, 'extract' : systemd_journal_remote_extract_sources, - 'objects' : conf.get('HAVE_MICROHTTPD') == 1 ? ['systemd-journal-gatewayd'] : [], - 'dependencies' : common_deps + [libmicrohttpd], + 'dependencies' : common_deps + [libmicrohttpd_cflags], }, libexec_template + { 'name' : 'systemd-journal-upload', @@ -65,7 +60,7 @@ executables += [ 'sources' : systemd_journal_upload_sources, 'extract' : systemd_journal_upload_extract_sources, 'objects' : ['systemd-journal-remote'], - 'dependencies' : common_deps + [libcurl], + 'dependencies' : common_deps, }, test_template + { 'sources' : files('test-journal-header-util.c'), @@ -75,7 +70,7 @@ executables += [ fuzz_template + { 'sources' : files('fuzz-journal-remote.c'), 'objects' : ['systemd-journal-remote'], - 'dependencies' : common_deps + [libmicrohttpd], + 'dependencies' : common_deps + [libmicrohttpd_cflags], }, ] diff --git a/src/journal/bsod.c b/src/journal/bsod.c index 9a370af3908a7..b314c08660ac8 100644 --- a/src/journal/bsod.c +++ b/src/journal/bsod.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -11,10 +10,12 @@ #include "build.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "io-util.h" #include "log.h" #include "logs-show.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "qrcode-util.h" @@ -29,29 +30,32 @@ STATIC_DESTRUCTOR_REGISTER(arg_tty, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-bsod", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...]\n\n" - "%5$sFilter the journal to fetch the first message from the current boot with an%6$s\n" - "%5$semergency log level and display it as a string and a QR code.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -c --continuous Make systemd-bsod wait continuously\n" - " for changes in the journal\n" - " --tty=TTY Specify path to TTY to use\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n\n" + "%sFilter the journal to fetch the first message from the current boot with an\n" + "emergency log level and display it as a string and a QR code.%s\n" + "\n%sOptions:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } @@ -239,55 +243,35 @@ static int display_emergency_message_fullscreen(const char *message) { return r; } -static int parse_argv(int argc, char * argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_TTY, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "continuous", no_argument, NULL, 'c' }, - { "tty", required_argument, NULL, ARG_TTY }, - {} - }; - - int c, r; - +static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hc", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'c': + OPTION('c', "continuous", NULL, "Continuously wait for changes in the journal"): arg_continuous = true; break; - case ARG_TTY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tty); + OPTION_LONG("tty", "TTY", "Specify path to TTY to use"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tty); if (r < 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s takes no argument.", program_invocation_short_name); diff --git a/src/journal/cat.c b/src/journal/cat.c index 76d36fce7e477..62249663c581b 100644 --- a/src/journal/cat.c +++ b/src/journal/cat.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -12,12 +11,15 @@ #include "build.h" #include "env-util.h" #include "fd-util.h" +#include "format-table.h" #include "format-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "string-util.h" +#include "strv.h" #include "syslog-util.h" static const char *arg_identifier = NULL; @@ -28,104 +30,81 @@ static bool arg_level_prefix = true; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-cat", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] COMMAND ...\n" - "\n%sExecute process with stdout/stderr connected to the journal.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -t --identifier=STRING Set syslog identifier\n" - " -p --priority=PRIORITY Set priority value (0..7)\n" - " --stderr-priority=PRIORITY Set priority value (0..7) used for stderr\n" - " --level-prefix=BOOL Control whether level prefix shall be parsed\n" - " --namespace=NAMESPACE Connect to specified journal namespace\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sExecute process with stdout/stderr connected to the journal.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_STDERR_PRIORITY, - ARG_LEVEL_PREFIX, - ARG_NAMESPACE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "identifier", required_argument, NULL, 't' }, - { "priority", required_argument, NULL, 'p' }, - { "stderr-priority", required_argument, NULL, ARG_STDERR_PRIORITY }, - { "level-prefix", required_argument, NULL, ARG_LEVEL_PREFIX }, - { "namespace", required_argument, NULL, ARG_NAMESPACE }, - {} - }; - - int c, r; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; - while ((c = getopt_long(argc, argv, "+ht:p:", options, NULL)) >= 0) + OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - help(); - return 0; + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 't': - arg_identifier = empty_to_null(optarg); + OPTION('t', "identifier", "STRING", "Set syslog identifier"): + arg_identifier = empty_to_null(arg); break; - case 'p': - arg_priority = log_level_from_string(optarg); + OPTION('p', "priority", "PRIORITY", "Set priority value (0..7)"): + arg_priority = log_level_from_string(arg); if (arg_priority < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse priority value."); break; - case ARG_STDERR_PRIORITY: - arg_stderr_priority = log_level_from_string(optarg); + OPTION_LONG("stderr-priority", "PRIORITY", + "Set priority value (0..7) used for stderr"): + arg_stderr_priority = log_level_from_string(arg); if (arg_stderr_priority < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse stderr priority value."); break; - case ARG_LEVEL_PREFIX: - r = parse_boolean_argument("--level-prefix=", optarg, &arg_level_prefix); + OPTION_LONG("level-prefix", "BOOL", + "Control whether level prefix shall be parsed"): + r = parse_boolean_argument("--level-prefix=", arg, &arg_level_prefix); if (r < 0) return r; break; - case ARG_NAMESPACE: - arg_namespace = empty_to_null(optarg); + OPTION_LONG("namespace", "NAMESPACE", + "Connect to specified journal namespace"): + arg_namespace = empty_to_null(arg); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } @@ -135,7 +114,8 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -157,7 +137,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to rearrange stdout/stderr: %m"); - if (argc <= optind) + if (strv_isempty(args)) (void) execlp("cat", "cat", NULL); else { struct stat st; @@ -171,7 +151,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to set environment variable JOURNAL_STREAM: %m"); - (void) execvp(argv[optind], argv + optind); + (void) execvp(args[0], args); } r = -errno; diff --git a/src/journal/journalctl-filter.c b/src/journal/journalctl-filter.c index 0430f24ce9e46..ce2a6cb18c6ed 100644 --- a/src/journal/journalctl-filter.c +++ b/src/journal/journalctl-filter.c @@ -154,7 +154,6 @@ int journal_add_unit_matches( } } - if (!strv_isempty(patterns)) { _cleanup_set_free_ Set *units = NULL; diff --git a/src/journal/journalctl-varlink-server.c b/src/journal/journalctl-varlink-server.c index f44e2a807cfcb..85b4e225f5137 100644 --- a/src/journal/journalctl-varlink-server.c +++ b/src/journal/journalctl-varlink-server.c @@ -14,7 +14,6 @@ #include "strv.h" #include "unit-name.h" /* IWYU pragma: keep */ #include "user-util.h" -#include "varlink-util.h" typedef struct GetEntriesParameters { char **units; @@ -100,7 +99,7 @@ int vl_method_get_entries(sd_varlink *link, sd_json_variant *parameters, sd_varl if (r < 0) return r; - r = varlink_set_sentinel(link, "io.systemd.JournalAccess.NoEntries"); + r = sd_varlink_set_sentinel(link, "io.systemd.JournalAccess.NoEntries"); if (r < 0) return r; diff --git a/src/journal/journald-audit.c b/src/journal/journald-audit.c index fa6ba50b37708..173fe9944af47 100644 --- a/src/journal/journald-audit.c +++ b/src/journal/journald-audit.c @@ -158,6 +158,8 @@ static int map_generic_field( char *c, *t; int r; + assert(p); + /* Implements fallback mappings for all fields we don't know */ for (e = *p; e < *p + 16; e++) { diff --git a/src/journal/journald-manager.c b/src/journal/journald-manager.c index 8d95280fc61c7..764e5091ab3fd 100644 --- a/src/journal/journald-manager.c +++ b/src/journal/journald-manager.c @@ -135,6 +135,8 @@ static int manager_determine_path_usage( } static void cache_space_invalidate(JournalStorageSpace *space) { + assert(space); + zero(*space); } @@ -461,7 +463,9 @@ static int manager_find_user_journal(Manager *m, uid_t uid, JournalFile **ret) { _cleanup_free_ char *p = NULL; int r; + assert(m); assert(!uid_for_system_journal(uid)); + assert(ret); f = ordered_hashmap_get(m->user_journals, UID_TO_PTR(uid)); if (f) diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c index d61e8f5a743f7..1c36ca9434fa4 100644 --- a/src/journal/journald-native.c +++ b/src/journal/journald-native.c @@ -10,7 +10,6 @@ #include "fd-util.h" #include "format-util.h" #include "iovec-util.h" -#include "journal-importer.h" #include "journal-internal.h" #include "journald-client.h" #include "journald-console.h" @@ -45,6 +44,10 @@ static void manager_process_entry_meta( char **message, pid_t *object_pid) { + assert(priority); + assert(identifier); + assert(message); + /* We need to determine the priority of this entry for the rate limiting logic */ if (l == 10 && @@ -113,6 +116,8 @@ static int manager_process_entry( const char *p; int r = 1; + assert(remaining); + p = buffer; while (*remaining > 0) { @@ -140,7 +145,7 @@ static int manager_process_entry( } /* A property follows */ - if (n > ENTRY_FIELD_COUNT_MAX) { + if (n >= ENTRY_FIELD_COUNT_MAX) { log_debug("Received an entry that has more than " STRINGIFY(ENTRY_FIELD_COUNT_MAX) " fields, ignoring entry."); goto finish; } diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c index 677fb93b7597f..ee903755cf7a4 100644 --- a/src/journal/journald-stream.c +++ b/src/journal/journald-stream.c @@ -557,6 +557,8 @@ static int stdout_stream_process(sd_event_source *es, int fd, uint32_t revents, /* Try to make use of the allocated buffer in full, but never read more than the configured line size. Also, * always leave room for a terminating NUL we might need to add. */ + /* Silence static analyzers, GREEDY_REALLOC above ensures allocated > 0 */ + assert(allocated > 0); limit = MIN(allocated - 1, MAX(s->manager->config.line_max, STDOUT_STREAM_SETUP_PROTOCOL_LINE_MAX)); assert(s->length <= limit); iovec = IOVEC_MAKE(s->buffer + s->length, limit - s->length); diff --git a/src/journal/meson.build b/src/journal/meson.build index 5f64304219447..142d2246c1fe0 100644 --- a/src/journal/meson.build +++ b/src/journal/meson.build @@ -19,9 +19,6 @@ systemd_journald_extract_sources = files( 'journald-syslog.c', 'journald-varlink.c', 'journald-wall.c', - # Build fuzz-journald.c as part of systemd-journald so we only compile it once instead of once per - # fuzz test. - 'fuzz-journald-util.c', ) journald_gperf_c = custom_target( @@ -58,7 +55,10 @@ journal_test_template = test_template + { } journal_fuzz_template = fuzz_template + { - 'objects' : ['systemd-journald'], + 'objects' : [ + 'fuzz-journald-audit', + 'systemd-journald', + ], 'dependencies' : libselinux_cflags, } @@ -133,8 +133,11 @@ executables += [ libselinux_cflags, ], }, - journal_fuzz_template + { + fuzz_template + { 'sources' : files('fuzz-journald-audit.c'), + # fuzz-journald-util.c is shared with the other fuzzers below. + 'extract' : files('fuzz-journald-util.c'), + 'objects' : ['systemd-journald'], }, journal_fuzz_template + { 'sources' : files('fuzz-journald-kmsg.c'), diff --git a/src/kernel-install/60-ukify.install.in b/src/kernel-install/60-ukify.install.in index 076390dd0475e..310b6f26cafc0 100755 --- a/src/kernel-install/60-ukify.install.in +++ b/src/kernel-install/60-ukify.install.in @@ -185,7 +185,12 @@ def devicetree_file_location(opts) -> Optional[Path]: def kernel_cmdline_base() -> list[str]: path = input_file_location('cmdline') if path: - return path.read_text().split() + # Filter out commented out lines from cmdline. + lines = path.read_text().splitlines() + return [opt + for line in lines + if not line.startswith('#') + for opt in line.split()] # If we read /proc/cmdline, we need to do some additional filtering. options = Path('/proc/cmdline').read_text().split() diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index a38dcaab8b556..331923e9e5578 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -152,10 +152,10 @@ static int context_copy(const Context *source, Context *ret) { assert(source); assert(ret); - assert(source->rfd >= 0 || source->rfd == AT_FDCWD); + assert(source->rfd >= 0 || source->rfd == AT_FDCWD || source->rfd == XAT_FDROOT); _cleanup_(context_done) Context copy = (Context) { - .rfd = AT_FDCWD, + .rfd = source->rfd, .action = source->action, .machine_id = source->machine_id, .machine_id_is_random = source->machine_id_is_random, @@ -348,7 +348,7 @@ static int context_set_path(Context *c, const char *s, const char *source, const return 0; if (c->rfd >= 0) { - r = chaseat(c->rfd, s, CHASE_AT_RESOLVE_IN_ROOT, &p, /* ret_fd= */ NULL); + r = chaseat(c->rfd, c->rfd, s, /* flags= */ 0, &p, /* ret_fd= */ NULL); if (r < 0) return log_warning_errno(r, "Failed to chase path %s for %s specified via %s, ignoring: %m", s, name, source); @@ -396,7 +396,7 @@ static int context_set_path_strv(Context *c, char* const* strv, const char *sour char *p; if (c->rfd >= 0) { - r = chaseat(c->rfd, *s, CHASE_AT_RESOLVE_IN_ROOT, &p, /* ret_fd= */ NULL); + r = chaseat(c->rfd, c->rfd, *s, /* flags= */ 0, &p, /* ret_fd= */ NULL); if (r < 0) return log_warning_errno(r, "Failed to chase path %s for %s specified via %s: %m", *s, name, source); @@ -503,7 +503,7 @@ static int context_load_machine_info(Context *c) { return 0; } - r = chase_and_fopenat_unlocked(c->rfd, path, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f); + r = chase_and_fopenat_unlocked(c->rfd, c->rfd, path, /* chase_flags= */ 0, "re", NULL, &f); if (r == -ENOENT) return 0; if (r < 0) @@ -571,8 +571,7 @@ static int context_acquire_xbootldr(Context *c) { /* path= */ arg_xbootldr_path, /* unprivileged_mode= */ -1, /* ret_path= */ &c->boot_root, - /* ret_uuid= */ NULL, - /* ret_devid= */ NULL); + /* ret_fd= */ NULL); if (r == -ENOKEY) { log_debug_errno(r, "Couldn't find an XBOOTLDR partition."); return 0; @@ -597,11 +596,7 @@ static int context_acquire_esp(Context *c) { /* path= */ arg_esp_path, /* unprivileged_mode= */ -1, /* ret_path= */ &c->boot_root, - /* ret_part= */ NULL, - /* ret_pstart= */ NULL, - /* ret_psize= */ NULL, - /* ret_uuid= */ NULL, - /* ret_devid= */ NULL); + /* ret_fd= */ NULL); if (r == -ENOKEY) { log_debug_errno(r, "Couldn't find EFI system partition, ignoring."); return 0; @@ -636,7 +631,7 @@ static int context_ensure_boot_root(Context *c) { /* If all else fails, use /boot. */ if (c->rfd >= 0) { - r = chaseat(c->rfd, "/boot", CHASE_AT_RESOLVE_IN_ROOT, &c->boot_root, /* ret_fd= */ NULL); + r = chaseat(c->rfd, c->rfd, "/boot", 0, &c->boot_root, /* ret_fd= */ NULL); if (r < 0) return log_error_errno(r, "Failed to chase '/boot/': %m"); } else { @@ -757,12 +752,21 @@ static int context_from_cmdline(Context *c, Action action) { } static int context_inspect_kernel(Context *c) { + int r; + assert(c); if (!c->kernel) return 0; - return inspect_kernel(c->rfd, c->kernel, &c->kernel_image_type, NULL, NULL, NULL); + r = inspect_kernel( + c->rfd, + c->kernel, + &c->kernel_image_type); + if (r < 0) + return log_error_errno(r, "Failed to inspect kernel image '%s': %m", c->kernel); + + return 0; } static int context_ensure_layout(Context *c) { @@ -790,7 +794,7 @@ static int context_ensure_layout(Context *c) { return log_oom(); _cleanup_fclose_ FILE *f = NULL; - r = chase_and_fopenat_unlocked(c->rfd, srel_path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); + r = chase_and_fopenat_unlocked(c->rfd, c->rfd, srel_path, CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); if (r < 0) { if (r != -ENOENT) return log_error_errno(r, "Failed to open '%s': %m", srel_path); @@ -820,7 +824,7 @@ static int context_ensure_layout(Context *c) { if (!entry_token_path) return log_oom(); - r = chaseat(c->rfd, entry_token_path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, /* ret_fd= */ NULL); + r = chaseat(c->rfd, c->rfd, entry_token_path, CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, /* ret_fd= */ NULL); if (r < 0) { if (!IN_SET(r, -ENOENT, -ENOTDIR)) return log_error_errno(r, "Failed to check if '%s' exists and is a directory: %m", entry_token_path); @@ -912,7 +916,7 @@ static int context_make_entry_dir(Context *c) { return 0; log_debug("mkdir -p %s", c->entry_dir); - fd = chase_and_openat(c->rfd, c->entry_dir, CHASE_AT_RESOLVE_IN_ROOT | CHASE_MKDIR_0755, + fd = chase_and_openat(c->rfd, c->rfd, c->entry_dir, CHASE_MKDIR_0755, O_CLOEXEC | O_CREAT | O_DIRECTORY | O_PATH, NULL); if (fd < 0) return log_error_errno(fd, "Failed to make directory '%s': %m", c->entry_dir); @@ -936,7 +940,7 @@ static int context_remove_entry_dir(Context *c) { return 0; log_debug("rm -rf %s", c->entry_dir); - fd = chase_and_openat(c->rfd, c->entry_dir, CHASE_AT_RESOLVE_IN_ROOT, O_CLOEXEC | O_DIRECTORY, &p); + fd = chase_and_openat(c->rfd, c->rfd, c->entry_dir, /* chase_flags= */ 0, O_CLOEXEC | O_DIRECTORY, &p); if (fd < 0) { if (IN_SET(fd, -ENOTDIR, -ENOENT)) return 0; @@ -1126,6 +1130,7 @@ static int kernel_from_version(const char *version, char **ret_kernel) { int r; assert(version); + assert(ret_kernel); vmlinuz = path_join("/usr/lib/modules/", version, "/vmlinuz"); if (!vmlinuz) @@ -1185,7 +1190,7 @@ static int do_add( return context_execute(c); } -static int verb_add(int argc, char *argv[], void *userdata) { +static int verb_add(int argc, char *argv[], uintptr_t _data, void *userdata) { const char *version, *kernel; char **initrds; int r; @@ -1214,7 +1219,7 @@ static int verb_add(int argc, char *argv[], void *userdata) { return do_add(&c, version, kernel, initrds); } -static int verb_add_all(int argc, char *argv[], void *userdata) { +static int verb_add_all(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_close_ int fd = -EBADF; size_t n = 0; int ret = 0, r; @@ -1232,7 +1237,7 @@ static int verb_add_all(int argc, char *argv[], void *userdata) { if (r < 0) return r; - fd = chase_and_openat(c.rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); + fd = chase_and_openat(c.rfd, c.rfd, "/usr/lib/modules", /* chase_flags= */ 0, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); if (fd < 0) return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root)); @@ -1301,10 +1306,10 @@ static int run_as_installkernel(int argc, char *argv[]) { if (optind + 2 > argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'installkernel' command requires at least two arguments."); - return verb_add(3, STRV_MAKE("add", argv[optind], argv[optind+1]), /* userdata= */ NULL); + return verb_add(3, STRV_MAKE("add", argv[optind], argv[optind+1]), /* data= */ 0, /* userdata= */ NULL); } -static int verb_remove(int argc, char *argv[], void *userdata) { +static int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; assert(argc >= 2); @@ -1340,7 +1345,7 @@ static int verb_remove(int argc, char *argv[], void *userdata) { return context_execute(&c); } -static int verb_inspect(int argc, char *argv[], void *userdata) { +static int verb_inspect(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *t = NULL; _cleanup_free_ char *vmlinuz = NULL; const char *version, *kernel; @@ -1453,7 +1458,7 @@ static int verb_inspect(int argc, char *argv[], void *userdata) { return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, /* show_header= */ false); } -static int verb_list(int argc, char *argv[], void *userdata) { +static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_close_ int fd = -EBADF; int r; @@ -1462,7 +1467,7 @@ static int verb_list(int argc, char *argv[], void *userdata) { if (r < 0) return r; - fd = chase_and_openat(c.rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); + fd = chase_and_openat(c.rfd, c.rfd, "/usr/lib/modules", /* chase_flags= */ 0, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); if (fd < 0) return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root)); diff --git a/src/kernel-install/test-kernel-install.sh b/src/kernel-install/test-kernel-install.sh index e2add8ba80b5c..399979a0260e2 100755 --- a/src/kernel-install/test-kernel-install.sh +++ b/src/kernel-install/test-kernel-install.sh @@ -29,7 +29,12 @@ mkdir -p "$D/sources" echo 'buzy image' >"$D/sources/linux" echo 'the initrd' >"$D/sources/initrd" echo 'the-token' >"$D/sources/entry-token" -echo 'opt1 opt2' >"$D/sources/cmdline" + +cat >"$D/sources/cmdline" <"$D/sources/install.conf" < - #include "alloc-util.h" #include "ask-password-api.h" #include "build.h" +#include "crypto-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "log.h" #include "main-func.h" -#include "openssl-util.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "string-util.h" @@ -25,7 +25,7 @@ static char *arg_certificate_source = NULL; static CertificateSourceType arg_certificate_source_type = OPENSSL_CERTIFICATE_SOURCE_FILE; static char *arg_signature = NULL; static char *arg_content = NULL; -static char *arg_hash_algorithm = NULL; +static const char *arg_hash_algorithm = NULL; static char *arg_output = NULL; STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); @@ -36,164 +36,134 @@ STATIC_DESTRUCTOR_REGISTER(arg_signature, freep); STATIC_DESTRUCTOR_REGISTER(arg_content, freep); STATIC_DESTRUCTOR_REGISTER(arg_output, freep); -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-keyutil", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sPerform various operations on private keys and certificates.%6$s\n" - "\n%3$sCommands:%4$s\n" - " validate Load and validate the given certificate and private key\n" - " extract-public Extract a public key\n" - " extract-certificate Extract a certificate\n" - " pkcs7 Generate a PKCS#7 signature\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --private-key=KEY Private key in PEM format\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used for signing\n" - " --certificate=PATH|URI\n" - " PEM certificate to use for signing, or a provider\n" - " specific designation if --certificate-source= is used\n" - " --certificate-source=file|provider:PROVIDER\n" - " Specify how to interpret the certificate from\n" - " --certificate=. Allows the certificate to be loaded\n" - " from an OpenSSL provider\n" - " --content=PATH Raw data content to embed in PKCS#7 signature\n" - " --signature=PATH PKCS#1 signature to embed in PKCS#7 signature\n" - " --hash-algorithm=ALGORITHM\n" - " Hash algorithm used to create the PKCS#1 signature\n" - " --output=PATH Where to write the PKCS#7 signature\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sPerform various operations on private keys and certificates.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - ARG_CERTIFICATE, - ARG_CERTIFICATE_SOURCE, - ARG_SIGNATURE, - ARG_CONTENT, - ARG_HASH_ALGORITHM, - ARG_OUTPUT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, - { "signature", required_argument, NULL, ARG_SIGNATURE }, - { "content", required_argument, NULL, ARG_CONTENT }, - { "hash-algorithm", required_argument, NULL, ARG_HASH_ALGORITHM }, - { "output", required_argument, NULL, ARG_OUTPUT }, - {} - }; - - int c, r; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - help(0, NULL, NULL); - return 0; + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_PRIVATE_KEY: - r = free_and_strdup_warn(&arg_private_key, optarg); + OPTION_COMMON_PRIVATE_KEY("Private key in PEM format"): + r = free_and_strdup_warn(&arg_private_key, arg); if (r < 0) return r; - break; - case ARG_PRIVATE_KEY_SOURCE: + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - optarg, + arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) return r; - break; - case ARG_CERTIFICATE: - r = free_and_strdup_warn(&arg_certificate, optarg); + OPTION_COMMON_CERTIFICATE("PEM certificate to use for signing"): + r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - optarg, + arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) return r; break; - case ARG_SIGNATURE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_signature); + OPTION_LONG("signature", "PATH", "PKCS#1 signature to embed in PKCS#7 signature"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_signature); if (r < 0) return r; - break; - case ARG_CONTENT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_content); + OPTION_LONG("content", "PATH", "Raw data content to embed in PKCS#7 signature"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_content); if (r < 0) return r; - break; - case ARG_HASH_ALGORITHM: - arg_hash_algorithm = optarg; + OPTION_LONG("hash-algorithm", "ALGORITHM", + "Hash algorithm used to create the PKCS#1 signature"): + arg_hash_algorithm = arg; break; - case ARG_OUTPUT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_output); + OPTION_LONG("output", "PATH", "Where to write the PKCS#7 signature"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_output); if (r < 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_private_key_source && !arg_certificate) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When using --private-key-source=, --certificate= must be specified."); + *ret_args = option_parser_get_args(&state); return 1; } -static int verb_validate(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_validate, "validate", + "Load and validate the given certificate and private key"); +static int verb_validate(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(X509_freep) X509 *certificate = NULL; _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL; @@ -248,7 +218,10 @@ static int verb_validate(int argc, char *argv[], void *userdata) { return 0; } -static int verb_extract_public(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_extract_public, "extract-public", + "Extract a public key"); +VERB_NOARG(verb_extract_public, "public", /* help= */ NULL); /* Deprecated but kept for backward compat. */ +static int verb_extract_public(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *public_key = NULL; int r; @@ -261,6 +234,10 @@ static int verb_extract_public(int argc, char *argv[], void *userdata) { return r; } + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + r = openssl_load_x509_certificate( arg_certificate_source_type, arg_certificate_source, @@ -269,7 +246,7 @@ static int verb_extract_public(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); - public_key = X509_get_pubkey(certificate); + public_key = sym_X509_get_pubkey(certificate); if (!public_key) return log_error_errno( SYNTHETIC_ERRNO(EIO), @@ -309,13 +286,15 @@ static int verb_extract_public(int argc, char *argv[], void *userdata) { } else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "One of --certificate=, or --private-key= must be specified"); - if (PEM_write_PUBKEY(stdout, public_key) == 0) + if (sym_PEM_write_PUBKEY(stdout, public_key) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write public key to stdout"); return 0; } -static int verb_extract_certificate(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_extract_certificate, "extract-certificate", + "Extract a certificate"); +static int verb_extract_certificate(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(X509_freep) X509 *certificate = NULL; int r; @@ -336,13 +315,15 @@ static int verb_extract_certificate(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); - if (PEM_write_X509(stdout, certificate) == 0) + if (sym_PEM_write_X509(stdout, certificate) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write certificate to stdout."); return 0; } -static int verb_pkcs7(int argc, char *argv[], void *userdata) { +VERB(verb_pkcs7, "pkcs7", NULL, VERB_ANY, VERB_ANY, 0, + "Generate a PKCS#7 signature"); +static int verb_pkcs7(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(X509_freep) X509 *certificate = NULL; _cleanup_free_ char *pkcs1 = NULL; size_t pkcs1_len = 0; @@ -393,18 +374,18 @@ static int verb_pkcs7(int argc, char *argv[], void *userdata) { if (content_len == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Content file %s is empty", arg_content); - if (!PKCS7_content_new(pkcs7, NID_pkcs7_data)) + if (!sym_PKCS7_content_new(pkcs7, NID_pkcs7_data)) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Error creating new PKCS7 content field"); - ASN1_STRING_set0(pkcs7->d.sign->contents->d.data, TAKE_PTR(content), content_len); + sym_ASN1_STRING_set0(pkcs7->d.sign->contents->d.data, TAKE_PTR(content), content_len); } else - if (PKCS7_set_detached(pkcs7, true) == 0) + if (sym_PKCS7_set_detached(pkcs7, true) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS#7 detached attribute: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); /* Add PKCS1 signature to PKCS7_SIGNER_INFO */ - ASN1_STRING_set0(signer_info->enc_digest, TAKE_PTR(pkcs1), pkcs1_len); + sym_ASN1_STRING_set0(signer_info->enc_digest, TAKE_PTR(pkcs1), pkcs1_len); _cleanup_fclose_ FILE *output = NULL; _cleanup_(unlink_and_freep) char *tmp = NULL; @@ -412,9 +393,9 @@ static int verb_pkcs7(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to open temporary file: %m"); - if (!i2d_PKCS7_fp(output, pkcs7)) + if (!sym_i2d_PKCS7_fp(output, pkcs7)) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write PKCS#7 file: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); r = flink_tmpfile(output, tmp, arg_output, LINK_TMPFILE_REPLACE|LINK_TMPFILE_SYNC); if (r < 0) @@ -426,24 +407,16 @@ static int verb_pkcs7(int argc, char *argv[], void *userdata) { } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "validate", VERB_ANY, 1, 0, verb_validate }, - { "extract-public", VERB_ANY, 1, 0, verb_extract_public }, - { "public", VERB_ANY, 1, 0, verb_extract_public }, /* Deprecated but kept for backwards compat. */ - { "extract-certificate", VERB_ANY, 1, 0, verb_extract_certificate }, - { "pkcs7", VERB_ANY, VERB_ANY, 0, verb_pkcs7 }, - {} - }; int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/keyutil/meson.build b/src/keyutil/meson.build index 956f6039895de..ae3db9a276cf3 100644 --- a/src/keyutil/meson.build +++ b/src/keyutil/meson.build @@ -7,6 +7,6 @@ executables += [ 'HAVE_OPENSSL', ], 'sources' : files('keyutil.c'), - 'dependencies' : libopenssl, + 'dependencies' : libopenssl_cflags, }, ] diff --git a/src/libc/kexec.c b/src/libc/kexec.c new file mode 100644 index 0000000000000..122acff956c07 --- /dev/null +++ b/src/libc/kexec.c @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#if !HAVE_KEXEC_FILE_LOAD && defined __NR_kexec_file_load +int missing_kexec_file_load(int kernel_fd, int initrd_fd, unsigned long cmdline_len, const char *cmdline, unsigned long flags) { + return syscall(__NR_kexec_file_load, kernel_fd, initrd_fd, cmdline_len, cmdline, flags); +} +#endif diff --git a/src/libc/meson.build b/src/libc/meson.build index 306512ffd7002..3b7b96d07f219 100644 --- a/src/libc/meson.build +++ b/src/libc/meson.build @@ -4,6 +4,7 @@ libc_wrapper_sources = files( 'bpf.c', 'ioprio.c', 'kcmp.c', + 'kexec.c', 'keyctl.c', 'mempolicy.c', 'mount.c', diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h index f75ca71b4350c..fab4ff24aaf99 100644 --- a/src/libsystemd-network/dhcp-client-internal.h +++ b/src/libsystemd-network/dhcp-client-internal.h @@ -3,8 +3,11 @@ #include "sd-dhcp-client.h" -#include "sd-forward.h" +#include "dhcp-client-id-internal.h" +#include "ether-addr-util.h" #include "network-common.h" +#include "sd-forward.h" +#include "socket-util.h" typedef enum DHCPState { DHCP_STATE_STOPPED, @@ -22,7 +25,64 @@ typedef enum DHCPState { DECLARE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_state, DHCPState); -typedef struct sd_dhcp_client sd_dhcp_client; +struct sd_dhcp_client { + unsigned n_ref; + + DHCPState state; + sd_event *event; + int event_priority; + sd_event_source *timeout_resend; + + int ifindex; + char *ifname; + + sd_device *dev; + + uint16_t port; + uint16_t server_port; + union sockaddr_union link; + sd_event_source *receive_message; + bool request_broadcast; + Set *req_opts; + bool anonymize; + bool rapid_commit; + be32_t last_addr; + struct hw_addr_data hw_addr; + struct hw_addr_data bcast_addr; + uint16_t arp_type; + sd_dhcp_client_id client_id; + char *hostname; + char *vendor_class_identifier; + char *mudurl; + char **user_class; + uint32_t mtu; + usec_t fallback_lease_lifetime; + uint32_t xid; + usec_t start_time; + usec_t t1_time; + usec_t t2_time; + usec_t expire_time; + uint64_t discover_attempt; + uint64_t request_attempt; + uint64_t max_discover_attempts; + OrderedHashmap *extra_options; + OrderedHashmap *vendor_options; + sd_event_source *timeout_t1; + sd_event_source *timeout_t2; + sd_event_source *timeout_expire; + sd_dhcp_client_callback_t callback; + void *userdata; + sd_dhcp_client_callback_t state_callback; + void *state_userdata; + sd_dhcp_lease *lease; + usec_t start_delay; + int ip_service_type; + int socket_priority; + bool socket_priority_set; + bool ipv6_acquired; + bool bootp; + bool send_release; +}; int dhcp_client_set_state_callback( sd_dhcp_client *client, @@ -30,6 +90,17 @@ int dhcp_client_set_state_callback( void *userdata); int dhcp_client_get_state(sd_dhcp_client *client); +int client_receive_message_raw( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata); +int client_receive_message_udp( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata); + /* If we are invoking callbacks of a dhcp-client, ensure unreffing the * client from the callback doesn't destroy the object we are working * on */ diff --git a/src/libsystemd-network/dhcp-client-send.c b/src/libsystemd-network/dhcp-client-send.c new file mode 100644 index 0000000000000..56306fbf53647 --- /dev/null +++ b/src/libsystemd-network/dhcp-client-send.c @@ -0,0 +1,189 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-event.h" + +#include "dhcp-client-internal.h" +#include "dhcp-client-send.h" +#include "dhcp-lease-internal.h" /* IWYU pragma: keep */ +#include "dhcp-network.h" +#include "dhcp-packet.h" +#include "fd-util.h" +#include "socket-util.h" + +static int client_get_socket(sd_dhcp_client *client, int domain) { + int r, d, fd; + + assert(client); + assert(IN_SET(domain, AF_PACKET, AF_INET)); + + if (!client->receive_message) + return -EBADF; + + fd = sd_event_source_get_io_fd(client->receive_message); + if (fd < 0) + return fd; + + r = getsockopt_int(fd, SOL_SOCKET, SO_DOMAIN, &d); + if (r < 0) + return r; + + if (d != domain) + return -EBADF; + + return fd; +} + +static int client_setup_io_event( + sd_dhcp_client *client, + int fd, + sd_event_io_handler_t callback, + const char *description) { + + int r; + + assert(client); + assert(fd >= 0); + assert(callback); + assert(description); + + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + r = sd_event_add_io(client->event, &s, fd, EPOLLIN, callback, client); + if (r < 0) + return r; + + r = sd_event_source_set_priority(s, client->event_priority); + if (r < 0) + return r; + + r = sd_event_source_set_description(s, description); + if (r < 0) + return r; + + r = sd_event_source_set_io_fd_own(s, true); + if (r < 0) + return r; + + sd_event_source_disable_unref(client->receive_message); + client->receive_message = TAKE_PTR(s); + return 0; +} + +int dhcp_client_send_raw( + sd_dhcp_client *client, + bool expect_reply, + DHCPPacket *packet, + size_t optoffset) { + + _cleanup_close_ int fd_close = -EBADF; + int r, fd; + + assert(client); + assert(packet); + + fd = client_get_socket(client, AF_PACKET); + if (fd < 0) { + fd = dhcp_network_bind_raw_socket( + client->ifindex, + &client->link, + client->xid, + &client->hw_addr, + &client->bcast_addr, + client->arp_type, + client->port, + client->socket_priority_set, + client->socket_priority); + if (fd < 0) + return fd; + + fd_close = fd; + } + + r = dhcp_packet_append_ip_headers( + packet, + INADDR_ANY, + client->port, + INADDR_BROADCAST, + client->server_port, + sizeof(DHCPPacket) + optoffset, + client->ip_service_type); + if (r < 0) + return r; + + r = dhcp_network_send_raw_socket( + fd, + &client->link, + packet, + sizeof(DHCPPacket) + optoffset); + if (r < 0) + return r; + + if (!expect_reply) { + /* We do not expect any replies, hence stop the IO event source if enabled. */ + client->receive_message = sd_event_source_disable_unref(client->receive_message); + return 0; + } + + if (fd_close < 0) + return 0; /* Already opened socket is reused. Not necessary to setup new IO event source. */ + + r = client_setup_io_event(client, fd, client_receive_message_raw, "dhcp4-receive-message-raw"); + if (r < 0) + return r; + + TAKE_FD(fd_close); + return 0; +} + +int dhcp_client_send_udp( + sd_dhcp_client *client, + bool expect_reply, + DHCPPacket *packet, + size_t optoffset) { + + _cleanup_close_ int fd_close = -EBADF; + int r, fd; + + assert(client); + assert(packet); + + if (!client->lease || client->lease->address == 0) + return -EADDRNOTAVAIL; + + fd = client_get_socket(client, AF_INET); + if (fd < 0) { + fd = dhcp_network_bind_udp_socket( + client->ifindex, + client->lease->address, + client->port, + client->ip_service_type); + if (fd < 0) + return fd; + + fd_close = fd; + } + + r = dhcp_network_send_udp_socket( + fd, + client->lease->server_address, + client->server_port, + &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + if (r < 0) + return r; + + if (!expect_reply) { + /* We do not expect any replies, hence stop the IO event source if enabled. */ + client->receive_message = sd_event_source_disable_unref(client->receive_message); + return 0; + } + + if (fd_close < 0) + return 0; /* Already opened socket is reused. Not necessary to setup new IO event source. */ + + r = client_setup_io_event(client, fd, client_receive_message_udp, "dhcp4-receive-message-udp"); + if (r < 0) + return r; + + TAKE_FD(fd_close); + return 0; +} diff --git a/src/libsystemd-network/dhcp-client-send.h b/src/libsystemd-network/dhcp-client-send.h new file mode 100644 index 0000000000000..2dbb6a0878dcc --- /dev/null +++ b/src/libsystemd-network/dhcp-client-send.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +#include "dhcp-protocol.h" + +int dhcp_client_send_raw( + sd_dhcp_client *client, + bool expect_reply, + DHCPPacket *packet, + size_t optoffset); + +int dhcp_client_send_udp( + sd_dhcp_client *client, + bool expect_reply, + DHCPPacket *packet, + size_t optoffset); diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index a840b61462157..a195a4b098353 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -59,6 +59,11 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, break; case SD_DHCP_OPTION_USER_CLASS: { + /* When called with raw data (optlen > 0), e.g. from SendOption=, append as a plain TLV. + * The structured handling below expects optval to be a strv. */ + if (optlen > 0) + return dhcp_option_append_tlv(options, size, offset, code, optlen, optval); + size_t total = 0; if (strv_isempty((char **) optval)) @@ -103,6 +108,11 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, break; case SD_DHCP_OPTION_VENDOR_SPECIFIC: { + /* When called with raw data (optlen > 0), e.g. from SendOption=, append as a plain TLV. + * The structured handling below expects optval to be an OrderedSet*. */ + if (optlen > 0) + return dhcp_option_append_tlv(options, size, offset, code, optlen, optval); + OrderedSet *s = (OrderedSet *) optval; struct sd_dhcp_option *p; size_t l = 0; @@ -125,6 +135,11 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, break; } case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION: { + /* When called with raw data (optlen > 0), e.g. from SendOption=, append as a plain TLV. + * The structured handling below expects optval to be an sd_dhcp_server*. */ + if (optlen > 0) + return dhcp_option_append_tlv(options, size, offset, code, optlen, optval); + sd_dhcp_server *server = (sd_dhcp_server *) optval; size_t current_offset = *offset + 2; @@ -412,7 +427,7 @@ int dhcp_option_parse_string(const uint8_t *option, size_t len, char **ret) { if (r < 0) return r; - if (!string_is_safe(string) || !utf8_is_valid(string)) + if (!string_is_safe(string, STRING_ALLOW_EMPTY|STRING_ALLOW_GLOBS)) return -EINVAL; *ret = TAKE_PTR(string); diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c index 90eae88379ad5..27b09a25bbb4f 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -4,11 +4,12 @@ ***/ #include -#include #include "dhcp-option.h" #include "dhcp-packet.h" -#include "log.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "ip-util.h" #include "memory-util.h" #define DHCP_CLIENT_MIN_OPTIONS_SIZE 312 @@ -79,133 +80,42 @@ int dhcp_message_init( return 0; } -uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len) { - uint64_t *buf_64 = (uint64_t*)buf; - uint64_t *end_64 = buf_64 + (len / sizeof(uint64_t)); - uint64_t sum = 0; - - /* See RFC1071 */ - - while (buf_64 < end_64) { - sum += *buf_64; - if (sum < *buf_64) - /* wrap around in one's complement */ - sum++; - - buf_64++; - } - - if (len % sizeof(uint64_t)) { - /* If the buffer is not aligned to 64-bit, we need - to zero-pad the last few bytes and add them in */ - uint64_t buf_tail = 0; - - memcpy(&buf_tail, buf_64, len % sizeof(uint64_t)); - - sum += buf_tail; - if (sum < buf_tail) - /* wrap around */ - sum++; - } - - while (sum >> 16) - sum = (sum & 0xffff) + (sum >> 16); - - return ~sum; -} - -void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr, - uint16_t source_port, be32_t destination_addr, - uint16_t destination_port, uint16_t len, int ip_service_type) { - packet->ip.version = IPVERSION; - packet->ip.ihl = DHCP_IP_SIZE / 4; - packet->ip.tot_len = htobe16(len); - - if (ip_service_type >= 0) - packet->ip.tos = ip_service_type; - else - packet->ip.tos = IPTOS_CLASS_CS6; - - packet->ip.protocol = IPPROTO_UDP; - packet->ip.saddr = source_addr; - packet->ip.daddr = destination_addr; - - packet->udp.source = htobe16(source_port); - packet->udp.dest = htobe16(destination_port); - - packet->udp.len = htobe16(len - DHCP_IP_SIZE); +int dhcp_packet_append_ip_headers( + DHCPPacket *packet, + be32_t source_addr, + uint16_t source_port, + be32_t destination_addr, + uint16_t destination_port, + uint16_t len, + int ip_service_type) { + + struct iphdr ip; + struct udphdr udp; + int r; - packet->ip.check = packet->udp.len; - packet->udp.check = dhcp_packet_checksum(&packet->ip.ttl, len - 8); + assert(packet); + assert(len > offsetof(DHCPPacket, dhcp)); + + r = udp_packet_build( + source_addr, + source_port, + destination_addr, + destination_port, + ip_service_type, + &(struct iovec_wrapper) { + .iovec = &IOVEC_MAKE(&packet->dhcp, len - offsetof(DHCPPacket, dhcp)), + .count = 1, + }, + &ip, + &udp); + if (r < 0) + return r; - packet->ip.ttl = IPDEFTTL; - packet->ip.check = 0; - packet->ip.check = dhcp_packet_checksum((uint8_t*)&packet->ip, DHCP_IP_SIZE); + packet->ip = ip; + packet->udp = udp; + return 0; } int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port) { - size_t hdrlen; - - assert(packet); - - if (len < sizeof(DHCPPacket)) - return 0; - - /* IP */ - - if (packet->ip.version != IPVERSION) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: not IPv4"); - - if (packet->ip.ihl < 5) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: IPv4 IHL (%i words) invalid", - packet->ip.ihl); - - hdrlen = packet->ip.ihl * 4; - if (hdrlen < 20) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: IPv4 IHL (%zu bytes) smaller than minimum (20 bytes)", - hdrlen); - - if (len < hdrlen) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: packet (%zu bytes) smaller than expected (%zu) by IP header", - len, hdrlen); - - /* UDP */ - - if (packet->ip.protocol != IPPROTO_UDP) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: not UDP"); - - if (len < hdrlen + be16toh(packet->udp.len)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: packet (%zu bytes) smaller than expected (%zu) by UDP header", - len, hdrlen + be16toh(packet->udp.len)); - - if (be16toh(packet->udp.dest) != port) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: to port %u, which is not the DHCP client port (%u)", - be16toh(packet->udp.dest), port); - - /* checksums - computing these is relatively expensive, so only do it - if all the other checks have passed - */ - - if (dhcp_packet_checksum((uint8_t*)&packet->ip, hdrlen)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: invalid IP checksum"); - - if (checksum && packet->udp.check) { - packet->ip.check = packet->udp.len; - packet->ip.ttl = 0; - - if (dhcp_packet_checksum(&packet->ip.ttl, - be16toh(packet->udp.len) + 12)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: invalid UDP checksum"); - } - - return 0; + return udp_packet_verify(&IOVEC_MAKE(packet, len), port, checksum, /* ret_payload= */ NULL); } diff --git a/src/libsystemd-network/dhcp-packet.h b/src/libsystemd-network/dhcp-packet.h index 967bd5d89df71..90ea2caac987a 100644 --- a/src/libsystemd-network/dhcp-packet.h +++ b/src/libsystemd-network/dhcp-packet.h @@ -23,9 +23,7 @@ int dhcp_message_init( size_t optlen, size_t *ret_optoffset); -uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len); - -void dhcp_packet_append_ip_headers( +int dhcp_packet_append_ip_headers( DHCPPacket *packet, be32_t source_addr, uint16_t source, diff --git a/src/libsystemd-network/dhcp-protocol.c b/src/libsystemd-network/dhcp-protocol.c new file mode 100644 index 0000000000000..3a22e0f225aaa --- /dev/null +++ b/src/libsystemd-network/dhcp-protocol.c @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dhcp-protocol.h" +#include "string-table.h" + +static const char * const bootp_message_type_table[_BOOTP_MESSAGE_TYPE_MAX] = { + [BOOTREQUEST] = "BOOTREQUEST", + [BOOTREPLY] = "BOOTREPLY", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(bootp_message_type, BOOTPMessageType); + +static const char * const dhcp_message_type_table[_DHCP_MESSAGE_TYPE_MAX] = { + [DHCP_DISCOVER] = "DISCOVER", + [DHCP_OFFER] = "OFFER", + [DHCP_REQUEST] = "REQUEST", + [DHCP_DECLINE] = "DECLINE", + [DHCP_ACK] = "ACK", + [DHCP_NAK] = "NAK", + [DHCP_RELEASE] = "RELEASE", + [DHCP_INFORM] = "INFORM", + [DHCP_FORCERENEW] = "FORCERENEW", + [DHCP_LEASEQUERY] = "LEASEQUERY", + [DHCP_LEASEUNASSIGNED] = "LEASEUNASSIGNED", + [DHCP_LEASEUNKNOWN] = "LEASEUNKNOWN", + [DHCP_LEASEACTIVE] = "LEASEACTIVE", + [DHCP_BULKLEASEQUERY] = "BULKLEASEQUERY", + [DHCP_LEASEQUERYDONE] = "LEASEQUERYDONE", + [DHCP_ACTIVELEASEQUERY] = "ACTIVELEASEQUERY", + [DHCP_LEASEQUERYSTATUS] = "LEASEQUERYSTATUS", + [DHCP_TLS] = "TLS", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_message_type, DHCPMessageType); + +static const char * const dhcp_option_code_table[] = { + [SD_DHCP_OPTION_PAD] = "pad", + [SD_DHCP_OPTION_SUBNET_MASK] = "subnet mask", + [SD_DHCP_OPTION_TIME_OFFSET] = "time offset", + [SD_DHCP_OPTION_ROUTER] = "router", + [SD_DHCP_OPTION_TIME_SERVER] = "time server", + [SD_DHCP_OPTION_NAME_SERVER] = "name server", + [SD_DHCP_OPTION_DOMAIN_NAME_SERVER] = "domain name server", + [SD_DHCP_OPTION_LOG_SERVER] = "log server", + [SD_DHCP_OPTION_QUOTES_SERVER] = "quotes server", + [SD_DHCP_OPTION_LPR_SERVER] = "LPR server", + [SD_DHCP_OPTION_IMPRESS_SERVER] = "impress server", + [SD_DHCP_OPTION_RLP_SERVER] = "RLP server", + [SD_DHCP_OPTION_HOST_NAME] = "hostname", + [SD_DHCP_OPTION_BOOT_FILE_SIZE] = "boot file size", + [SD_DHCP_OPTION_MERIT_DUMP_FILE] = "merit dump file", + [SD_DHCP_OPTION_DOMAIN_NAME] = "domain name", + [SD_DHCP_OPTION_SWAP_SERVER] = "swap server", + [SD_DHCP_OPTION_ROOT_PATH] = "root path", + [SD_DHCP_OPTION_EXTENSION_FILE] = "extension file", + [SD_DHCP_OPTION_FORWARD] = "IP forwarding", + [SD_DHCP_OPTION_SOURCE_ROUTE] = "source routing", + [SD_DHCP_OPTION_POLICY_FILTER] = "policy filter", + [SD_DHCP_OPTION_MAX_DATAGRAM_ASSEMBLY] = "max datagram assembly", + [SD_DHCP_OPTION_DEFAULT_IP_TTL] = "default IP TTL", + [SD_DHCP_OPTION_MTU_TIMEOUT] = "MTU timeout", + [SD_DHCP_OPTION_MTU_PLATEAU] = "MTU plateau", + [SD_DHCP_OPTION_MTU_INTERFACE] = "MTU size", + [SD_DHCP_OPTION_MTU_SUBNET] = "MTU subnet", + [SD_DHCP_OPTION_BROADCAST] = "broadcast address", + [SD_DHCP_OPTION_MASK_DISCOVERY] = "mask discovery", + [SD_DHCP_OPTION_MASK_SUPPLIER] = "mask supplier", + [SD_DHCP_OPTION_ROUTER_DISCOVERY] = "router discovery", + [SD_DHCP_OPTION_ROUTER_REQUEST] = "router request", + [SD_DHCP_OPTION_STATIC_ROUTE] = "static route", + [SD_DHCP_OPTION_TRAILERS] = "trailers", + [SD_DHCP_OPTION_ARP_TIMEOUT] = "ARP timeout", + [SD_DHCP_OPTION_ETHERNET] = "Ethernet encapsulation", + [SD_DHCP_OPTION_DEFAULT_TCP_TTL] = "default TCP TTL", + [SD_DHCP_OPTION_KEEPALIVE_TIME] = "keepalive time", + [SD_DHCP_OPTION_KEEPALIVE_DATA] = "keepalive data", + [SD_DHCP_OPTION_NIS_DOMAIN] = "NIS domain", + [SD_DHCP_OPTION_NIS_SERVER] = "NIS server", + [SD_DHCP_OPTION_NTP_SERVER] = "NTP server", + [SD_DHCP_OPTION_VENDOR_SPECIFIC] = "vendor specific", + [SD_DHCP_OPTION_NETBIOS_NAME_SERVER] = "NETBIOS name server", + [SD_DHCP_OPTION_NETBIOS_DIST_SERVER] = "NETBIOS distribution server", + [SD_DHCP_OPTION_NETBIOS_NODE_TYPE] = "NETBIOS node type", + [SD_DHCP_OPTION_NETBIOS_SCOPE] = "NETBIOS scope", + [SD_DHCP_OPTION_X_WINDOW_FONT] = "X Window font", + [SD_DHCP_OPTION_X_WINDOW_MANAGER] = "X Window manager", + [SD_DHCP_OPTION_REQUESTED_IP_ADDRESS] = "requested IP address", + [SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME] = "lease time", + [SD_DHCP_OPTION_OVERLOAD] = "overload", + [SD_DHCP_OPTION_MESSAGE_TYPE] = "message type", + [SD_DHCP_OPTION_SERVER_IDENTIFIER] = "server identifier", + [SD_DHCP_OPTION_PARAMETER_REQUEST_LIST] = "parameter request list", + [SD_DHCP_OPTION_ERROR_MESSAGE] = "error message", + [SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE] = "max message size", + [SD_DHCP_OPTION_RENEWAL_TIME] = "renewal time", + [SD_DHCP_OPTION_REBINDING_TIME] = "rebinding time", + [SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER] = "vendor class identifier", + [SD_DHCP_OPTION_CLIENT_IDENTIFIER] = "client identifier", + [SD_DHCP_OPTION_NETWARE_IP_DOMAIN] = "NetWare IP domain", + [SD_DHCP_OPTION_NETWARE_IP_OPTION] = "NetWare IP option", + [SD_DHCP_OPTION_NIS_DOMAIN_NAME] = "NIS+ v3 domain", + [SD_DHCP_OPTION_NIS_SERVER_ADDR] = "NIS+ v3 server", + [SD_DHCP_OPTION_BOOT_SERVER_NAME] = "TFTP server name", + [SD_DHCP_OPTION_BOOT_FILENAME] = "boot file name", + [SD_DHCP_OPTION_HOME_AGENT_ADDRESS] = "home agent address", + [SD_DHCP_OPTION_SMTP_SERVER] = "SMTP server", + [SD_DHCP_OPTION_POP3_SERVER] = "POP3 server", + [SD_DHCP_OPTION_NNTP_SERVER] = "NNTP server", + [SD_DHCP_OPTION_WWW_SERVER] = "WWW server", + [SD_DHCP_OPTION_FINGER_SERVER] = "finger server", + [SD_DHCP_OPTION_IRC_SERVER] = "IRC server", + [SD_DHCP_OPTION_STREETTALK_SERVER] = "StreetTalk server", + [SD_DHCP_OPTION_STDA_SERVER] = "STDA server", + [SD_DHCP_OPTION_USER_CLASS] = "user class", + [SD_DHCP_OPTION_DIRECTORY_AGENT] = "directory agent", + [SD_DHCP_OPTION_SERVICE_SCOPE] = "service scope", + [SD_DHCP_OPTION_RAPID_COMMIT] = "rapid commit", + [SD_DHCP_OPTION_FQDN] = "FQDN", + [SD_DHCP_OPTION_RELAY_AGENT_INFORMATION] = "relay agent information", + [SD_DHCP_OPTION_ISNS] = "iSNS", + [SD_DHCP_OPTION_NDS_SERVER] = "NDS server", + [SD_DHCP_OPTION_NDS_TREE_NAME] = "NDS tree name", + [SD_DHCP_OPTION_NDS_CONTEXT] = "NDS context", + [SD_DHCP_OPTION_BCMCS_CONTROLLER_DOMAIN_NAME] = "BCMCS controller domain name", + [SD_DHCP_OPTION_BCMCS_CONTROLLER_ADDRESS] = "BCMCS controller address", + [SD_DHCP_OPTION_AUTHENTICATION] = "authentication", + [SD_DHCP_OPTION_CLIENT_LAST_TRANSACTION_TIME] = "client last transaction time", + [SD_DHCP_OPTION_ASSOCIATED_IP] = "associated IP", + [SD_DHCP_OPTION_CLIENT_SYSTEM] = "client system", + [SD_DHCP_OPTION_CLIENT_NDI] = "client NDI", + [SD_DHCP_OPTION_LDAP] = "LDAP", + [SD_DHCP_OPTION_UUID] = "UUID", + [SD_DHCP_OPTION_USER_AUTHENTICATION] = "user authentication", + [SD_DHCP_OPTION_GEOCONF_CIVIC] = "geoconf civic", + [SD_DHCP_OPTION_POSIX_TIMEZONE] = "posix timezone", + [SD_DHCP_OPTION_TZDB_TIMEZONE] = "tzdb timezone", + [SD_DHCP_OPTION_IPV6_ONLY_PREFERRED] = "IPv6-only preferred", + [SD_DHCP_OPTION_DHCP4O6_SOURCE_ADDRESS] = "DHCPv4 over DHCPv6 source address", + [SD_DHCP_OPTION_NETINFO_ADDRESS] = "Netinfo address", + [SD_DHCP_OPTION_NETINFO_TAG] = "Netinfo tag", + [SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL] = "captive portal", + [SD_DHCP_OPTION_AUTO_CONFIG] = "auto config", + [SD_DHCP_OPTION_NAME_SERVICE_SEARCH] = "name service search", + [SD_DHCP_OPTION_SUBNET_SELECTION] = "subnet selection", + [SD_DHCP_OPTION_DOMAIN_SEARCH] = "domain search", + [SD_DHCP_OPTION_SIP_SERVER] = "SIP server", + [SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE] = "classless static route", + [SD_DHCP_OPTION_CABLELABS_CLIENT_CONFIGURATION] = "CableLabs client configuration", + [SD_DHCP_OPTION_GEOCONF] = "geoconf", + [SD_DHCP_OPTION_VENDOR_CLASS] = "vendor class", + [SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION] = "vendor specific information", + [SD_DHCP_OPTION_PANA_AGENT] = "PANA agent", + [SD_DHCP_OPTION_LOST_SERVER_FQDN] = "LoST server", + [SD_DHCP_OPTION_CAPWAP_AC_ADDRESS] = "CAPWAP access controller address", + [SD_DHCP_OPTION_MOS_ADDRESS] = "MoS address", + [SD_DHCP_OPTION_MOS_FQDN] = "MoS FQDN", + [SD_DHCP_OPTION_SIP_SERVICE_DOMAIN] = "SIP service domain", + [SD_DHCP_OPTION_ANDSF_ADDRESS] = "ANDSF address", + [SD_DHCP_OPTION_SZTP_REDIRECT] = "SZTP server", + [SD_DHCP_OPTION_GEOLOC] = "geospatial location", + [SD_DHCP_OPTION_FORCERENEW_NONCE_CAPABLE] = "forcerenew nonce capable", + [SD_DHCP_OPTION_RDNSS_SELECTION] = "RDNSS selection", + [SD_DHCP_OPTION_DOTS_RI] = "DOTS agent name", + [SD_DHCP_OPTION_DOTS_ADDRESS] = "DOTS agent address", + [SD_DHCP_OPTION_TFTP_SERVER_ADDRESS] = "TFTP server address", + [SD_DHCP_OPTION_STATUS_CODE] = "status code", + [SD_DHCP_OPTION_BASE_TIME] = "base time", + [SD_DHCP_OPTION_START_TIME_OF_STATE] = "start time of state", + [SD_DHCP_OPTION_QUERY_START_TIME] = "query start time", + [SD_DHCP_OPTION_QUERY_END_TIME] = "query end time", + [SD_DHCP_OPTION_DHCP_STATE] = "DHCP state", + [SD_DHCP_OPTION_DATA_SOURCE] = "data source", + [SD_DHCP_OPTION_PCP_SERVER] = "PCP server", + [SD_DHCP_OPTION_PORT_PARAMS] = "port parameter", + [SD_DHCP_OPTION_MUD_URL] = "MUD URL", + [SD_DHCP_OPTION_V4_DNR] = "encrypted DNS server", + [SD_DHCP_OPTION_PXELINUX_MAGIC] = "PXELinux magic", + [SD_DHCP_OPTION_CONFIGURATION_FILE] = "configuration file", + [SD_DHCP_OPTION_PATH_PREFIX] = "path prefix", + [SD_DHCP_OPTION_REBOOT_TIME] = "reboot time", + [SD_DHCP_OPTION_6RD] = "6rd", + [SD_DHCP_OPTION_ACCESS_DOMAIN] = "access network domain", + [SD_DHCP_OPTION_SUBNET_ALLOCATION] = "subnet allocation", + [SD_DHCP_OPTION_VIRTUAL_SUBNET_SELECTION] = "virtual subnet selection", + [SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE] = "(private) classless static route", + [SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY] = "(private) proxy autodiscovery", + [SD_DHCP_OPTION_END] = "end", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_option_code, int); diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index 425f730894d2b..c2df5a574a89e 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -8,7 +8,7 @@ #include #include -#include "sd-dhcp-protocol.h" +#include "sd-dhcp-protocol.h" /* IWYU pragma: export */ #include "sd-forward.h" #include "sparse-endian.h" @@ -35,12 +35,19 @@ uint8_t file[128]; \ be32_t magic; +struct DHCPMessageHeader { + DHCP_MESSAGE_HEADER_DEFINITION; +} _packed_; + +typedef struct DHCPMessageHeader DHCPMessageHeader; + struct DHCPMessage { DHCP_MESSAGE_HEADER_DEFINITION; uint8_t options[]; } _packed_; typedef struct DHCPMessage DHCPMessage; +assert_cc(sizeof(DHCPMessageHeader) == offsetof(DHCPMessage, options)); struct DHCPPacket { struct iphdr ip; @@ -58,17 +65,25 @@ typedef struct DHCPPacket DHCPPacket; #define DHCP_MIN_PACKET_SIZE (DHCP_MIN_MESSAGE_SIZE + DHCP_IP_UDP_SIZE) #define DHCP_MAGIC_COOKIE (uint32_t)(0x63825363) +/* The size of BOOTP message. The BOOTP message does not have the magic field, but has the 64-byte + * vendor-specific area. */ +#define BOOTP_MESSAGE_SIZE (offsetof(DHCPMessageHeader, magic) + 64) + enum { DHCP_PORT_SERVER = 67, DHCP_PORT_CLIENT = 68, }; -enum { +typedef enum { BOOTREQUEST = 1, BOOTREPLY = 2, -}; + _BOOTP_MESSAGE_TYPE_MAX, + _BOOTP_MESSAGE_TYPE_INVALID = -EINVAL, +} BOOTPMessageType; -enum { +DECLARE_STRING_TABLE_LOOKUP_TO_STRING(bootp_message_type, BOOTPMessageType); + +typedef enum { DHCP_DISCOVER = 1, /* [RFC2132] */ DHCP_OFFER = 2, /* [RFC2132] */ DHCP_REQUEST = 3, /* [RFC2132] */ @@ -78,21 +93,27 @@ enum { DHCP_RELEASE = 7, /* [RFC2132] */ DHCP_INFORM = 8, /* [RFC2132] */ DHCP_FORCERENEW = 9, /* [RFC3203] */ - DHCPLEASEQUERY = 10, /* [RFC4388] */ - DHCPLEASEUNASSIGNED = 11, /* [RFC4388] */ - DHCPLEASEUNKNOWN = 12, /* [RFC4388] */ - DHCPLEASEACTIVE = 13, /* [RFC4388] */ - DHCPBULKLEASEQUERY = 14, /* [RFC6926] */ - DHCPLEASEQUERYDONE = 15, /* [RFC6926] */ - DHCPACTIVELEASEQUERY = 16, /* [RFC7724] */ - DHCPLEASEQUERYSTATUS = 17, /* [RFC7724] */ - DHCPTLS = 18, /* [RFC7724] */ -}; - -enum { - DHCP_OVERLOAD_FILE = 1, - DHCP_OVERLOAD_SNAME = 2, -}; + DHCP_LEASEQUERY = 10, /* [RFC4388] */ + DHCP_LEASEUNASSIGNED = 11, /* [RFC4388] */ + DHCP_LEASEUNKNOWN = 12, /* [RFC4388] */ + DHCP_LEASEACTIVE = 13, /* [RFC4388] */ + DHCP_BULKLEASEQUERY = 14, /* [RFC6926] */ + DHCP_LEASEQUERYDONE = 15, /* [RFC6926] */ + DHCP_ACTIVELEASEQUERY = 16, /* [RFC7724] */ + DHCP_LEASEQUERYSTATUS = 17, /* [RFC7724] */ + DHCP_TLS = 18, /* [RFC7724] */ + _DHCP_MESSAGE_TYPE_MAX, + _DHCP_MESSAGE_TYPE_INVALID = -EINVAL, +} DHCPMessageType; + +DECLARE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_message_type, DHCPMessageType); + +typedef enum { + DHCP_OVERLOAD_NONE = 0, + DHCP_OVERLOAD_FILE = 1 << 0, + DHCP_OVERLOAD_SNAME = 1 << 1, + _DHCP_OVERLOAD_ALL = DHCP_OVERLOAD_FILE | DHCP_OVERLOAD_SNAME, +} DHCPOptionOverload; #define DHCP_MAX_FQDN_LENGTH 255 @@ -102,3 +123,5 @@ enum { DHCP_FQDN_FLAG_E = (1 << 2), DHCP_FQDN_FLAG_N = (1 << 3), }; + +DECLARE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_option_code, int); diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index 751aed78a7f02..d62fd70588923 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -250,6 +250,8 @@ int dhcp6_option_append( int r; + assert(buf); + assert(offset); assert(optval || optlen == 0); r = option_append_hdr(buf, offset, code, optlen); @@ -546,7 +548,7 @@ int dhcp6_option_parse_string(const uint8_t *data, size_t data_len, char **ret) return 0; } - r = make_cstring((const char *) data, data_len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &string); + r = make_cstring(data, data_len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &string); if (r < 0) return r; diff --git a/src/libsystemd-network/dhcp6-protocol.c b/src/libsystemd-network/dhcp6-protocol.c index be0f651f1ab4a..2633a23861ced 100644 --- a/src/libsystemd-network/dhcp6-protocol.c +++ b/src/libsystemd-network/dhcp6-protocol.c @@ -52,6 +52,8 @@ static const char * const dhcp6_message_type_table[_DHCP6_MESSAGE_TYPE_MAX] = { [DHCP6_MESSAGE_DISCONNECT] = "Disconnect", [DHCP6_MESSAGE_STATE] = "State", [DHCP6_MESSAGE_CONTACT] = "Contact", + [DHCP6_MESSAGE_ADDR_REG_INFORM] = "Address Registration Inform", + [DHCP6_MESSAGE_ADDR_REG_REPLY] = "Address Registration Reply", }; DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, DHCP6MessageType); diff --git a/src/libsystemd-network/dns-resolver-internal.h b/src/libsystemd-network/dns-resolver-internal.h index c8221421d1e6d..c9b3103cdb911 100644 --- a/src/libsystemd-network/dns-resolver-internal.h +++ b/src/libsystemd-network/dns-resolver-internal.h @@ -34,4 +34,4 @@ int dns_resolvers_to_dot_strv(const sd_dns_resolver *resolvers, size_t n_resolve void sd_dns_resolver_done(sd_dns_resolver *res); -void dns_resolver_done_many(sd_dns_resolver *resolvers, size_t n); +void dns_resolver_free_array(sd_dns_resolver *array, size_t n); diff --git a/src/libsystemd-network/fuzz-dhcp-client.c b/src/libsystemd-network/fuzz-dhcp-client.c index 23471f89fcedd..21f693c4873a3 100644 --- a/src/libsystemd-network/fuzz-dhcp-client.c +++ b/src/libsystemd-network/fuzz-dhcp-client.c @@ -3,9 +3,11 @@ #include #include "dhcp-network.h" +#include "fd-util.h" #include "fuzz.h" #include "network-internal.h" #include "sd-dhcp-client.c" +#include "tests.h" #include "tmpfile-util.h" int dhcp_network_bind_raw_socket( @@ -19,77 +21,56 @@ int dhcp_network_bind_raw_socket( bool so_priority_set, int so_priority) { - int fd; - fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - return fd; + return ASSERT_OK_ERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); } int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const void *packet, size_t len) { - return len; + return 0; } int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) { - int fd; - - fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - return fd; + return ASSERT_OK_ERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); } int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, const void *packet, size_t len) { - return len; + return 0; } int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - uint8_t mac_addr[] = {'A', 'B', 'C', '1', '2', '3'}; - uint8_t bcast_addr[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; - _cleanup_(unlink_tempfilep) char lease_file[] = "/tmp/fuzz-dhcp-client.XXXXXX"; - _cleanup_close_ int fd = -1; - int res, r; + static const uint8_t mac_addr[] = {'A', 'B', 'C', '1', '2', '3'}; + static const uint8_t bcast_addr[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - assert_se(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", /* overwrite= */ true)); fuzz_setup_logging(); - r = sd_dhcp_client_new(&client, false); - assert_se(r >= 0); - assert_se(client); + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_NOT_NULL(e); - assert_se(sd_event_new(&e) >= 0); + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ false)); + ASSERT_NOT_NULL(client); - r = sd_dhcp_client_attach_event(client, e, 0); - assert_se(r >= 0); + ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 0)); - assert_se(sd_dhcp_client_set_ifindex(client, 42) >= 0); - assert_se(sd_dhcp_client_set_mac(client, mac_addr, bcast_addr, ETH_ALEN, ARPHRD_ETHER) >= 0); + ASSERT_OK(sd_dhcp_client_set_ifindex(client, 42)); + ASSERT_OK(sd_dhcp_client_set_mac(client, mac_addr, bcast_addr, ETH_ALEN, ARPHRD_ETHER)); - res = sd_dhcp_client_start(client); - assert_se(IN_SET(res, 0, -EINPROGRESS)); + ASSERT_OK(sd_dhcp_client_start(client)); client->xid = 2; client->state = DHCP_STATE_SELECTING; - if (client_handle_offer_or_rapid_ack(client, (DHCPMessage*) data, size, NULL) < 0) - goto end; - - fd = mkostemp_safe(lease_file); - assert_se(fd >= 0); - - r = dhcp_lease_save(client->lease, lease_file); - assert_se(r >= 0); + if (client_handle_offer_or_rapid_ack(client, (DHCPMessage*) data, size, NULL) >= 0) { + _cleanup_(unlink_tempfilep) char lease_file[] = "/tmp/fuzz-dhcp-client.XXXXXX"; + _unused_ _cleanup_close_ int fd = ASSERT_OK(mkostemp_safe(lease_file)); - r = dhcp_lease_load(&lease, lease_file); - assert_se(r >= 0); + ASSERT_OK(dhcp_lease_save(client->lease, lease_file)); -end: - assert_se(sd_dhcp_client_stop(client) >= 0); + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + ASSERT_OK(dhcp_lease_load(&lease, lease_file)); + } + ASSERT_OK(sd_dhcp_client_stop(client)); return 0; } diff --git a/src/libsystemd-network/ip-util.c b/src/libsystemd-network/ip-util.c new file mode 100644 index 0000000000000..3f062a7ef004e --- /dev/null +++ b/src/libsystemd-network/ip-util.c @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "ip-util.h" +#include "log.h" + +union iphdr_union { + struct iphdr ip; + uint8_t buf[15 * 4]; /* ip->ihl is 4 bits, hence max length is 15 * 4 */ +}; + +struct udp_pseudo_header { + be32_t saddr; + be32_t daddr; + uint8_t unused; + uint8_t protocol; + be16_t len; +} _packed_; + +static uint64_t complement_sum(uint64_t a, uint64_t b) { + /* This performs one's complement addition (end-around carry). See RFC1071. */ + if (a <= UINT64_MAX - b) + return a + b; + + return a - (UINT64_MAX - b); +} + +static uint64_t checksum_iov(uint64_t sum, const struct iovec *iov) { + assert(iov); + + for (struct iovec i = *iov; iovec_is_set(&i); iovec_inc(&i, sizeof(uint64_t))) { + uint64_t t = 0; + memcpy(&t, i.iov_base, MIN(i.iov_len, sizeof(uint64_t))); + sum = complement_sum(sum, t); + } + + return sum; +} + +static uint16_t checksum_finalize(uint64_t sum) { + while ((sum >> 16) != 0) + sum = (sum & 0xffffu) + (sum >> 16); + + return ~sum; +} + +uint16_t ip_checksum(const void *buf, size_t len) { + /* See RFC1071 */ + return checksum_finalize(checksum_iov(0, &IOVEC_MAKE(buf, len))); +} + +static uint16_t iphdr_checksum(const union iphdr_union *ip) { + assert(ip); + return ip_checksum(ip, ip->ip.ihl * 4); +} + +static uint16_t udphdr_checksum( + be32_t saddr, + be32_t daddr, + const struct udphdr *udp, + const struct iovec_wrapper *payload) { + + assert(udp); + assert(payload); + + /* RFC 768 */ + + struct udp_pseudo_header pseudo = { + .saddr = saddr, + .daddr = daddr, + .protocol = IPPROTO_UDP, + .len = udp->len, + }; + + uint64_t sum = 0; + sum = checksum_iov(sum, &IOVEC_MAKE(&pseudo, sizeof(struct udp_pseudo_header))); + sum = checksum_iov(sum, &IOVEC_MAKE(udp, sizeof(struct udphdr))); + + uint8_t buf[2] = {}; + bool odd = false; + FOREACH_ARRAY(i, payload->iovec, payload->count) { + if (!iovec_is_set(i)) + continue; + + struct iovec v = *i; + if (odd) { + buf[1] = *(uint8_t*) v.iov_base; + sum = checksum_iov(sum, &IOVEC_MAKE(buf, 2)); + iovec_inc(&v, 1); + } + + odd = v.iov_len % 2; + if (odd) { + buf[0] = ((uint8_t*) v.iov_base)[v.iov_len - 1]; + v.iov_len--; + } + sum = checksum_iov(sum, &v); + } + if (odd) { + buf[1] = 0; + sum = checksum_iov(sum, &IOVEC_MAKE(buf, 2)); + } + + return checksum_finalize(sum); +} + +int udp_packet_build( + be32_t source_addr, + uint16_t source_port, + be32_t destination_addr, + uint16_t destination_port, + int ip_service_type, + const struct iovec_wrapper *payload, + struct iphdr *ret_iphdr, + struct udphdr *ret_udphdr) { + + assert(payload); + assert(ret_iphdr); + assert(ret_udphdr); + + /* When ip_service_type is negative, IPTOS_CLASS_CS6 will be used. Otherwise, it must be a valid TOS, + * hence must be in 0…255. Here, we only check its range. */ + if (ip_service_type > UINT8_MAX) + return -EINVAL; + + /* iphdr.tot_len is uint16_t, hence the total length must be <= UINT16_MAX. */ + size_t len = iovw_size(payload); + if (len > UDP_PAYLOAD_MAX_SIZE) + return -E2BIG; + + union iphdr_union ip = { + .ip.version = IPVERSION, + .ip.ihl = sizeof(struct iphdr) / 4, + .ip.tos = ip_service_type >= 0 ? ip_service_type : IPTOS_CLASS_CS6, + .ip.tot_len = htobe16(sizeof(struct iphdr) + sizeof(struct udphdr) + len), + .ip.ttl = IPDEFTTL, + .ip.protocol = IPPROTO_UDP, + .ip.saddr = source_addr, + .ip.daddr = destination_addr, + }; + + ip.ip.check = iphdr_checksum(&ip); + + struct udphdr udp = { + .source = htobe16(source_port), + .dest = htobe16(destination_port), + .len = htobe16(sizeof(struct udphdr) + len), + }; + + udp.check = udphdr_checksum(source_addr, destination_addr, &udp, payload); + + *ret_iphdr = ip.ip; + *ret_udphdr = udp; + return 0; +} + +int udp_packet_verify( + const struct iovec *packet, + uint16_t port, + bool checksum, + struct iovec *ret_payload) { + + assert(packet); + + /* This verifies IP and UDP packet headers and optionally returns the UDP payload. */ + + /* IP */ + if (packet->iov_len < sizeof(struct iphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet (%zu bytes) smaller than minimum IP header (%zu bytes), ignoring packet.", + packet->iov_len, sizeof(struct iphdr)); + + const union iphdr_union *ip = (const union iphdr_union*) packet->iov_base; + if (ip->ip.version != IPVERSION) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet is not IPv4, ignoring packet."); + + size_t iphdrlen = ip->ip.ihl * 4; + if (iphdrlen < sizeof(struct iphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: IP header size (%zu bytes) smaller than minimum (%zu bytes), ignoring packet.", + iphdrlen, sizeof(struct iphdr)); + + if (packet->iov_len < iphdrlen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet (%zu bytes) smaller than IP header size (%zu bytes), ignoring packet.", + packet->iov_len, iphdrlen); + + size_t totlen = be16toh(ip->ip.tot_len); + if (totlen < iphdrlen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet size (%zu bytes) by IP header is smaller than the IP header size (%zu), ignoring packet.", + totlen, iphdrlen); + if (packet->iov_len < totlen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet (%zu bytes) smaller than expected (%zu) by IP header, ignoring packet.", + packet->iov_len, totlen); + + if (ip->ip.protocol != IPPROTO_UDP) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: not UDP, ignoring packet."); + + if (iphdr_checksum(ip) != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: invalid IP checksum, ignoring packet."); + + /* UDP */ + if (totlen < iphdrlen + sizeof(struct udphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: packet (%zu bytes) smaller than IP header + UDP header, ignoring packet.", + totlen); + + const struct udphdr *udp = (const struct udphdr*) ((const uint8_t*) packet->iov_base + iphdrlen); + size_t udplen = be16toh(udp->len); + if (udplen < sizeof(struct udphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: UDP datagram (%zu bytes) smaller than UDP header (%zu bytes), ignoring packet.", + udplen, sizeof(struct udphdr)); + + if (totlen != iphdrlen + udplen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: packet length by IP header (%zu bytes) does not match with the one by UDP header " + "(IP header %zu bytes + UDP %zu bytes = %zu bytes), ignoring packet.", + totlen, iphdrlen, udplen, iphdrlen + udplen); + + if (be16toh(udp->dest) != port) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: to port %u, which is not the expected port (%u), ignoring packet.", + be16toh(udp->dest), port); + + /* Calculate the UDP payload length from the UDP header (udplen), rather than the input packet length + * (len). The packet may contain garbage at the end. */ + struct iovec payload = IOVEC_MAKE( + (const uint8_t*) packet->iov_base + iphdrlen + sizeof(struct udphdr), + udplen - sizeof(struct udphdr)); + if (checksum && udp->check != 0 && + udphdr_checksum(ip->ip.saddr, ip->ip.daddr, udp, + &(struct iovec_wrapper) { + .iovec = &payload, + .count = 1, + }) != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: invalid UDP checksum, ignoring packet."); + + if (ret_payload) + *ret_payload = payload; + return 0; +} diff --git a/src/libsystemd-network/ip-util.h b/src/libsystemd-network/ip-util.h new file mode 100644 index 0000000000000..31fa4631c35f9 --- /dev/null +++ b/src/libsystemd-network/ip-util.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +#include "sd-forward.h" + +#include "sparse-endian.h" + +/* RFC 791 + * Fragmentation and Reassembly. + * Every internet destination must be able to receive a datagram of 576 octets either in one piece or in + * fragments to be reassembled. */ +#define IPV4_MIN_REASSEMBLY_SIZE 576u + +/* This is a maximal UDP payload size in a packet when its IP header does not contain options. When a packet + * contains some IP options, then of course the allowed UDP payload size in the packet becomes smaller. */ +#define UDP_PAYLOAD_MAX_SIZE (UINT16_MAX - sizeof(struct iphdr) - sizeof(struct udphdr)) + +uint16_t ip_checksum(const void *buf, size_t len); + +int udp_packet_build( + be32_t source_addr, + uint16_t source_port, + be32_t destination_addr, + uint16_t destination_port, + int ip_service_type, + const struct iovec_wrapper *payload, + struct iphdr *ret_iphdr, + struct udphdr *ret_udphdr); + +int udp_packet_verify( + const struct iovec *packet, + uint16_t port, + bool checksum, + struct iovec *ret_payload); diff --git a/src/libsystemd-network/lldp-neighbor.c b/src/libsystemd-network/lldp-neighbor.c index 727e8feb3319f..487bd50182c32 100644 --- a/src/libsystemd-network/lldp-neighbor.c +++ b/src/libsystemd-network/lldp-neighbor.c @@ -408,6 +408,7 @@ static int format_mac_address(const void *data, size_t sz, char **ret) { char *k; assert(data || sz <= 0); + assert(ret); if (sz != 7) return 0; diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index e0012abe0bcf3..b0443c3695206 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -2,14 +2,17 @@ libsystemd_network_sources = files( 'arp-util.c', + 'dhcp-client-send.c', 'dhcp-network.c', 'dhcp-option.c', 'dhcp-packet.c', + 'dhcp-protocol.c', 'dhcp6-network.c', 'dhcp6-option.c', 'dhcp6-protocol.c', 'icmp6-packet.c', 'icmp6-util.c', + 'ip-util.c', 'lldp-neighbor.c', 'lldp-network.c', 'ndisc-option.c', @@ -82,6 +85,9 @@ executables += [ network_test_template + { 'sources' : files('test-dhcp6-client.c'), }, + network_test_template + { + 'sources' : files('test-ip-util.c'), + }, network_test_template + { 'sources' : files('test-ipv4ll-manual.c'), 'type' : 'manual', diff --git a/src/libsystemd-network/ndisc-option.c b/src/libsystemd-network/ndisc-option.c index 0104515cb0c9e..604ef57cf1111 100644 --- a/src/libsystemd-network/ndisc-option.c +++ b/src/libsystemd-network/ndisc-option.c @@ -1376,7 +1376,7 @@ static int ndisc_option_parse_encrypted_dns(Set **options, size_t offset, size_t union in_addr_union addr; memcpy(&addr.in6, opt + off, sizeof(struct in6_addr)); if (in_addr_is_multicast(AF_INET6, &addr) || - in_addr_is_localhost(AF_INET, &addr)) + in_addr_is_localhost(AF_INET6, &addr)) return -EBADMSG; res.addrs[i] = addr; off += sizeof(struct in6_addr); diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index c8aa6b63e0861..c1eaa361afa0d 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -169,7 +169,7 @@ int deserialize_dnr(sd_dns_resolver **ret, const char *string) { sd_dns_resolver *dnr = NULL; size_t n = 0; - CLEANUP_ARRAY(dnr, n, dns_resolver_done_many); + CLEANUP_ARRAY(dnr, n, dns_resolver_free_array); int priority = 0; for (;;) { diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 8c373146f6185..9742ab833bd5b 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -7,28 +7,22 @@ #include #include -#include "sd-dhcp-client.h" - #include "alloc-util.h" #include "device-util.h" -#include "dhcp-client-id-internal.h" #include "dhcp-client-internal.h" +#include "dhcp-client-send.h" #include "dhcp-lease-internal.h" -#include "dhcp-network.h" #include "dhcp-option.h" #include "dhcp-packet.h" #include "dns-domain.h" #include "errno-util.h" -#include "ether-addr-util.h" #include "event-util.h" -#include "fd-util.h" #include "hostname-util.h" #include "iovec-util.h" #include "memory-util.h" #include "network-common.h" #include "random-util.h" #include "set.h" -#include "socket-util.h" #include "sort-util.h" #include "string-table.h" #include "string-util.h" @@ -41,71 +35,11 @@ #define RESTART_AFTER_NAK_MIN_USEC (1 * USEC_PER_SEC) #define RESTART_AFTER_NAK_MAX_USEC (30 * USEC_PER_MINUTE) +#define MAX_REQUEST_ATTEMPTS_ON_REBOOTING 2 +#define MAX_REQUEST_ATTEMPTS 5 #define TRANSIENT_FAILURE_ATTEMPTS 3 /* Arbitrary limit: how many attempts are considered enough to report * transient failure. */ -struct sd_dhcp_client { - unsigned n_ref; - - DHCPState state; - sd_event *event; - int event_priority; - sd_event_source *timeout_resend; - - int ifindex; - char *ifname; - - sd_device *dev; - - int fd; - uint16_t port; - uint16_t server_port; - union sockaddr_union link; - sd_event_source *receive_message; - bool request_broadcast; - Set *req_opts; - bool anonymize; - bool rapid_commit; - be32_t last_addr; - struct hw_addr_data hw_addr; - struct hw_addr_data bcast_addr; - uint16_t arp_type; - sd_dhcp_client_id client_id; - char *hostname; - char *vendor_class_identifier; - char *mudurl; - char **user_class; - uint32_t mtu; - usec_t fallback_lease_lifetime; - uint32_t xid; - usec_t start_time; - usec_t t1_time; - usec_t t2_time; - usec_t expire_time; - uint64_t discover_attempt; - uint64_t request_attempt; - uint64_t max_discover_attempts; - uint64_t max_request_attempts; - OrderedHashmap *extra_options; - OrderedHashmap *vendor_options; - sd_event_source *timeout_t1; - sd_event_source *timeout_t2; - sd_event_source *timeout_expire; - sd_event_source *timeout_ipv6_only_mode; - sd_dhcp_client_callback_t callback; - void *userdata; - sd_dhcp_client_callback_t state_callback; - void *state_userdata; - sd_dhcp_lease *lease; - usec_t start_delay; - int ip_service_type; - int socket_priority; - bool socket_priority_set; - bool ipv6_acquired; - bool bootp; - bool send_release; -}; - static const uint8_t default_req_opts[] = { SD_DHCP_OPTION_SUBNET_MASK, SD_DHCP_OPTION_ROUTER, @@ -140,16 +74,6 @@ static const uint8_t default_req_opts_anonymize[] = { SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY, /* 252 */ }; -static int client_receive_message_raw( - sd_event_source *s, - int fd, - uint32_t revents, - void *userdata); -static int client_receive_message_udp( - sd_event_source *s, - int fd, - uint32_t revents, - void *userdata); static void client_stop(sd_dhcp_client *client, int error); static int client_restart(sd_dhcp_client *client); @@ -683,6 +607,30 @@ static void client_set_state(sd_dhcp_client *client, DHCPState state) { client->state = state; + switch (state) { + case DHCP_STATE_STOPPED: + case DHCP_STATE_BOUND: + /* In these cases, the next DHCPDISCOVER message will be sent in a new cycle. + * Hence, clear the counter for DHCPDISCOVER messages. */ + client->discover_attempt = 0; + break; + + case DHCP_STATE_REBOOTING: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + /* In these cases, the next DHCPREQUEST message will be the first message in this new state. + * Hence, clear the counter for DHCPREQUEST messages. */ + client->request_attempt = 0; + break; + + default: + /* otherwise, do not reset the counters. */ + ; + } + + // FIXME: If the state callback changes the state, we may not safely free/stop the client, and the + // state machine diagram becomes needlessly complicated. Introduce a guard to avoid that. */ if (client->state_callback) client->state_callback(client, state, client->state_userdata); } @@ -702,28 +650,26 @@ static int client_notify(sd_dhcp_client *client, int event) { return 0; } -static int client_initialize(sd_dhcp_client *client) { - assert_return(client, -EINVAL); +static void client_disable_event_sources(sd_dhcp_client *client) { + assert(client); client->receive_message = sd_event_source_disable_unref(client->receive_message); - client->fd = safe_close(client->fd); - (void) event_source_disable(client->timeout_resend); (void) event_source_disable(client->timeout_t1); (void) event_source_disable(client->timeout_t2); (void) event_source_disable(client->timeout_expire); - (void) event_source_disable(client->timeout_ipv6_only_mode); +} - client->discover_attempt = 0; - client->request_attempt = 0; +static void client_initialize(sd_dhcp_client *client) { + assert(client); + + client_disable_event_sources(client); client_set_state(client, DHCP_STATE_STOPPED); client->xid = 0; client->lease = sd_dhcp_lease_unref(client->lease); - - return 0; } static void client_stop(sd_dhcp_client *client, int error) { @@ -778,6 +724,9 @@ static usec_t client_compute_reacquisition_timeout(usec_t now_usec, usec_t expir } static int cmp_uint8(const uint8_t *a, const uint8_t *b) { + assert(a); + assert(b); + return CMP(*a, *b); } @@ -876,7 +825,7 @@ static int client_message_init( MAY contain the Parameter Request List option. */ /* NOTE: in case that there would be an option to do not send * any PRL at all, the size should be checked before sending */ - if (!set_isempty(client->req_opts) && type != DHCP_RELEASE) { + if (!set_isempty(client->req_opts) && IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST)) { _cleanup_free_ uint8_t *opts = NULL; size_t n_opts, i = 0; void *val; @@ -962,18 +911,6 @@ static int client_append_fqdn_option( return r; } -static int dhcp_client_send_raw( - sd_dhcp_client *client, - DHCPPacket *packet, - size_t len) { - - dhcp_packet_append_ip_headers(packet, INADDR_ANY, client->port, - INADDR_BROADCAST, client->server_port, len, client->ip_service_type); - - return dhcp_network_send_raw_socket(client->fd, &client->link, - packet, len); -} - static int client_append_common_discover_request_options(sd_dhcp_client *client, DHCPPacket *packet, size_t *optoffset, size_t optlen) { sd_dhcp_option *j; int r; @@ -1020,8 +957,7 @@ static int client_append_common_discover_request_options(sd_dhcp_client *client, if (client->user_class) { r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, SD_DHCP_OPTION_USER_CLASS, - strv_length(client->user_class), - client->user_class); + /* optlen= */ 0, client->user_class); if (r < 0) return r; } @@ -1037,7 +973,7 @@ static int client_append_common_discover_request_options(sd_dhcp_client *client, r = dhcp_option_append( &packet->dhcp, optlen, optoffset, 0, SD_DHCP_OPTION_VENDOR_SPECIFIC, - ordered_hashmap_size(client->vendor_options), client->vendor_options); + /* optlen= */ 0, client->vendor_options); if (r < 0) return r; } @@ -1051,7 +987,6 @@ static int client_send_dhcp_discover(sd_dhcp_client *client) { int r; assert(client); - assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING)); r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset); if (r < 0) @@ -1089,12 +1024,11 @@ static int client_send_dhcp_discover(sd_dhcp_client *client) { if (r < 0) return r; - r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset); + r = dhcp_client_send_raw(client, /* expect_reply= */ true, discover, optoffset); if (r < 0) return r; log_dhcp_client(client, "DISCOVER"); - return 0; } @@ -1104,7 +1038,6 @@ static int client_send_bootp_discover(sd_dhcp_client *client) { int r; assert(client); - assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING)); r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset); if (r < 0) @@ -1127,7 +1060,7 @@ static int client_send_bootp_discover(sd_dhcp_client *client) { optoffset = 60; } - r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset); + r = dhcp_client_send_raw(client, /* expect_reply= */ true, discover, optoffset); if (r < 0) return r; @@ -1135,6 +1068,15 @@ static int client_send_bootp_discover(sd_dhcp_client *client) { return 0; } +static int client_send_discover(sd_dhcp_client *client) { + assert(client); + assert(client->state == DHCP_STATE_SELECTING); + + return client->bootp ? + client_send_bootp_discover(client) : + client_send_dhcp_discover(client); +} + static int client_send_request(sd_dhcp_client *client) { _cleanup_free_ DHCPPacket *request = NULL; size_t optoffset, optlen; @@ -1169,10 +1111,9 @@ static int client_send_request(sd_dhcp_client *client) { 4, &client->lease->address); if (r < 0) return r; - break; - case DHCP_STATE_INIT_REBOOT: + case DHCP_STATE_REBOOTING: /* ’server identifier’ MUST NOT be filled in, ’requested IP address’ option MUST be filled in with client’s notion of its previously assigned address. ’ciaddr’ MUST be zero. @@ -1198,16 +1139,10 @@ static int client_send_request(sd_dhcp_client *client) { This message MUST be broadcast to the 0xffffffff IP broadcast address. */ request->dhcp.ciaddr = client->lease->address; - break; - case DHCP_STATE_INIT: - case DHCP_STATE_SELECTING: - case DHCP_STATE_REBOOTING: - case DHCP_STATE_BOUND: - case DHCP_STATE_STOPPED: default: - return -EINVAL; + assert_not_reached(); } r = client_append_common_discover_request_options(client, request, &optoffset, optlen); @@ -1220,13 +1155,9 @@ static int client_send_request(sd_dhcp_client *client) { return r; if (client->state == DHCP_STATE_RENEWING) - r = dhcp_network_send_udp_socket(client->fd, - client->lease->server_address, - client->server_port, - &request->dhcp, - sizeof(DHCPMessage) + optoffset); + r = dhcp_client_send_udp(client, /* expect_reply= */ true, request, optoffset); else - r = dhcp_client_send_raw(client, request, sizeof(DHCPPacket) + optoffset); + r = dhcp_client_send_raw(client, /* expect_reply= */ true, request, optoffset); if (r < 0) return r; @@ -1236,8 +1167,8 @@ static int client_send_request(sd_dhcp_client *client) { log_dhcp_client(client, "REQUEST (requesting)"); break; - case DHCP_STATE_INIT_REBOOT: - log_dhcp_client(client, "REQUEST (init-reboot)"); + case DHCP_STATE_REBOOTING: + log_dhcp_client(client, "REQUEST (rebooting)"); break; case DHCP_STATE_RENEWING: @@ -1249,14 +1180,12 @@ static int client_send_request(sd_dhcp_client *client) { break; default: - log_dhcp_client(client, "REQUEST (invalid)"); + assert_not_reached(); } return 0; } -static int client_start(sd_dhcp_client *client); - static int client_timeout_resend( sd_event_source *s, uint64_t usec, @@ -1284,41 +1213,43 @@ static int client_timeout_resend( next_timeout = client_compute_reacquisition_timeout(time_now, client->expire_time); break; - case DHCP_STATE_REBOOTING: - /* start over as we did not receive a timely ack or nak */ - r = client_initialize(client); - if (r < 0) - goto error; - - r = client_start(client); - if (r < 0) - goto error; - - log_dhcp_client(client, "REBOOTED"); - return 0; - case DHCP_STATE_INIT: - case DHCP_STATE_INIT_REBOOT: + client_set_state(client, DHCP_STATE_SELECTING); + _fallthrough_; + case DHCP_STATE_SELECTING: - if (client->discover_attempt >= client->max_discover_attempts) + if (client->discover_attempt >= client->max_discover_attempts) { + r = -ETIMEDOUT; goto error; + } client->discover_attempt++; next_timeout = client_compute_request_timeout(client->discover_attempt); break; + + case DHCP_STATE_INIT_REBOOT: + client_set_state(client, DHCP_STATE_REBOOTING); + _fallthrough_; + + case DHCP_STATE_REBOOTING: + /* There is nothing explicitly mentioned about retry interval on reboot. Let's reuse the same + * algorithm as in the requesting state below, but slightly speed up for faster reboot. */ + + if (client->request_attempt >= MAX_REQUEST_ATTEMPTS_ON_REBOOTING) + goto restart; + + client->request_attempt++; + next_timeout = client_compute_request_timeout(client->request_attempt) / 4; + break; + case DHCP_STATE_REQUESTING: - case DHCP_STATE_BOUND: - if (client->request_attempt >= client->max_request_attempts) - goto error; + if (client->request_attempt >= MAX_REQUEST_ATTEMPTS) + goto restart; client->request_attempt++; next_timeout = client_compute_request_timeout(client->request_attempt); break; - case DHCP_STATE_STOPPED: - r = -EINVAL; - goto error; - default: assert_not_reached(); } @@ -1332,63 +1263,45 @@ static int client_timeout_resend( goto error; switch (client->state) { - case DHCP_STATE_INIT: - if (client->bootp) - r = client_send_bootp_discover(client); - else - r = client_send_dhcp_discover(client); - if (r >= 0) { - client_set_state(client, DHCP_STATE_SELECTING); - client->discover_attempt = 0; - } else if (client->discover_attempt >= client->max_discover_attempts) - goto error; - break; - case DHCP_STATE_SELECTING: - if (client->bootp) - r = client_send_bootp_discover(client); - else - r = client_send_dhcp_discover(client); + r = client_send_discover(client); if (r < 0 && client->discover_attempt >= client->max_discover_attempts) goto error; + + if (client->discover_attempt >= TRANSIENT_FAILURE_ATTEMPTS) + client_notify(client, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE); + break; + + case DHCP_STATE_REBOOTING: + r = client_send_request(client); + if (r < 0 && client->request_attempt >= MAX_REQUEST_ATTEMPTS_ON_REBOOTING) + goto restart; break; - case DHCP_STATE_INIT_REBOOT: case DHCP_STATE_REQUESTING: case DHCP_STATE_RENEWING: case DHCP_STATE_REBINDING: r = client_send_request(client); - if (r < 0 && client->request_attempt >= client->max_request_attempts) - goto error; - - if (client->state == DHCP_STATE_INIT_REBOOT) - client_set_state(client, DHCP_STATE_REBOOTING); - break; - - case DHCP_STATE_REBOOTING: - case DHCP_STATE_BOUND: + if (r < 0 && client->request_attempt >= MAX_REQUEST_ATTEMPTS) + goto restart; break; - case DHCP_STATE_STOPPED: default: - r = -EINVAL; - goto error; + assert_not_reached(); } - if (client->discover_attempt >= TRANSIENT_FAILURE_ATTEMPTS) - client_notify(client, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE); - return 0; -error: +restart: /* Avoid REQUEST infinite loop. Per RFC 2131 section 3.1.5: if the client receives neither a DHCPACK or a DHCPNAK message after employing the retransmission algorithm, the client reverts to INIT state and restarts the initialization process */ - if (client->request_attempt >= client->max_request_attempts) { - log_dhcp_client(client, "Max REQUEST attempts reached. Restarting..."); - client_restart(client); + log_dhcp_client(client, "Max REQUEST attempts reached. Restarting..."); + r = client_restart(client); + if (r >= 0) return 0; - } + +error: client_stop(client, r); /* Errors were dealt with when stopping the client, don't spill @@ -1396,117 +1309,89 @@ static int client_timeout_resend( return 0; } -static int client_initialize_io_events( - sd_dhcp_client *client, - sd_event_io_handler_t io_callback) { - - int r; - +static int client_initialize_time_events(sd_dhcp_client *client) { assert(client); assert(client->event); - r = sd_event_add_io(client->event, &client->receive_message, - client->fd, EPOLLIN, io_callback, - client); - if (r < 0) - goto error; - - r = sd_event_source_set_priority(client->receive_message, - client->event_priority); - if (r < 0) - goto error; - - r = sd_event_source_set_description(client->receive_message, "dhcp4-receive-message"); - if (r < 0) - goto error; - -error: - if (r < 0) - client_stop(client, r); - - return 0; + return event_reset_time_relative( + client->event, + &client->timeout_resend, + CLOCK_BOOTTIME, + client->start_delay, + /* accuracy= */ 0, + client_timeout_resend, + client, + client->event_priority, + "dhcp4-resend-timer", + /* force_reset= */ true); } -static int client_initialize_time_events(sd_dhcp_client *client) { - usec_t usec = 0; - int r; - +static int client_start_delayed(sd_dhcp_client *client) { assert(client); - assert(client->event); + DHCP_CLIENT_DONT_DESTROY(client); - (void) event_source_disable(client->timeout_ipv6_only_mode); + client_disable_event_sources(client); + client->lease = sd_dhcp_lease_unref(client->lease); - if (client->start_delay > 0) { - assert_se(sd_event_now(client->event, CLOCK_BOOTTIME, &usec) >= 0); - usec = usec_add(usec, client->start_delay); - } + client->xid = random_u32(); + client->start_time = now(CLOCK_BOOTTIME); - r = event_reset_time(client->event, &client->timeout_resend, - CLOCK_BOOTTIME, - usec, 0, - client_timeout_resend, client, - client->event_priority, "dhcp4-resend-timer", true); - if (r < 0) - client_stop(client, r); + if (client->state != DHCP_STATE_INIT_REBOOT) + client_set_state(client, DHCP_STATE_INIT); - return 0; + return client_initialize_time_events(client); } -static int client_initialize_events(sd_dhcp_client *client, sd_event_io_handler_t io_callback) { - client_initialize_io_events(client, io_callback); - client_initialize_time_events(client); +static int client_start(sd_dhcp_client *client) { + assert(client); - return 0; + client->start_delay = 0; + return client_start_delayed(client); } -static int client_start_delayed(sd_dhcp_client *client) { - int r; +static int client_restart(sd_dhcp_client *client) { + assert(client); + DHCP_CLIENT_DONT_DESTROY(client); - assert_return(client, -EINVAL); - assert_return(client->event, -EINVAL); - assert_return(client->ifindex > 0, -EINVAL); - assert_return(client->fd < 0, -EBUSY); - assert_return(client->xid == 0, -EINVAL); - assert_return(IN_SET(client->state, DHCP_STATE_STOPPED, DHCP_STATE_INIT_REBOOT), -EBUSY); + /* This is called when we receive a DHCPNAK or could not receive any replies. */ - client->xid = random_u32(); + /* First, if we have a bound lease, then notify it is expired. */ + if (IN_SET(client->state, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING)) { + client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); - r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid, - &client->hw_addr, &client->bcast_addr, - client->arp_type, client->port, - client->socket_priority_set, client->socket_priority); - if (r < 0) { - client_stop(client, r); - return r; + if (client->state == DHCP_STATE_STOPPED) + return 0; /* The notify callback stopped the client. */ } - client->fd = r; - client->start_time = now(CLOCK_BOOTTIME); - - if (client->state == DHCP_STATE_STOPPED) - client->state = DHCP_STATE_INIT; + /* On reboot, DHCPNAK or no reply suggests that the network is changed or the address is already + * used by another host. Let's restart the client immediately without any delay to speed up the + * reboot process. */ + if (client->state == DHCP_STATE_REBOOTING) + return client_start(client); - return client_initialize_events(client, client_receive_message_raw); -} + /* Otherwise, we should restart the client with a short delay. */ + client->start_delay = CLAMP(client->start_delay * 2, + RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC); -static int client_start(sd_dhcp_client *client) { - client->start_delay = 0; + log_dhcp_client(client, "REBOOT in %s", FORMAT_TIMESPAN(client->start_delay, USEC_PER_SEC)); return client_start_delayed(client); } static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp_client *client = userdata; DHCP_CLIENT_DONT_DESTROY(client); + int r; log_dhcp_client(client, "EXPIRED"); client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); - /* lease was lost, start over if not freed or stopped in callback */ - if (client->state != DHCP_STATE_STOPPED) { - client_initialize(client); - client_start(client); - } + if (client->state == DHCP_STATE_STOPPED) + return 0; /* The notify callback stopped the client. */ + + r = client_start(client); + if (r < 0) + client_stop(client, r); return 0; } @@ -1514,40 +1399,24 @@ static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userda static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp_client *client = ASSERT_PTR(userdata); DHCP_CLIENT_DONT_DESTROY(client); - int r; + /* Explicitly close the unicast socket opened during renewing. On success path, the socket will be + * closed anyway on sending broadcast DHCPREQUEST, but let's explicitly close it here for failure + * path to ignore all unicast replies from now on. */ client->receive_message = sd_event_source_disable_unref(client->receive_message); - client->fd = safe_close(client->fd); client_set_state(client, DHCP_STATE_REBINDING); - client->discover_attempt = 0; - client->request_attempt = 0; - - r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid, - &client->hw_addr, &client->bcast_addr, - client->arp_type, client->port, - client->socket_priority_set, client->socket_priority); - if (r < 0) { - client_stop(client, r); - return 0; - } - client->fd = r; - return client_initialize_events(client, client_receive_message_raw); + return client_timeout_resend(s, usec, userdata); } static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) { - sd_dhcp_client *client = userdata; + sd_dhcp_client *client = ASSERT_PTR(userdata); DHCP_CLIENT_DONT_DESTROY(client); - if (client->lease) - client_set_state(client, DHCP_STATE_RENEWING); - else if (client->state != DHCP_STATE_INIT) - client_set_state(client, DHCP_STATE_INIT_REBOOT); - client->discover_attempt = 0; - client->request_attempt = 0; + client_set_state(client, DHCP_STATE_RENEWING); - return client_initialize_time_events(client); + return client_timeout_resend(s, usec, userdata); } static int dhcp_option_parse_and_verify( @@ -1739,39 +1608,15 @@ static int client_handle_offer_or_rapid_ack(sd_dhcp_client *client, DHCPMessage return 0; } -static int client_enter_requesting_now(sd_dhcp_client *client) { - assert(client); - - client_set_state(client, DHCP_STATE_REQUESTING); - client->discover_attempt = 0; - client->request_attempt = 0; - - return event_reset_time(client->event, &client->timeout_resend, - CLOCK_BOOTTIME, 0, 0, - client_timeout_resend, client, - client->event_priority, "dhcp4-resend-timer", - /* force_reset= */ true); -} - -static int client_enter_requesting_delayed(sd_event_source *s, uint64_t usec, void *userdata) { - sd_dhcp_client *client = ASSERT_PTR(userdata); - DHCP_CLIENT_DONT_DESTROY(client); - int r; - - r = client_enter_requesting_now(client); - if (r < 0) - client_stop(client, r); - - return 0; -} - static int client_enter_requesting(sd_dhcp_client *client) { assert(client); assert(client->lease); - (void) event_source_disable(client->timeout_resend); + client_disable_event_sources(client); - if (client->lease->ipv6_only_preferred_usec > 0) { + client_set_state(client, DHCP_STATE_REQUESTING); + + if (sd_dhcp_client_is_waiting_for_ipv6_connectivity(client)) { if (client->ipv6_acquired) { log_dhcp_client(client, "Received an OFFER with IPv6-only preferred option, and the host already acquired IPv6 connectivity, stopping DHCPv4 client."); @@ -1781,35 +1626,19 @@ static int client_enter_requesting(sd_dhcp_client *client) { log_dhcp_client(client, "Received an OFFER with IPv6-only preferred option, delaying to send REQUEST with %s.", FORMAT_TIMESPAN(client->lease->ipv6_only_preferred_usec, USEC_PER_SEC)); - - return event_reset_time_relative(client->event, &client->timeout_ipv6_only_mode, - CLOCK_BOOTTIME, - client->lease->ipv6_only_preferred_usec, 0, - client_enter_requesting_delayed, client, - client->event_priority, "dhcp4-ipv6-only-mode-timer", - /* force_reset= */ true); } - return client_enter_requesting_now(client); -} - -static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, size_t len) { - int r; - - r = dhcp_option_parse(force, len, NULL, NULL, NULL); - if (r != DHCP_FORCERENEW) - return -ENOMSG; - -#if 0 - log_dhcp_client(client, "FORCERENEW"); - return 0; -#else - /* FIXME: Ignore FORCERENEW requests until we implement RFC3118 (Authentication for DHCP - * Messages) and/or RFC6704 (Forcerenew Nonce Authentication), as unauthenticated FORCERENEW - * requests causes a security issue (TALOS-2020-1142, CVE-2020-13529). */ - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "Received FORCERENEW, ignoring."); -#endif + return event_reset_time_relative( + client->event, + &client->timeout_resend, + CLOCK_BOOTTIME, + client->lease->ipv6_only_preferred_usec, + /* accuracy= */ 0, + client_timeout_resend, + client, + client->event_priority, + "dhcp4-resend-timer", + /* force_reset= */ true); } static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) { @@ -1962,108 +1791,28 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { return 0; } -static int client_enter_bound_now(sd_dhcp_client *client, int notify_event) { +static int client_enter_bound(sd_dhcp_client *client, int notify_event) { int r; assert(client); + assert(client->lease); if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING)) notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; + client_disable_event_sources(client); + + client->start_delay = 0; + client_set_state(client, DHCP_STATE_BOUND); - client->discover_attempt = 0; - client->request_attempt = 0; client->last_addr = client->lease->address; r = client_set_lease_timeouts(client); if (r < 0) - log_dhcp_client_errno(client, r, "could not set lease timeouts: %m"); - - if (client->bootp) { - client->receive_message = sd_event_source_disable_unref(client->receive_message); - client->fd = safe_close(client->fd); - } else { - r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type); - if (r < 0) - return log_dhcp_client_errno(client, r, "could not bind UDP socket: %m"); - - client->receive_message = sd_event_source_disable_unref(client->receive_message); - close_and_replace(client->fd, r); - client_initialize_io_events(client, client_receive_message_udp); - } + return log_dhcp_client_errno(client, r, "Failed to set lease timeouts: %m"); client_notify(client, notify_event); - - return 0; -} - -static int client_enter_bound_delayed(sd_event_source *s, uint64_t usec, void *userdata) { - sd_dhcp_client *client = ASSERT_PTR(userdata); - DHCP_CLIENT_DONT_DESTROY(client); - int r; - - r = client_enter_bound_now(client, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); - if (r < 0) - client_stop(client, r); - - return 0; -} - -static int client_enter_bound(sd_dhcp_client *client, int notify_event) { - assert(client); - assert(client->lease); - - client->start_delay = 0; - (void) event_source_disable(client->timeout_resend); - - /* RFC 8925 section 3.2 - * If the client is in the INIT-REBOOT state, it SHOULD stop the DHCPv4 configuration process or - * disable the IPv4 stack completely for V6ONLY_WAIT seconds or until the network attachment event, - * whichever happens first. - * - * In the below, the condition uses REBOOTING, instead of INIT-REBOOT, as the client state has - * already transitioned from INIT-REBOOT to REBOOTING after sending a DHCPREQUEST message. */ - if (client->state == DHCP_STATE_REBOOTING && client->lease->ipv6_only_preferred_usec > 0) { - if (client->ipv6_acquired) { - log_dhcp_client(client, - "Received an ACK with IPv6-only preferred option, and the host already acquired IPv6 connectivity, stopping DHCPv4 client."); - return sd_dhcp_client_stop(client); - } - - log_dhcp_client(client, - "Received an ACK with IPv6-only preferred option, delaying to enter bound state with %s.", - FORMAT_TIMESPAN(client->lease->ipv6_only_preferred_usec, USEC_PER_SEC)); - - return event_reset_time_relative(client->event, &client->timeout_ipv6_only_mode, - CLOCK_BOOTTIME, - client->lease->ipv6_only_preferred_usec, 0, - client_enter_bound_delayed, client, - client->event_priority, "dhcp4-ipv6-only-mode", - /* force_reset= */ true); - } - - return client_enter_bound_now(client, notify_event); -} - -static int client_restart(sd_dhcp_client *client) { - int r; - assert(client); - - client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); - - r = client_initialize(client); - if (r < 0) - return r; - - r = client_start_delayed(client); - if (r < 0) - return r; - - log_dhcp_client(client, "REBOOT in %s", FORMAT_TIMESPAN(client->start_delay, USEC_PER_SEC)); - - client->start_delay = CLAMP(client->start_delay * 2, - RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC); return 0; } @@ -2104,10 +1853,7 @@ static int client_verify_message_header(sd_dhcp_client *client, DHCPMessage *mes return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received chaddr does not match expected, ignoring."); - if (client->state != DHCP_STATE_BOUND && - be32toh(message->xid) != client->xid) - /* in BOUND state, we may receive FORCERENEW with xid set by server, - so ignore the xid in this case */ + if (be32toh(message->xid) != client->xid) return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received xid (%u) does not match expected (%u), ignoring.", be32toh(message->xid), client->xid); @@ -2161,13 +1907,8 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, s return client_enter_bound(client, r); case DHCP_STATE_BOUND: - r = client_handle_forcerenew(client, message, len); - if (ERRNO_IS_NEG_RESOURCE(r)) - return r; - if (r < 0) - return 0; /* invalid message, let's ignore it */ - - return client_timeout_t1(NULL, 0, client); + log_dhcp_client(client, "Unexpected DHCP message received in BOUND state, ignoring."); + return 0; case DHCP_STATE_INIT: case DHCP_STATE_INIT_REBOOT: @@ -2181,7 +1922,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, s return 0; } -static int client_receive_message_udp( +int client_receive_message_udp( sd_event_source *s, int fd, uint32_t revents, @@ -2234,7 +1975,7 @@ static int client_receive_message_udp( return 0; } -static int client_receive_message_raw( +int client_receive_message_raw( sd_event_source *s, int fd, uint32_t revents, @@ -2302,11 +2043,9 @@ int sd_dhcp_client_send_renew(sd_dhcp_client *client) { if (!sd_dhcp_client_is_running(client) || client->state != DHCP_STATE_BOUND || client->bootp) return 0; /* do nothing */ - client->start_delay = 0; - client->discover_attempt = 1; - client->request_attempt = 1; client_set_state(client, DHCP_STATE_RENEWING); + client->start_delay = 0; return client_initialize_time_events(client); } @@ -2321,13 +2060,8 @@ int sd_dhcp_client_start(sd_dhcp_client *client) { int r; assert_return(client, -EINVAL); - - /* Note, do not reset the flag in client_initialize(), as it is also called on expire. */ - client->ipv6_acquired = false; - - r = client_initialize(client); - if (r < 0) - return r; + assert_return(client->event, -EINVAL); + assert_return(client->ifindex > 0, -EINVAL); /* If no client identifier exists, construct an RFC 4361-compliant one */ if (!sd_dhcp_client_id_is_set(&client->client_id)) { @@ -2356,70 +2090,88 @@ int sd_dhcp_client_start(sd_dhcp_client *client) { return r; } -static int client_send_release(sd_dhcp_client *client) { - _cleanup_free_ DHCPPacket *release = NULL; - size_t optoffset, optlen; +static int client_send_release_or_decline(sd_dhcp_client *client, uint8_t type) { int r; - assert(client); + assert(IN_SET(type, DHCP_RELEASE, DHCP_DECLINE)); - if (!client->send_release) - return 0; /* disabled */ + if (!sd_dhcp_client_is_running(client) || !client->lease || client->bootp) + return 0; /* there is nothing to release or decline */ - if (!client->lease || client->bootp) - return 0; /* there is nothing to be released */ + const char *name = type == DHCP_RELEASE ? "RELEASE" : "DECLINE"; - r = client_message_init(client, DHCP_RELEASE, &release, &optlen, &optoffset); + _cleanup_free_ DHCPPacket *packet = NULL; + size_t optoffset, optlen; + r = client_message_init(client, type, &packet, &optlen, &optoffset); if (r < 0) - return r; + return log_dhcp_client_errno(client, r, "Failed to initialize DHCP %s message: %m", name); - /* Fill up release IP and MAC */ - release->dhcp.ciaddr = client->lease->address; - memcpy(&release->dhcp.chaddr, client->hw_addr.bytes, client->hw_addr.length); + /* See RFC 2131, Table 5 */ + switch (type) { + case DHCP_RELEASE: + /* On release, the acquired address must be set in ciaddr. */ + packet->dhcp.ciaddr = client->lease->address; + break; - r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_END, 0, NULL); + case DHCP_DECLINE: + /* On decline, the acquired address must be set in Requested IP Address option. */ + r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, /* overload= */ 0, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, + 4, &client->lease->address); + if (r < 0) + return log_dhcp_client_errno( + client, r, + "Failed to append Requested IP Address option to DHCP %s message: %m", + name); + break; + + default: + assert_not_reached(); + } + + /* In both cases, the server identifier must be set. */ + r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, /* overload= */ 0, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + 4, &client->lease->server_address); if (r < 0) - return r; + return log_dhcp_client_errno( + client, r, + "Failed to append Server Identifier option to DHCP %s message: %m", + name); - r = dhcp_network_send_udp_socket(client->fd, - client->lease->server_address, - client->server_port, - &release->dhcp, - sizeof(DHCPMessage) + optoffset); + r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, /* overload= */ 0, + SD_DHCP_OPTION_END, /* optlen= */ 0, /* optval= */ NULL); if (r < 0) - return r; + return log_dhcp_client_errno( + client, r, + "Failed to finalize DHCP %s message: %m", + name); + + switch (type) { + case DHCP_RELEASE: + r = dhcp_client_send_udp(client, /* expect_reply= */ false, packet, optoffset); + break; + case DHCP_DECLINE: + r = dhcp_client_send_raw(client, /* expect_reply= */ false, packet, optoffset); + break; + default: + assert_not_reached(); + } + if (r < 0) + return log_dhcp_client_errno( + client, r, + "Failed to send DHCP %s message: %m", + name); - log_dhcp_client(client, "RELEASE"); - return 0; + log_dhcp_client(client, "%s", name); + return 1; /* sent */ } int sd_dhcp_client_send_decline(sd_dhcp_client *client) { - _cleanup_free_ DHCPPacket *release = NULL; - size_t optoffset, optlen; int r; - if (!sd_dhcp_client_is_running(client) || !client->lease) - return 0; /* do nothing */ - - r = client_message_init(client, DHCP_DECLINE, &release, &optlen, &optoffset); - if (r < 0) - return r; - - release->dhcp.ciaddr = client->lease->address; - memcpy(&release->dhcp.chaddr, client->hw_addr.bytes, client->hw_addr.length); - - r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - r = dhcp_network_send_udp_socket(client->fd, - client->lease->server_address, - client->server_port, - &release->dhcp, - sizeof(DHCPMessage) + optoffset); - if (r < 0) + r = client_send_release_or_decline(client, DHCP_DECLINE); + if (r <= 0) return r; log_dhcp_client(client, "DECLINE"); @@ -2434,47 +2186,61 @@ int sd_dhcp_client_send_decline(sd_dhcp_client *client) { } int sd_dhcp_client_stop(sd_dhcp_client *client) { - int r; - if (!client) return 0; DHCP_CLIENT_DONT_DESTROY(client); - r = client_send_release(client); - if (r < 0) - log_dhcp_client_errno(client, r, - "Failed to send DHCP release message, ignoring: %m"); + if (client->send_release) + (void) client_send_release_or_decline(client, DHCP_RELEASE); client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); - return 0; } +int sd_dhcp_client_is_waiting_for_ipv6_connectivity(sd_dhcp_client *client) { + /* Note that we intentionally do not implement the following behavior: + * + * RFC 8925, section 3.2: + * If the client is in the INIT-REBOOT state, it SHOULD stop the DHCPv4 configuration process or + * disable the IPv4 stack completely for V6ONLY_WAIT seconds or until the next network attachment + * event, whichever occurs first. + * + * Delaying the application of an acquired IPv4 address after DHCPACK introduces several issues: + * + * - If T1 is reached before the address is assigned to the interface, the client cannot send a + * unicast DHCPREQUEST during RENEWING. + * + * - If the client is stopped before the address is configured, it cannot send a DHCPRELEASE message, + * which also requires a valid source address. + * + * While these issues could be worked around, doing so would significantly complicate the + * implementation and violate assumptions in the DHCP state machine as defined in RFC 2131. + * + * Instead, we only honor the IPv6-Only Preferred delay (Option 108) in the REQUESTING state, i.e. + * before any DHCPREQUEST has been sent. */ + + return + client && + client->state == DHCP_STATE_REQUESTING && + client->request_attempt == 0 && + client->lease && + client->lease->ipv6_only_preferred_usec > 0; +} + int sd_dhcp_client_set_ipv6_connectivity(sd_dhcp_client *client, int have) { if (!client) return 0; - /* We have already received a message with IPv6-Only preferred option, and are waiting for IPv6 - * connectivity or timeout, let's stop the client. */ - if (have && sd_event_source_get_enabled(client->timeout_ipv6_only_mode, NULL) > 0) - return sd_dhcp_client_stop(client); - - /* Otherwise, save that the host already has IPv6 connectivity. */ client->ipv6_acquired = have; - return 0; -} -int sd_dhcp_client_interrupt_ipv6_only_mode(sd_dhcp_client *client) { - assert_return(client, -EINVAL); - assert_return(sd_dhcp_client_is_running(client), -ESTALE); - assert_return(client->fd >= 0, -EINVAL); - - if (sd_event_source_get_enabled(client->timeout_ipv6_only_mode, NULL) <= 0) - return 0; + if (have && sd_dhcp_client_is_waiting_for_ipv6_connectivity(client)) { + log_dhcp_client(client, + "Acquired IPv6 connectivity before sending REQUEST, stopping DHCPv4 client."); + return sd_dhcp_client_stop(client); + } - client_initialize(client); - return client_start(client); + return 0; } int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int64_t priority) { @@ -2506,7 +2272,7 @@ int sd_dhcp_client_detach_event(sd_dhcp_client *client) { return 0; } -sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client) { +sd_event* sd_dhcp_client_get_event(sd_dhcp_client *client) { assert_return(client, NULL); return client->event; @@ -2518,7 +2284,7 @@ int sd_dhcp_client_attach_device(sd_dhcp_client *client, sd_device *dev) { return device_unref_and_replace(client->dev, dev); } -static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) { +static sd_dhcp_client* dhcp_client_free(sd_dhcp_client *client) { if (!client) return NULL; @@ -2526,10 +2292,10 @@ static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) { client_initialize(client); - client->timeout_resend = sd_event_source_unref(client->timeout_resend); - client->timeout_t1 = sd_event_source_unref(client->timeout_t1); - client->timeout_t2 = sd_event_source_unref(client->timeout_t2); - client->timeout_expire = sd_event_source_unref(client->timeout_expire); + sd_event_source_unref(client->timeout_resend); + sd_event_source_unref(client->timeout_t1); + sd_event_source_unref(client->timeout_t2); + sd_event_source_unref(client->timeout_expire); sd_dhcp_client_detach_event(client); @@ -2563,13 +2329,11 @@ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) { .n_ref = 1, .state = DHCP_STATE_STOPPED, .ifindex = -1, - .fd = -EBADF, .mtu = DHCP_MIN_PACKET_SIZE, .port = DHCP_PORT_CLIENT, .server_port = DHCP_PORT_SERVER, .anonymize = !!anonymize, .max_discover_attempts = UINT64_MAX, - .max_request_attempts = 5, .ip_service_type = -1, }; /* NOTE: this could be moved to a function. */ diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index efded5df01025..fa41d293da2f3 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -434,7 +434,7 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) { for (sd_dhcp_lease_server_type_t i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++) free(lease->servers[i].addr); - dns_resolver_done_many(lease->dnr, lease->n_dnr); + dns_resolver_free_array(lease->dnr, lease->n_dnr); free(lease->static_routes); free(lease->classless_routes); free(lease->vendor_specific); @@ -642,7 +642,7 @@ static int lease_parse_dnr(const uint8_t *option, size_t len, sd_dns_resolver ** int r; sd_dns_resolver *res_list = NULL; size_t n_resolvers = 0; - CLEANUP_ARRAY(res_list, n_resolvers, dns_resolver_done_many); + CLEANUP_ARRAY(res_list, n_resolvers, dns_resolver_free_array); assert(option || len == 0); assert(dnr); @@ -747,7 +747,7 @@ static int lease_parse_dnr(const uint8_t *option, size_t len, sd_dns_resolver ** typesafe_qsort(res_list, n_resolvers, dns_resolver_prio_compare); - dns_resolver_done_many(*dnr, *n_dnr); + dns_resolver_free_array(*dnr, *n_dnr); *dnr = TAKE_PTR(res_list); *n_dnr = n_resolvers; @@ -1266,6 +1266,8 @@ int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const vo int dhcp_lease_new(sd_dhcp_lease **ret) { sd_dhcp_lease *lease; + assert(ret); + lease = new0(sd_dhcp_lease, 1); if (!lease) return -ENOMEM; diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c index 5c24de4084fb8..0268bbf2c29af 100644 --- a/src/libsystemd-network/sd-dhcp-server-lease.c +++ b/src/libsystemd-network/sd-dhcp-server-lease.c @@ -490,7 +490,7 @@ static int load_leases_file(int dir_fd, const char *path, SavedInfo *ret) { /* f= */ NULL, dir_fd, path, - /* flags= */ 0, + /* flags= */ SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* ret_column= */ NULL); diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index d1fe0e227323b..ba2b035821918 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -271,7 +271,9 @@ int sd_dhcp_server_set_boot_server_name(sd_dhcp_server *server, const char *name int sd_dhcp_server_set_boot_filename(sd_dhcp_server *server, const char *filename) { assert_return(server, -EINVAL); - if (filename && !string_is_safe_ascii(filename)) + if (isempty(filename)) + filename = NULL; + else if (!string_is_safe(filename, STRING_ASCII|STRING_ALLOW_GLOBS)) return -EINVAL; return free_and_strdup(&server->boot_filename, filename); @@ -320,6 +322,7 @@ static int dhcp_server_send_unicast_raw( .ll.sll_ifindex = server->ifindex, .ll.sll_halen = hlen, }; + int r; assert(server); assert(server->ifindex > 0); @@ -334,9 +337,16 @@ static int dhcp_server_send_unicast_raw( if (len > UINT16_MAX) return -EOVERFLOW; - dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER, - packet->dhcp.yiaddr, - DHCP_PORT_CLIENT, len, -1); + r = dhcp_packet_append_ip_headers( + packet, + server->address, + DHCP_PORT_SERVER, + packet->dhcp.yiaddr, + DHCP_PORT_CLIENT, + len, + /* ip_service_type= */ -1); + if (r < 0) + return r; return dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len); } @@ -719,7 +729,7 @@ static int server_send_offer_or_ack( r = dhcp_option_append( &packet->dhcp, req->max_optlen, &offset, 0, SD_DHCP_OPTION_VENDOR_SPECIFIC, - ordered_set_size(server->vendor_options), server->vendor_options); + /* optlen= */ 0, server->vendor_options); if (r < 0) return r; } @@ -1641,6 +1651,7 @@ int sd_dhcp_server_set_callback(sd_dhcp_server *server, sd_dhcp_server_callback_ int sd_dhcp_server_set_relay_target(sd_dhcp_server *server, const struct in_addr *address) { assert_return(server, -EINVAL); + assert_return(address, -EINVAL); assert_return(!sd_dhcp_server_is_running(server), -EBUSY); if (memcmp(address, &server->relay_target, sizeof(struct in_addr)) == 0) diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 448a2e7557ee9..ee67664364c9f 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -379,6 +379,9 @@ int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, int *enable } static int be16_compare_func(const be16_t *a, const be16_t *b) { + assert(a); + assert(b); + return CMP(be16toh(*a), be16toh(*b)); } diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index 835f6c2d65303..4e745bbc3b569 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -1076,7 +1076,7 @@ static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) { dhcp6_ia_free(lease->ia_na); dhcp6_ia_free(lease->ia_pd); free(lease->dns); - dns_resolver_done_many(lease->dnr, lease->n_dnr); + dns_resolver_free_array(lease->dnr, lease->n_dnr); free(lease->fqdn); free(lease->captive_portal); strv_free(lease->domains); diff --git a/src/libsystemd-network/sd-dns-resolver.c b/src/libsystemd-network/sd-dns-resolver.c index c8c6618c725f1..8285c5cb57640 100644 --- a/src/libsystemd-network/sd-dns-resolver.c +++ b/src/libsystemd-network/sd-dns-resolver.c @@ -26,14 +26,7 @@ sd_dns_resolver *sd_dns_resolver_unref(sd_dns_resolver *res) { return mfree(res); } -void dns_resolver_done_many(sd_dns_resolver resolvers[], size_t n) { - assert(resolvers || n == 0); - - FOREACH_ARRAY(res, resolvers, n) - sd_dns_resolver_done(res); - - free(resolvers); -} +DEFINE_ARRAY_FREE_FUNC(dns_resolver_free_array, sd_dns_resolver, sd_dns_resolver_done); int dns_resolver_prio_compare(const sd_dns_resolver *a, const sd_dns_resolver *b) { return CMP(ASSERT_PTR(a)->priority, ASSERT_PTR(b)->priority); @@ -259,8 +252,8 @@ int dnr_parse_svc_params(const uint8_t *option, size_t len, sd_dns_resolver *res return -EBADMSG; case DNS_SVC_PARAM_KEY_DOHPATH: - r = make_cstring((const char*) &option[offset], plen, - MAKE_CSTRING_REFUSE_TRAILING_NUL, &dohpath); + r = make_cstring(&option[offset], plen, + MAKE_CSTRING_REFUSE_TRAILING_NUL, &dohpath); if (ERRNO_IS_NEG_RESOURCE(r)) return r; if (r < 0) @@ -306,7 +299,7 @@ int dns_resolvers_to_dot_addrs(const sd_dns_resolver *resolvers, size_t n_resolv struct in_addr_full **addrs = NULL; size_t n = 0; - CLEANUP_ARRAY(addrs, n, in_addr_full_array_free); + CLEANUP_ARRAY(addrs, n, in_addr_full_free_array); FOREACH_ARRAY(res, resolvers, n_resolvers) { if (!FLAGS_SET(res->transports, SD_DNS_ALPN_DOT)) @@ -347,7 +340,7 @@ int dns_resolvers_to_dot_strv(const sd_dns_resolver *resolvers, size_t n_resolve struct in_addr_full **addrs = NULL; size_t n = 0; - CLEANUP_ARRAY(addrs, n, in_addr_full_array_free); + CLEANUP_ARRAY(addrs, n, in_addr_full_free_array); r = dns_resolvers_to_dot_addrs(resolvers, n_resolvers, &addrs, &n); if (r < 0) diff --git a/src/libsystemd-network/sd-lldp-tx.c b/src/libsystemd-network/sd-lldp-tx.c index 4097091002a31..59da447ef342c 100644 --- a/src/libsystemd-network/sd-lldp-tx.c +++ b/src/libsystemd-network/sd-lldp-tx.c @@ -157,6 +157,7 @@ int sd_lldp_tx_set_multicast_mode(sd_lldp_tx *lldp_tx, sd_lldp_multicast_mode_t int sd_lldp_tx_set_hwaddr(sd_lldp_tx *lldp_tx, const struct ether_addr *hwaddr) { assert_return(lldp_tx, -EINVAL); + assert_return(hwaddr, -EINVAL); assert_return(!ether_addr_is_null(hwaddr), -EINVAL); lldp_tx->hwaddr = *hwaddr; diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 35cfcec6aca04..b2cadc07e5b1e 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -8,9 +8,6 @@ #include #include #include -#if HAVE_VALGRIND_VALGRIND_H -# include -#endif #include "sd-dhcp-client.h" #include "sd-dhcp-lease.h" @@ -20,9 +17,9 @@ #include "dhcp-duid-internal.h" #include "dhcp-network.h" #include "dhcp-option.h" -#include "dhcp-packet.h" #include "ether-addr-util.h" #include "fd-util.h" +#include "ip-util.h" #include "log.h" #include "tests.h" @@ -33,7 +30,7 @@ static struct hw_addr_data hw_addr = { .length = ETH_ALEN, .ether = {{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }}, }; -typedef int (*test_callback_recv_t)(size_t size, DHCPMessage *dhcp); +typedef void (*test_callback_recv_t)(size_t size, DHCPMessage *dhcp); struct bootp_addr_data { uint8_t *offer_buf; @@ -41,131 +38,89 @@ struct bootp_addr_data { int netmask_offset; int ip_offset; }; -struct bootp_addr_data *bootp_test_context; +static struct bootp_addr_data *bootp_test_context; -static bool verbose = true; static int test_fd[2]; static test_callback_recv_t callback_recv; static be32_t xid; -static void test_request_basic(sd_event *e) { - int r; - - sd_dhcp_client *client; - - if (verbose) - log_info("* %s", __func__); - +TEST(dhcp_client_setters) { /* Initialize client without Anonymize settings. */ - r = sd_dhcp_client_new(&client, false); - - assert_se(r >= 0); - assert_se(client); - - r = sd_dhcp_client_attach_event(client, e, 0); - assert_se(r >= 0); + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ false)); + ASSERT_NOT_NULL(client); ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_request_option(NULL, 0) == -EINVAL); ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_request_address(NULL, NULL) == -EINVAL); ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_ifindex(NULL, 0) == -EINVAL); - assert_se(sd_dhcp_client_set_ifindex(client, 15) == 0); + ASSERT_OK(sd_dhcp_client_set_ifindex(client, 15)); ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_ifindex(client, -42) == -EINVAL); ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_ifindex(client, -1) == -EINVAL); ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_ifindex(client, 0) == -EINVAL); - assert_se(sd_dhcp_client_set_ifindex(client, 1) == 0); - - assert_se(sd_dhcp_client_set_hostname(client, "host") == 1); - assert_se(sd_dhcp_client_set_hostname(client, "host.domain") == 1); - assert_se(sd_dhcp_client_set_hostname(client, NULL) == 1); - assert_se(sd_dhcp_client_set_hostname(client, "~host") == -EINVAL); - assert_se(sd_dhcp_client_set_hostname(client, "~host.domain") == -EINVAL); - - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_SUBNET_MASK) == 0); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_ROUTER) == 0); - /* This PRL option is not set when using Anonymize, but in this test - * Anonymize settings are not being used. */ - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_HOST_NAME) == 0); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_DOMAIN_NAME) == 0); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_DOMAIN_NAME_SERVER) == 0); - - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_PAD) == -EINVAL); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_END) == -EINVAL); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_MESSAGE_TYPE) == -EINVAL); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_OVERLOAD) == -EINVAL); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST) == -EINVAL); + ASSERT_OK(sd_dhcp_client_set_ifindex(client, 1)); + + ASSERT_OK_POSITIVE(sd_dhcp_client_set_hostname(client, "host")); + ASSERT_OK_ZERO(sd_dhcp_client_set_hostname(client, "host")); + ASSERT_OK_POSITIVE(sd_dhcp_client_set_hostname(client, "host.domain")); + ASSERT_OK_POSITIVE(sd_dhcp_client_set_hostname(client, NULL)); + ASSERT_ERROR(sd_dhcp_client_set_hostname(client, "~host"), EINVAL); + ASSERT_ERROR(sd_dhcp_client_set_hostname(client, "~host.domain"), EINVAL); + + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_SUBNET_MASK)); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_ROUTER)); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_HOST_NAME)); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_DOMAIN_NAME)); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_DOMAIN_NAME_SERVER)); + + ASSERT_ERROR(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_PAD), EINVAL); + ASSERT_ERROR(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_END), EINVAL); + ASSERT_ERROR(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_MESSAGE_TYPE), EINVAL); + ASSERT_ERROR(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_OVERLOAD), EINVAL); + ASSERT_ERROR(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST), EINVAL); /* RFC7844: option 33 (SD_DHCP_OPTION_STATIC_ROUTE) is set in the * default PRL when using Anonymize, so it is changed to other option * that is not set by default, to check that it was set successfully. * Options not set by default (using or not anonymize) are option 17 * (SD_DHCP_OPTION_ROOT_PATH) and 42 (SD_DHCP_OPTION_NTP_SERVER) */ - assert_se(sd_dhcp_client_set_request_option(client, 17) == 1); - assert_se(sd_dhcp_client_set_request_option(client, 17) == 0); - assert_se(sd_dhcp_client_set_request_option(client, 42) == 1); - assert_se(sd_dhcp_client_set_request_option(client, 17) == 0); - - sd_dhcp_client_unref(client); + ASSERT_OK_POSITIVE(sd_dhcp_client_set_request_option(client, 17)); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, 17)); + ASSERT_OK_POSITIVE(sd_dhcp_client_set_request_option(client, 42)); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, 17)); } -static void test_request_anonymize(sd_event *e) { - int r; - - sd_dhcp_client *client; - - if (verbose) - log_info("* %s", __func__); - +TEST(dhcp_client_anonymize) { /* Initialize client with Anonymize settings. */ - r = sd_dhcp_client_new(&client, true); - - assert_se(r >= 0); - assert_se(client); - - r = sd_dhcp_client_attach_event(client, e, 0); - assert_se(r >= 0); + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ true)); + ASSERT_NOT_NULL(client); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_NETBIOS_NAME_SERVER) == 0); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_NETBIOS_NAME_SERVER)); /* This PRL option is not set when using Anonymize */ - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_HOST_NAME) == 1); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST) == -EINVAL); + ASSERT_OK_POSITIVE(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_HOST_NAME)); + ASSERT_ERROR(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST), EINVAL); /* RFC7844: option 101 (SD_DHCP_OPTION_NEW_TZDB_TIMEZONE) is not set in the * default PRL when using Anonymize, */ - assert_se(sd_dhcp_client_set_request_option(client, 101) == 1); - assert_se(sd_dhcp_client_set_request_option(client, 101) == 0); - - sd_dhcp_client_unref(client); + ASSERT_OK_POSITIVE(sd_dhcp_client_set_request_option(client, 101)); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, 101)); } -static void test_checksum(void) { - uint8_t buf[20] = { - 0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, - 0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, 0xff - }; - - if (verbose) - log_info("* %s", __func__); - - assert_se(dhcp_packet_checksum((uint8_t*)&buf, 20) == be16toh(0x78ae)); -} - -static void test_dhcp_identifier_set_iaid(void) { +TEST(dhcp_identifier_set_iaid) { uint32_t iaid_legacy; be32_t iaid; - assert_se(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy_unstable_byteorder= */ true, &iaid_legacy) >= 0); - assert_se(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy_unstable_byteorder= */ false, &iaid) >= 0); + ASSERT_OK(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy_unstable_byteorder= */ true, &iaid_legacy)); + ASSERT_OK(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy_unstable_byteorder= */ false, &iaid)); - /* we expect, that the MAC address was hashed. The legacy value is in native - * endianness. */ - assert_se(iaid_legacy == 0x8dde4ba8u); - assert_se(iaid == htole32(0x8dde4ba8u)); + /* we expect, that the MAC address was hashed. The legacy value is in native endianness. */ + ASSERT_EQ(iaid_legacy, 0x8dde4ba8u); + ASSERT_EQ(iaid, htole32(0x8dde4ba8u)); #if __BYTE_ORDER == __LITTLE_ENDIAN - assert_se(iaid == iaid_legacy); + ASSERT_EQ(iaid, iaid_legacy); #else - assert_se(iaid == bswap_32(iaid_legacy)); + ASSERT_EQ(iaid, bswap_32(iaid_legacy)); #endif } @@ -175,15 +130,15 @@ static int check_options(uint8_t code, uint8_t len, const void *option, void *us sd_dhcp_duid duid; uint32_t iaid; - assert_se(sd_dhcp_duid_set_en(&duid) >= 0); - assert_se(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy_unstable_byteorder= */ true, &iaid) >= 0); + ASSERT_OK(sd_dhcp_duid_set_en(&duid)); + ASSERT_OK(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy_unstable_byteorder= */ true, &iaid)); - assert_se(len == sizeof(uint8_t) + sizeof(uint32_t) + duid.size); - assert_se(len == 19); - assert_se(((uint8_t*) option)[0] == 0xff); + ASSERT_EQ(len, 19u); + ASSERT_EQ(len, sizeof(uint8_t) + sizeof(uint32_t) + duid.size); + ASSERT_EQ(((uint8_t*) option)[0], 0xff); - assert_se(memcmp((uint8_t*) option + 1, &iaid, sizeof(iaid)) == 0); - assert_se(memcmp((uint8_t*) option + 5, &duid.duid, duid.size) == 0); + ASSERT_EQ(memcmp((uint8_t*) option + 1, &iaid, sizeof(iaid)), 0); + ASSERT_EQ(memcmp((uint8_t*) option + 5, &duid.duid, duid.size), 0); break; } @@ -195,48 +150,43 @@ static int check_options(uint8_t code, uint8_t len, const void *option, void *us } int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const void *packet, size_t len) { - size_t size; - _cleanup_free_ DHCPPacket *discover = NULL; uint16_t ip_check, udp_check; - assert_se(s >= 0); - assert_se(packet); + ASSERT_OK(s); + ASSERT_NOT_NULL(packet); - size = sizeof(DHCPPacket); - assert_se(len > size); + ASSERT_GT(len, sizeof(DHCPPacket)); - discover = memdup(packet, len); + _cleanup_free_ DHCPPacket *discover = ASSERT_NOT_NULL(memdup(packet, len)); - assert_se(discover->ip.ttl == IPDEFTTL); - assert_se(discover->ip.protocol == IPPROTO_UDP); - assert_se(discover->ip.saddr == INADDR_ANY); - assert_se(discover->ip.daddr == INADDR_BROADCAST); - assert_se(discover->udp.source == be16toh(DHCP_PORT_CLIENT)); - assert_se(discover->udp.dest == be16toh(DHCP_PORT_SERVER)); + ASSERT_EQ(discover->ip.ttl, IPDEFTTL); + ASSERT_EQ(discover->ip.protocol, IPPROTO_UDP); + ASSERT_EQ(discover->ip.saddr, INADDR_ANY); + ASSERT_EQ(discover->ip.daddr, INADDR_BROADCAST); + ASSERT_EQ(discover->udp.source, be16toh(DHCP_PORT_CLIENT)); + ASSERT_EQ(discover->udp.dest, be16toh(DHCP_PORT_SERVER)); ip_check = discover->ip.check; discover->ip.ttl = 0; discover->ip.check = discover->udp.len; - udp_check = ~dhcp_packet_checksum(&discover->ip.ttl, len - 8); - assert_se(udp_check == 0xffff); + udp_check = ~ip_checksum(&discover->ip.ttl, len - 8); + ASSERT_EQ(udp_check, 0xffff); discover->ip.ttl = IPDEFTTL; discover->ip.check = ip_check; - ip_check = ~dhcp_packet_checksum((uint8_t*)&discover->ip, sizeof(discover->ip)); - assert_se(ip_check == 0xffff); - - assert_se(discover->dhcp.xid); - assert_se(memcmp(discover->dhcp.chaddr, hw_addr.bytes, hw_addr.length) == 0); + ip_check = ~ip_checksum((uint8_t*) &discover->ip, sizeof(discover->ip)); + ASSERT_EQ(ip_check, 0xffff); - size = len - sizeof(struct iphdr) - sizeof(struct udphdr); + ASSERT_NE(discover->dhcp.xid, 0u); + ASSERT_EQ(memcmp(discover->dhcp.chaddr, hw_addr.bytes, hw_addr.length), 0); - assert_se(callback_recv); - callback_recv(size, &discover->dhcp); + ASSERT_NOT_NULL(callback_recv); + callback_recv(len - sizeof(struct iphdr) - sizeof(struct udphdr), &discover->dhcp); - return 575; + return 0; } int dhcp_network_bind_raw_socket( @@ -250,70 +200,47 @@ int dhcp_network_bind_raw_socket( bool so_priority_set, int so_priority) { - if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0) - return -errno; - + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd)); return test_fd[0]; } int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) { - int fd; - - fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - return fd; + return ASSERT_OK_ERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); } int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, const void *packet, size_t len) { return 0; } -static int test_discover_message_verify(size_t size, struct DHCPMessage *dhcp) { - int res; - - res = dhcp_option_parse(dhcp, size, check_options, NULL, NULL); - assert_se(res == DHCP_DISCOVER); - - if (verbose) - log_info(" recv DHCP Discover 0x%08x", be32toh(dhcp->xid)); - - return 0; +static void test_discover_message_verify(size_t size, struct DHCPMessage *dhcp) { + ASSERT_OK_EQ(dhcp_option_parse(dhcp, size, check_options, NULL, NULL), DHCP_DISCOVER); + log_debug(" recv DHCP Discover 0x%08x", be32toh(dhcp->xid)); } -static void test_discover_message(sd_event *e) { - sd_dhcp_client *client; - int res, r; - - if (verbose) - log_info("* %s", __func__); +TEST(discover_message) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_NOT_NULL(e); - r = sd_dhcp_client_new(&client, false); - assert_se(r >= 0); - assert_se(client); + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ false)); + ASSERT_NOT_NULL(client); - r = sd_dhcp_client_attach_event(client, e, 0); - assert_se(r >= 0); + ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 0)); - assert_se(sd_dhcp_client_set_ifindex(client, 42) >= 0); - assert_se(sd_dhcp_client_set_mac(client, hw_addr.bytes, bcast_addr.bytes, hw_addr.length, ARPHRD_ETHER) >= 0); + ASSERT_OK(sd_dhcp_client_set_ifindex(client, 42)); + ASSERT_OK(sd_dhcp_client_set_mac(client, hw_addr.bytes, bcast_addr.bytes, hw_addr.length, ARPHRD_ETHER)); - assert_se(sd_dhcp_client_set_request_option(client, 248) >= 0); + ASSERT_OK(sd_dhcp_client_set_request_option(client, 248)); callback_recv = test_discover_message_verify; - res = sd_dhcp_client_start(client); - - assert_se(IN_SET(res, 0, -EINPROGRESS)); - - sd_event_run(e, UINT64_MAX); - - sd_dhcp_client_stop(client); - sd_dhcp_client_unref(client); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_run(e, /* timeout= */ UINT64_MAX)); + ASSERT_OK(sd_dhcp_client_stop(client)); + ASSERT_NULL(client = sd_dhcp_client_unref(client)); test_fd[1] = safe_close(test_fd[1]); - callback_recv = NULL; } @@ -405,52 +332,41 @@ static uint8_t test_addr_acq_ack[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; -static int test_addr_acq_acquired(sd_dhcp_client *client, int event, - void *userdata) { - sd_event *e = userdata; - sd_dhcp_lease *lease; - struct in_addr addr; - const struct in_addr *addrs; - - assert_se(client); - assert_se(IN_SET(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE, SD_DHCP_CLIENT_EVENT_SELECTING)); - - assert_se(sd_dhcp_client_get_lease(client, &lease) >= 0); - assert_se(lease); +static int test_addr_acq_acquired(sd_dhcp_client *client, int event, void *userdata) { + ASSERT_NOT_NULL(client); + ASSERT_TRUE(IN_SET(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE, SD_DHCP_CLIENT_EVENT_SELECTING)); - assert_se(sd_dhcp_lease_get_address(lease, &addr) >= 0); - assert_se(memcmp(&addr.s_addr, &test_addr_acq_ack[44], - sizeof(addr.s_addr)) == 0); + sd_dhcp_lease *lease; + ASSERT_OK(sd_dhcp_client_get_lease(client, &lease)); + ASSERT_NOT_NULL(lease); - assert_se(sd_dhcp_lease_get_netmask(lease, &addr) >= 0); - assert_se(memcmp(&addr.s_addr, &test_addr_acq_ack[285], - sizeof(addr.s_addr)) == 0); + struct in_addr addr; + ASSERT_OK(sd_dhcp_lease_get_address(lease, &addr)); + ASSERT_EQ(memcmp(&addr.s_addr, &test_addr_acq_ack[44], sizeof(addr.s_addr)), 0); - assert_se(sd_dhcp_lease_get_router(lease, &addrs) == 1); - assert_se(memcmp(&addrs[0].s_addr, &test_addr_acq_ack[308], - sizeof(addrs[0].s_addr)) == 0); + ASSERT_OK(sd_dhcp_lease_get_netmask(lease, &addr)); + ASSERT_EQ(memcmp(&addr.s_addr, &test_addr_acq_ack[285], sizeof(addr.s_addr)), 0); - if (verbose) - log_info(" DHCP address acquired"); + const struct in_addr *addrs; + ASSERT_OK_EQ(sd_dhcp_lease_get_router(lease, &addrs), 1); + ASSERT_EQ(memcmp(&addrs[0].s_addr, &test_addr_acq_ack[308], sizeof(addrs[0].s_addr)), 0); - sd_event_exit(e, 0); + log_info(" DHCP address acquired"); - return 0; + sd_event *e = ASSERT_NOT_NULL(sd_dhcp_client_get_event(client)); + return ASSERT_OK(sd_event_exit(e, 0)); } -static int test_addr_acq_recv_request(size_t size, DHCPMessage *request) { +static void test_addr_acq_recv_request(size_t size, DHCPMessage *request) { uint16_t udp_check = 0; uint8_t *msg_bytes = (uint8_t *)request; - int res; - res = dhcp_option_parse(request, size, check_options, NULL, NULL); - assert_se(res == DHCP_REQUEST); - assert_se(xid == request->xid); + ASSERT_OK_EQ(dhcp_option_parse(request, size, check_options, NULL, NULL), DHCP_REQUEST); + ASSERT_EQ(request->xid, xid); - assert_se(msg_bytes[size - 1] == SD_DHCP_OPTION_END); + ASSERT_EQ(msg_bytes[size - 1], SD_DHCP_OPTION_END); - if (verbose) - log_info(" recv DHCP Request 0x%08x", be32toh(xid)); + log_info(" recv DHCP Request 0x%08x", be32toh(xid)); memcpy(&test_addr_acq_ack[26], &udp_check, sizeof(udp_check)); memcpy(&test_addr_acq_ack[32], &xid, sizeof(xid)); @@ -458,30 +374,23 @@ static int test_addr_acq_recv_request(size_t size, DHCPMessage *request) { callback_recv = NULL; - res = write(test_fd[1], test_addr_acq_ack, - sizeof(test_addr_acq_ack)); - assert_se(res == sizeof(test_addr_acq_ack)); + ASSERT_OK_EQ_ERRNO(write(test_fd[1], test_addr_acq_ack, sizeof(test_addr_acq_ack)), + (ssize_t) sizeof(test_addr_acq_ack)); - if (verbose) - log_info(" send DHCP Ack"); - - return 0; + log_info(" send DHCP Ack"); }; -static int test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) { +static void test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) { uint16_t udp_check = 0; uint8_t *msg_bytes = (uint8_t *)discover; - int res; - res = dhcp_option_parse(discover, size, check_options, NULL, NULL); - assert_se(res == DHCP_DISCOVER); + ASSERT_OK_EQ(dhcp_option_parse(discover, size, check_options, NULL, NULL), DHCP_DISCOVER); - assert_se(msg_bytes[size - 1] == SD_DHCP_OPTION_END); + ASSERT_EQ(msg_bytes[size - 1], SD_DHCP_OPTION_END); xid = discover->xid; - if (verbose) - log_info(" recv DHCP Discover 0x%08x", be32toh(xid)); + log_info(" recv DHCP Discover 0x%08x", be32toh(xid)); memcpy(&test_addr_acq_offer[26], &udp_check, sizeof(udp_check)); memcpy(&test_addr_acq_offer[32], &xid, sizeof(xid)); @@ -489,59 +398,48 @@ static int test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) { callback_recv = test_addr_acq_recv_request; - res = write(test_fd[1], test_addr_acq_offer, - sizeof(test_addr_acq_offer)); - assert_se(res == sizeof(test_addr_acq_offer)); - - if (verbose) - log_info(" sent DHCP Offer"); + ASSERT_OK_EQ_ERRNO(write(test_fd[1], test_addr_acq_offer, sizeof(test_addr_acq_offer)), + (ssize_t) sizeof(test_addr_acq_offer)); - return 0; + log_info(" sent DHCP Offer"); } -static void test_addr_acq(sd_event *e) { - sd_dhcp_client *client; - int res, r; - - if (verbose) - log_info("* %s", __func__); +TEST(addr_acq) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_NOT_NULL(e); - r = sd_dhcp_client_new(&client, false); - assert_se(r >= 0); - assert_se(client); + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ false)); + ASSERT_NOT_NULL(client); - r = sd_dhcp_client_attach_event(client, e, 0); - assert_se(r >= 0); + ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 0)); - assert_se(sd_dhcp_client_set_ifindex(client, 42) >= 0); - assert_se(sd_dhcp_client_set_mac(client, hw_addr.bytes, bcast_addr.bytes, hw_addr.length, ARPHRD_ETHER) >= 0); + ASSERT_OK(sd_dhcp_client_set_ifindex(client, 42)); + ASSERT_OK(sd_dhcp_client_set_mac(client, hw_addr.bytes, bcast_addr.bytes, hw_addr.length, ARPHRD_ETHER)); - assert_se(sd_dhcp_client_set_callback(client, test_addr_acq_acquired, e) >= 0); + ASSERT_OK(sd_dhcp_client_set_callback(client, test_addr_acq_acquired, NULL)); callback_recv = test_addr_acq_recv_discover; - assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, + ASSERT_OK(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, 30 * USEC_PER_SEC, 0, - NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0); - - res = sd_dhcp_client_start(client); - assert_se(IN_SET(res, 0, -EINPROGRESS)); - - assert_se(sd_event_loop(e) >= 0); + NULL, INT_TO_PTR(-ETIMEDOUT))); - assert_se(sd_dhcp_client_set_callback(client, NULL, NULL) >= 0); - assert_se(sd_dhcp_client_stop(client) >= 0); - sd_dhcp_client_unref(client); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_dhcp_client_set_callback(client, NULL, NULL)); + ASSERT_OK(sd_dhcp_client_stop(client)); + ASSERT_NULL(client = sd_dhcp_client_unref(client)); test_fd[1] = safe_close(test_fd[1]); - callback_recv = NULL; xid = 0; } static uint8_t test_addr_bootp_reply[] = { - 0x45, 0x00, 0x01, 0x48, 0x00, 0x00, 0x40, 0x00, - 0xff, 0x11, 0x70, 0xa3, 0x0a, 0x00, 0x00, 0x02, + 0x45, 0x00, 0x01, 0x40, 0x00, 0x00, 0x40, 0x00, + 0xff, 0x11, 0x70, 0xab, 0x0a, 0x00, 0x00, 0x02, 0xff, 0xff, 0xff, 0xff, 0x00, 0x43, 0x00, 0x44, 0x01, 0x2c, 0x2b, 0x91, 0x02, 0x01, 0x06, 0x00, 0x69, 0xd3, 0x79, 0x11, 0x17, 0x00, 0x80, 0x00, @@ -641,42 +539,33 @@ static struct bootp_addr_data bootp_addr_data[] = { }, }; -static int test_bootp_acquired(sd_dhcp_client *client, int event, - void *userdata) { - sd_dhcp_lease *lease = NULL; - sd_event *e = userdata; - struct in_addr addr; - +static int test_bootp_acquired(sd_dhcp_client *client, int event, void *userdata) { ASSERT_NOT_NULL(client); - assert_se(IN_SET(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE, SD_DHCP_CLIENT_EVENT_SELECTING)); + ASSERT_TRUE(IN_SET(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE, SD_DHCP_CLIENT_EVENT_SELECTING)); + sd_dhcp_lease *lease; ASSERT_OK(sd_dhcp_client_get_lease(client, &lease)); ASSERT_NOT_NULL(lease); + struct in_addr addr; ASSERT_OK(sd_dhcp_lease_get_address(lease, &addr)); - ASSERT_EQ(memcmp(&addr.s_addr, &bootp_test_context->offer_buf[bootp_test_context->ip_offset], - sizeof(addr.s_addr)), 0); + ASSERT_EQ(memcmp(&addr.s_addr, &bootp_test_context->offer_buf[bootp_test_context->ip_offset], sizeof(addr.s_addr)), 0); ASSERT_OK(sd_dhcp_lease_get_netmask(lease, &addr)); - ASSERT_EQ(memcmp(&addr.s_addr, &bootp_test_context->offer_buf[bootp_test_context->netmask_offset], - sizeof(addr.s_addr)), 0); - - if (verbose) - log_info(" BOOTP address acquired"); + ASSERT_EQ(memcmp(&addr.s_addr, &bootp_test_context->offer_buf[bootp_test_context->netmask_offset], sizeof(addr.s_addr)), 0); - sd_event_exit(e, 0); + log_info(" BOOTP address acquired"); - return 0; + sd_event *e = ASSERT_NOT_NULL(sd_dhcp_client_get_event(client)); + return ASSERT_OK(sd_event_exit(e, 0)); } -static int test_bootp_recv_request(size_t size, DHCPMessage *request) { +static void test_bootp_recv_request(size_t size, DHCPMessage *request) { uint16_t udp_check = 0; - size_t res; xid = request->xid; - if (verbose) - log_info(" recv BOOTP Request 0x%08x", be32toh(xid)); + log_info(" recv BOOTP Request 0x%08x", be32toh(xid)); callback_recv = NULL; @@ -684,34 +573,29 @@ static int test_bootp_recv_request(size_t size, DHCPMessage *request) { memcpy(&bootp_test_context->offer_buf[32], &xid, sizeof(xid)); memcpy(&bootp_test_context->offer_buf[56], hw_addr.bytes, hw_addr.length); - res = write(test_fd[1], bootp_test_context->offer_buf, - bootp_test_context->offer_len); - ASSERT_EQ(res, bootp_test_context->offer_len); + ASSERT_OK_EQ_ERRNO(write(test_fd[1], bootp_test_context->offer_buf, bootp_test_context->offer_len), + (ssize_t) bootp_test_context->offer_len); - if (verbose) - log_info(" sent BOOTP Reply"); - - return 0; + log_info(" sent BOOTP Reply"); }; -static void test_acquire_bootp(sd_event *e) { - sd_dhcp_client *client = NULL; - int res; - - if (verbose) - log_info("* %s", __func__); +static void test_bootp_one(void) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_NOT_NULL(e); - ASSERT_OK(sd_dhcp_client_new(&client, false)); + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ false)); ASSERT_NOT_NULL(client); - ASSERT_OK(sd_dhcp_client_attach_event(client, e, 0)); + ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 0)); ASSERT_OK(sd_dhcp_client_set_bootp(client, true)); ASSERT_OK(sd_dhcp_client_set_ifindex(client, 42)); ASSERT_OK(sd_dhcp_client_set_mac(client, hw_addr.bytes, bcast_addr.bytes, hw_addr.length, ARPHRD_ETHER)); - ASSERT_OK(sd_dhcp_client_set_callback(client, test_bootp_acquired, e)); + ASSERT_OK(sd_dhcp_client_set_callback(client, test_bootp_acquired, NULL)); callback_recv = test_bootp_recv_request; @@ -719,53 +603,27 @@ static void test_acquire_bootp(sd_event *e) { 30 * USEC_PER_SEC, 0, NULL, INT_TO_PTR(-ETIMEDOUT))); - res = sd_dhcp_client_start(client); - assert_se(IN_SET(res, 0, -EINPROGRESS)); - + ASSERT_OK(sd_dhcp_client_start(client)); ASSERT_OK(sd_event_loop(e)); - ASSERT_OK(sd_dhcp_client_set_callback(client, NULL, NULL)); ASSERT_OK(sd_dhcp_client_stop(client)); - client = sd_dhcp_client_unref(client); - ASSERT_NULL(client); + ASSERT_NULL(client = sd_dhcp_client_unref(client)); test_fd[1] = safe_close(test_fd[1]); - callback_recv = NULL; xid = 0; } -int main(int argc, char *argv[]) { - _cleanup_(sd_event_unrefp) sd_event *e; - - assert_se(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", 1) >= 0); - - test_setup_logging(LOG_DEBUG); - - assert_se(sd_event_new(&e) >= 0); - - test_request_basic(e); - test_request_anonymize(e); - test_checksum(); - test_dhcp_identifier_set_iaid(); - - test_discover_message(e); - test_addr_acq(e); - +TEST(bootp) { FOREACH_ELEMENT(i, bootp_addr_data) { - sd_event_unref(e); - ASSERT_OK(sd_event_new(&e)); bootp_test_context = i; - test_acquire_bootp(e); + test_bootp_one(); } +} -#if HAVE_VALGRIND_VALGRIND_H - /* Make sure the async_close thread has finished. - * valgrind would report some of the phread_* structures - * as not cleaned up properly. */ - if (RUNNING_ON_VALGRIND) - sleep(1); -#endif - +static int intro(void) { + ASSERT_OK_ERRNO(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", /* overwrite= */ true)); return 0; } + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/libsystemd-network/test-dhcp-option.c b/src/libsystemd-network/test-dhcp-option.c index 05572e0b21a28..31f25fb5be854 100644 --- a/src/libsystemd-network/test-dhcp-option.c +++ b/src/libsystemd-network/test-dhcp-option.c @@ -120,6 +120,9 @@ static DHCPMessage *create_message(uint8_t *options, uint16_t optlen, } static void test_ignore_opts(uint8_t *descoption, int *descpos, int *desclen) { + assert(descoption); + assert(descpos); + assert(desclen); assert_se(*descpos >= 0); while (*descpos < *desclen) { diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index 7789037011e7d..3dc49491269b1 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -68,7 +68,7 @@ static int test_basic(bool bind_to_interface) { r = sd_dhcp_server_start(server); /* skip test if running in an environment with no full networking support, CONFIG_PACKET not * compiled in kernel, nor af_packet module available. */ - if (r == -EPERM || r == -EAFNOSUPPORT) + if (IN_SET(r, -EPERM, -EAFNOSUPPORT)) return r; ASSERT_OK(r); @@ -83,9 +83,7 @@ static int test_basic(bool bind_to_interface) { static void test_message_handler(void) { _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; struct { - struct { - DHCP_MESSAGE_HEADER_DEFINITION; - } _packed_ message; + DHCPMessageHeader header; struct { uint8_t code; uint8_t length; @@ -113,11 +111,11 @@ static void test_message_handler(void) { } _packed_ option_hostname; uint8_t end; } _packed_ test = { - .message.op = BOOTREQUEST, - .message.htype = ARPHRD_ETHER, - .message.hlen = ETHER_ADDR_LEN, - .message.xid = htobe32(0x12345678), - .message.chaddr = { 'A', 'B', 'C', 'D', 'E', 'F' }, + .header.op = BOOTREQUEST, + .header.htype = ARPHRD_ETHER, + .header.hlen = ETHER_ADDR_LEN, + .header.xid = htobe32(0x12345678), + .header.chaddr = { 'A', 'B', 'C', 'D', 'E', 'F' }, .option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE, .option_type.length = 1, .option_type.type = DHCP_DISCOVER, @@ -168,19 +166,19 @@ static void test_message_handler(void) { test.option_type.type = DHCP_DISCOVER; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); - test.message.op = 0; + test.header.op = 0; ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); - test.message.op = BOOTREQUEST; + test.header.op = BOOTREQUEST; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); - test.message.htype = 0; + test.header.htype = 0; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); - test.message.htype = ARPHRD_ETHER; + test.header.htype = ARPHRD_ETHER; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); - test.message.hlen = 0; + test.header.hlen = 0; ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), EBADMSG); - test.message.hlen = ETHER_ADDR_LEN; + test.header.hlen = ETHER_ADDR_LEN; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); test.option_type.type = DHCP_REQUEST; @@ -246,7 +244,7 @@ static void test_message_handler(void) { ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); /* release the bound static lease */ - test.message.ciaddr = htobe32(INADDR_LOOPBACK + 31); + test.header.ciaddr = htobe32(INADDR_LOOPBACK + 31); test.option_type.type = DHCP_RELEASE; ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); @@ -261,7 +259,7 @@ static void test_message_handler(void) { ASSERT_OK(sd_dhcp_server_start(server)); /* request a new non-static address */ - test.message.ciaddr = 0; + test.header.ciaddr = 0; test.option_type.type = DHCP_REQUEST; test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 29); ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); diff --git a/src/libsystemd-network/test-ip-util.c b/src/libsystemd-network/test-ip-util.c new file mode 100644 index 0000000000000..cf233ca91c575 --- /dev/null +++ b/src/libsystemd-network/test-ip-util.c @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "ip-util.h" +#include "random-util.h" +#include "tests.h" + +TEST(ip_checksum) { + uint8_t buf[20] = { + 0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff + }; + + ASSERT_EQ(ip_checksum(buf, 20), be16toh(0x78ae)); +} + +static void create_packet(struct iphdr *ip, struct udphdr *udp, struct iovec_wrapper *payload, struct iovec *ret) { + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + ASSERT_OK(iovw_put(&iovw, ip, sizeof(struct iphdr))); + ASSERT_OK(iovw_put(&iovw, udp, sizeof(struct udphdr))); + ASSERT_OK(iovw_put_iovw(&iovw, payload)); + ASSERT_OK(iovw_concat(&iovw, ret)); +} + +TEST(udp_packet_build_and_verify) { + size_t n = random_u64_range(20) + 20; + + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + size_t i; + FOREACH_ARGUMENT(i, 1, 0, 1, 1, 3, 1, 2, 1, n, n, n + 1, n + 1, n + 2, n + 3, n + 4, n + 5, n + 6) { + struct iovec tmp = {}; + ASSERT_OK(random_bytes_allocate_iovec(i, &tmp)); + ASSERT_OK(iovw_consume_iov(&payload, &tmp)); + } + + struct iphdr ip; + struct udphdr udp; + ASSERT_OK(udp_packet_build( + /* source_addr= */ htobe32(0xC0020001), + /* source_port= */ 42, + /* destination_addr= */ htobe32(0xC0020002), + /* destination_port= */ 43, + /* ip_service_type= */ 7, + &payload, + &ip, + &udp)); + + _cleanup_(iovec_done) struct iovec joined = {}; + ASSERT_OK(iovw_concat(&payload, &joined)); + + struct iphdr ip2; + struct udphdr udp2; + ASSERT_OK(udp_packet_build( + /* source_addr= */ htobe32(0xC0020001), + /* source_port= */ 42, + /* destination_addr= */ htobe32(0xC0020002), + /* destination_port= */ 43, + /* ip_service_type= */ 7, + &(struct iovec_wrapper) { + .iovec = &joined, + .count = 1, + }, + &ip2, + &udp2)); + + ASSERT_EQ(memcmp(&ip, &ip2, sizeof(struct iphdr)), 0); + ASSERT_EQ(memcmp(&udp, &udp2, sizeof(struct udphdr)), 0); + + _cleanup_(iovec_done) struct iovec packet = {}; + create_packet(&ip, &udp, &payload, &packet); + + struct iovec iov; + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ true, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + + /* UDP port mismatch */ + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 42, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* truncated packet */ + ASSERT_ERROR(udp_packet_verify(&IOVEC_MAKE(packet.iov_base, packet.iov_len - 1), + /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad IP version */ + struct iphdr badip = ip; + badip.version = 6; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad IP header size */ + badip = ip; + badip.ihl = 1; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* packet size in IP header is smaller than IP header size */ + badip = ip; + badip.tot_len = htobe16(1); + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* packet size in IP header is larger than the packet size */ + badip = ip; + badip.tot_len = htobe16(be16toh(ip.tot_len) + 1); + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* IP protocol mismatch */ + badip = ip; + badip.protocol = IPPROTO_TCP; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad IP header checksum */ + badip = ip; + badip.check = ~ip.check; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* UDP length is smaller than the UDP header size */ + struct udphdr badudp = udp; + badudp.len = htobe16(1); + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* UDP length is smaller than the packet size */ + badudp = udp; + badudp.len = htobe16(be16toh(udp.len) - 1); + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* UDP length is larger than the packet size */ + badudp = udp; + badudp.len = htobe16(be16toh(udp.len) + 1); + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad UDP checksum */ + badudp = udp; + if (udp.check != UINT16_MAX) + badudp.check = ~udp.check; + else + badudp.check = 0xdeadu; + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ true, /* ret_payload= */ NULL), EBADMSG); + + /* missing UDP checksum */ + badudp = udp; + badudp.check = 0; + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ true, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd-network/test-lldp-rx.c b/src/libsystemd-network/test-lldp-rx.c index 629093fe2e03b..09c916db5183b 100644 --- a/src/libsystemd-network/test-lldp-rx.c +++ b/src/libsystemd-network/test-lldp-rx.c @@ -35,6 +35,8 @@ static void lldp_rx_handler(sd_lldp_rx *lldp_rx, sd_lldp_rx_event_t event, sd_ll static int start_lldp_rx(sd_lldp_rx **lldp_rx, sd_event *e, sd_lldp_rx_callback_t cb, void *cb_data) { int r; + assert(lldp_rx); + r = sd_lldp_rx_new(lldp_rx); if (r < 0) return r; diff --git a/src/libsystemd-network/test-ndisc-send.c b/src/libsystemd-network/test-ndisc-send.c index 247cc0ec16cfb..e0bbcfe6c243a 100644 --- a/src/libsystemd-network/test-ndisc-send.c +++ b/src/libsystemd-network/test-ndisc-send.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-ndisc-protocol.h" @@ -17,6 +16,7 @@ #include "ndisc-option.h" #include "netlink-util.h" #include "network-common.h" +#include "options.h" #include "parse-util.h" #include "set.h" #include "string-util.h" @@ -72,197 +72,158 @@ static int parse_preference(const char *str) { } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_RA_HOP_LIMIT, - ARG_RA_MANAGED, - ARG_RA_OTHER, - ARG_RA_HOME_AGENT, - ARG_RA_PREFERENCE, - ARG_RA_LIFETIME, - ARG_RA_REACHABLE, - ARG_RA_RETRANSMIT, - ARG_NA_ROUTER, - ARG_NA_SOLICITED, - ARG_NA_OVERRIDE, - ARG_TARGET_ADDRESS, - ARG_REDIRECT_DESTINATION, - ARG_OPTION_SOURCE_LL, - ARG_OPTION_TARGET_LL, - ARG_OPTION_REDIRECTED_HEADER, - ARG_OPTION_MTU, - }; - - static const struct option options[] = { - { "version", no_argument, NULL, ARG_VERSION }, - { "interface", required_argument, NULL, 'i' }, - { "type", required_argument, NULL, 't' }, - { "dest", required_argument, NULL, 'd' }, - /* For Router Advertisement */ - { "hop-limit", required_argument, NULL, ARG_RA_HOP_LIMIT }, - { "managed", required_argument, NULL, ARG_RA_MANAGED }, - { "other", required_argument, NULL, ARG_RA_OTHER }, - { "home-agent", required_argument, NULL, ARG_RA_HOME_AGENT }, - { "preference", required_argument, NULL, ARG_RA_PREFERENCE }, - { "lifetime", required_argument, NULL, ARG_RA_LIFETIME }, - { "reachable-time", required_argument, NULL, ARG_RA_REACHABLE }, - { "retransmit-timer", required_argument, NULL, ARG_RA_RETRANSMIT }, - /* For Neighbor Advertisement */ - { "is-router", required_argument, NULL, ARG_NA_ROUTER }, - { "is-solicited", required_argument, NULL, ARG_NA_SOLICITED }, - { "is-override", required_argument, NULL, ARG_NA_OVERRIDE }, - /* For Neighbor Solicit, Neighbor Advertisement, and Redirect */ - { "target-address", required_argument, NULL, ARG_TARGET_ADDRESS }, - /* For Redirect */ - { "redirect-destination", required_argument, NULL, ARG_REDIRECT_DESTINATION }, - /* Options */ - { "source-ll-address", required_argument, NULL, ARG_OPTION_SOURCE_LL }, - { "target-ll-address", required_argument, NULL, ARG_OPTION_TARGET_LL }, - { "redirected-header", required_argument, NULL, ARG_OPTION_REDIRECTED_HEADER }, - { "mtu", required_argument, NULL, ARG_OPTION_MTU }, - {} - }; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "i:t:d:", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'i': - r = rtnl_resolve_interface_or_warn(&rtnl, optarg); + OPTION('i', "interface", "INTERFACE", "Network interface"): + r = rtnl_resolve_interface_or_warn(&rtnl, arg); if (r < 0) return r; arg_ifindex = r; break; - case 't': - r = parse_icmp6_type(optarg); + OPTION('t', "type", "TYPE", "ICMPv6 message type"): + r = parse_icmp6_type(arg); if (r < 0) return log_error_errno(r, "Failed to parse message type: %m"); arg_icmp6_type = r; break; - case 'd': - r = in_addr_from_string(AF_INET6, optarg, &arg_dest); + OPTION('d', "dest", "ADDRESS", "Destination address"): + r = in_addr_from_string(AF_INET6, arg, &arg_dest); if (r < 0) return log_error_errno(r, "Failed to parse destination address: %m"); if (!in6_addr_is_link_local(&arg_dest.in6)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "The destination address %s is not a link-local address.", optarg); + "The destination address %s is not a link-local address.", arg); break; - case ARG_RA_HOP_LIMIT: - r = safe_atou8(optarg, &arg_hop_limit); + OPTION_GROUP("Router Advertisement"): {} + + OPTION_LONG("hop-limit", "LIMIT", "Hop limit"): + r = safe_atou8(arg, &arg_hop_limit); if (r < 0) return log_error_errno(r, "Failed to parse hop limit: %m"); break; - case ARG_RA_MANAGED: - r = parse_boolean(optarg); + OPTION_LONG("managed", "BOOL", "Managed flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse managed flag: %m"); SET_FLAG(arg_ra_flags, ND_RA_FLAG_MANAGED, r); break; - case ARG_RA_OTHER: - r = parse_boolean(optarg); + OPTION_LONG("other", "BOOL", "Other flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse other flag: %m"); SET_FLAG(arg_ra_flags, ND_RA_FLAG_OTHER, r); break; - case ARG_RA_HOME_AGENT: - r = parse_boolean(optarg); + OPTION_LONG("home-agent", "BOOL", "Home-agent flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse home-agent flag: %m"); SET_FLAG(arg_ra_flags, ND_RA_FLAG_HOME_AGENT, r); break; - case ARG_RA_PREFERENCE: - r = parse_preference(optarg); + OPTION_LONG("preference", "PREF", "Preference"): + r = parse_preference(arg); if (r < 0) return log_error_errno(r, "Failed to parse preference: %m"); arg_preference = r; break; - case ARG_RA_LIFETIME: - r = parse_sec(optarg, &arg_lifetime); + OPTION_LONG("lifetime", "SECS", "Lifetime"): + r = parse_sec(arg, &arg_lifetime); if (r < 0) return log_error_errno(r, "Failed to parse lifetime: %m"); break; - case ARG_RA_REACHABLE: - r = parse_sec(optarg, &arg_reachable); + OPTION_LONG("reachable-time", "SECS", "Reachable time"): + r = parse_sec(arg, &arg_reachable); if (r < 0) return log_error_errno(r, "Failed to parse reachable time: %m"); break; - case ARG_RA_RETRANSMIT: - r = parse_sec(optarg, &arg_retransmit); + OPTION_LONG("retransmit-timer", "SECS", "Retransmit timer"): + r = parse_sec(arg, &arg_retransmit); if (r < 0) return log_error_errno(r, "Failed to parse retransmit timer: %m"); break; - case ARG_NA_ROUTER: - r = parse_boolean(optarg); + OPTION_GROUP("Neighbor Advertisement"): {} + + OPTION_LONG("is-router", "BOOL", "Router flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse is-router flag: %m"); SET_FLAG(arg_na_flags, ND_NA_FLAG_ROUTER, r); break; - case ARG_NA_SOLICITED: - r = parse_boolean(optarg); + OPTION_LONG("is-solicited", "BOOL", "Solicited flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse is-solicited flag: %m"); SET_FLAG(arg_na_flags, ND_NA_FLAG_SOLICITED, r); break; - case ARG_NA_OVERRIDE: - r = parse_boolean(optarg); + OPTION_LONG("is-override", "BOOL", "Override flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse is-override flag: %m"); SET_FLAG(arg_na_flags, ND_NA_FLAG_OVERRIDE, r); break; - case ARG_TARGET_ADDRESS: - r = in_addr_from_string(AF_INET6, optarg, &arg_target_address); + OPTION_GROUP("Neighbor Solicit/Advertisement and Redirect"): {} + + OPTION_LONG("target-address", "ADDRESS", "Target address"): + r = in_addr_from_string(AF_INET6, arg, &arg_target_address); if (r < 0) return log_error_errno(r, "Failed to parse target address: %m"); break; - case ARG_REDIRECT_DESTINATION: - r = in_addr_from_string(AF_INET6, optarg, &arg_redirect_destination); + OPTION_GROUP("Redirect"): {} + + OPTION_LONG("redirect-destination", "ADDRESS", "Redirect destination address"): + r = in_addr_from_string(AF_INET6, arg, &arg_redirect_destination); if (r < 0) return log_error_errno(r, "Failed to parse destination address: %m"); break; - case ARG_OPTION_SOURCE_LL: - r = parse_boolean(optarg); + OPTION_GROUP("NDisc Options"): {} + + OPTION_LONG("source-ll-address", "BOOL", "Include source link-layer address"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse source LL address option: %m"); arg_set_source_mac = r; break; - case ARG_OPTION_TARGET_LL: - r = parse_ether_addr(optarg, &arg_target_mac); + OPTION_LONG("target-ll-address", "ADDRESS", "Target link-layer address"): + r = parse_ether_addr(arg, &arg_target_mac); if (r < 0) return log_error_errno(r, "Failed to parse target LL address option: %m"); arg_set_target_mac = true; break; - case ARG_OPTION_REDIRECTED_HEADER: { + OPTION_LONG("redirected-header", "BASE64", "Redirected header (base64)"): { _cleanup_free_ void *p = NULL; size_t len; - r = unbase64mem(optarg, &p, &len); + r = unbase64mem(arg, &p, &len); if (r < 0) return log_error_errno(r, "Failed to parse redirected header: %m"); @@ -272,20 +233,14 @@ static int parse_argv(int argc, char *argv[]) { arg_redirected_header = TAKE_PTR(p); break; } - case ARG_OPTION_MTU: - r = safe_atou32(optarg, &arg_mtu); + + OPTION_LONG("mtu", "MTU", "MTU"): + r = safe_atou32(arg, &arg_mtu); if (r < 0) return log_error_errno(r, "Failed to parse MTU: %m"); arg_set_mtu = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } if (arg_ifindex <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--interface/-i option is mandatory."); diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index aa270a483a40e..38ab92dea124b 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -1090,3 +1090,16 @@ LIBSYSTEMD_260 { global: sd_session_get_extra_device_access; } LIBSYSTEMD_259; + +LIBSYSTEMD_261 { +global: + sd_varlink_call_and_upgrade; + sd_varlink_reply_and_upgrade; + sd_varlink_set_sentinel; + sd_event_add_cpu_pressure; + sd_event_source_set_cpu_pressure_type; + sd_event_source_set_cpu_pressure_period; + sd_event_add_io_pressure; + sd_event_source_set_io_pressure_type; + sd_event_source_set_io_pressure_period; +} LIBSYSTEMD_260; diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index 976f0e998766c..08d8d7c5c39e7 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -80,6 +80,7 @@ sd_login_sources = files('sd-login/sd-login.c') ############################################################ sd_json_sources = files( + 'sd-json/json-stream.c', 'sd-json/json-util.c', 'sd-json/sd-json.c', ) diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index f0fc09f8a9192..6c2b0fd8814b1 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -111,6 +111,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRODUCT_UUID, EOPNOTSUPP), SD_BUS_ERROR_MAP(BUS_ERROR_NO_HARDWARE_SERIAL, EOPNOTSUPP), + SD_BUS_ERROR_MAP(BUS_ERROR_FIELD_NOT_SET, ENODATA), SD_BUS_ERROR_MAP(BUS_ERROR_FILE_IS_PROTECTED, EACCES), SD_BUS_ERROR_MAP(BUS_ERROR_READ_ONLY_FILESYSTEM, EROFS), diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index 36676e83db509..b4788433bfce8 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -112,6 +112,7 @@ #define BUS_ERROR_NO_PRODUCT_UUID "org.freedesktop.hostname1.NoProductUUID" #define BUS_ERROR_NO_HARDWARE_SERIAL "org.freedesktop.hostname1.NoHardwareSerial" +#define BUS_ERROR_FIELD_NOT_SET "org.freedesktop.hostname1.FieldNotSet" #define BUS_ERROR_FILE_IS_PROTECTED "org.freedesktop.hostname1.FileIsProtected" #define BUS_ERROR_READ_ONLY_FILESYSTEM "org.freedesktop.hostname1.ReadOnlyFilesystem" diff --git a/src/libsystemd/sd-bus/bus-creds.c b/src/libsystemd/sd-bus/bus-creds.c index 0812dc532477c..0b2a402251cb6 100644 --- a/src/libsystemd/sd-bus/bus-creds.c +++ b/src/libsystemd/sd-bus/bus-creds.c @@ -358,6 +358,7 @@ _public_ int sd_bus_creds_get_tid(sd_bus_creds *c, pid_t *ret) { _public_ int sd_bus_creds_get_selinux_context(sd_bus_creds *c, const char **ret) { assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); if (!(c->mask & SD_BUS_CREDS_SELINUX_CONTEXT)) return -ENODATA; diff --git a/src/libsystemd/sd-bus/bus-dump-json.c b/src/libsystemd/sd-bus/bus-dump-json.c index 92fcde359b5f4..8238ac587e908 100644 --- a/src/libsystemd/sd-bus/bus-dump-json.c +++ b/src/libsystemd/sd-bus/bus-dump-json.c @@ -57,8 +57,8 @@ static int json_transform_variant(sd_bus_message *m, const char *contents, sd_js return sd_json_buildo( ret, - SD_JSON_BUILD_PAIR("type", SD_JSON_BUILD_STRING(contents)), - SD_JSON_BUILD_PAIR("data", SD_JSON_BUILD_VARIANT(value))); + SD_JSON_BUILD_PAIR_STRING("type", contents), + SD_JSON_BUILD_PAIR_VARIANT("data", value)); } static int json_transform_dict_array(sd_bus_message *m, sd_json_variant **ret) { @@ -287,8 +287,8 @@ static int json_transform_message(sd_bus_message *m, const char *type, sd_json_v return sd_json_buildo( ret, - SD_JSON_BUILD_PAIR("type", SD_JSON_BUILD_STRING(type)), - SD_JSON_BUILD_PAIR("data", SD_JSON_BUILD_VARIANT(v))); + SD_JSON_BUILD_PAIR_STRING("type", type), + SD_JSON_BUILD_PAIR_VARIANT("data", v)); } _public_ int sd_bus_message_dump_json(sd_bus_message *m, uint64_t flags, sd_json_variant **ret) { @@ -325,13 +325,13 @@ _public_ int sd_bus_message_dump_json(sd_bus_message *m, uint64_t flags, sd_json return sd_json_buildo( ret, - SD_JSON_BUILD_PAIR("type", SD_JSON_BUILD_STRING(bus_message_type_to_string(m->header->type))), - SD_JSON_BUILD_PAIR("endian", SD_JSON_BUILD_STRING(CHAR_TO_STR(m->header->endian))), - SD_JSON_BUILD_PAIR("flags", SD_JSON_BUILD_INTEGER(m->header->flags)), - SD_JSON_BUILD_PAIR("version", SD_JSON_BUILD_INTEGER(m->header->version)), - SD_JSON_BUILD_PAIR("cookie", SD_JSON_BUILD_INTEGER(BUS_MESSAGE_COOKIE(m))), + SD_JSON_BUILD_PAIR_STRING("type", bus_message_type_to_string(m->header->type)), + SD_JSON_BUILD_PAIR_STRING("endian", CHAR_TO_STR(m->header->endian)), + SD_JSON_BUILD_PAIR_INTEGER("flags", m->header->flags), + SD_JSON_BUILD_PAIR_INTEGER("version", m->header->version), + SD_JSON_BUILD_PAIR_INTEGER("cookie", BUS_MESSAGE_COOKIE(m)), SD_JSON_BUILD_PAIR_CONDITION(m->reply_cookie != 0, "reply_cookie", SD_JSON_BUILD_INTEGER(m->reply_cookie)), - SD_JSON_BUILD_PAIR("timestamp-realtime", SD_JSON_BUILD_UNSIGNED(ts)), + SD_JSON_BUILD_PAIR_UNSIGNED("timestamp-realtime", ts), SD_JSON_BUILD_PAIR_CONDITION(!!m->sender, "sender", SD_JSON_BUILD_STRING(m->sender)), SD_JSON_BUILD_PAIR_CONDITION(!!m->destination, "destination", SD_JSON_BUILD_STRING(m->destination)), SD_JSON_BUILD_PAIR_CONDITION(!!m->path, "path", SD_JSON_BUILD_STRING(m->path)), @@ -341,5 +341,5 @@ _public_ int sd_bus_message_dump_json(sd_bus_message *m, uint64_t flags, sd_json SD_JSON_BUILD_PAIR_CONDITION(m->realtime != 0, "realtime", SD_JSON_BUILD_INTEGER(m->realtime)), SD_JSON_BUILD_PAIR_CONDITION(m->seqnum != 0, "seqnum", SD_JSON_BUILD_INTEGER(m->seqnum)), SD_JSON_BUILD_PAIR_CONDITION(!!m->error.name, "error_name", SD_JSON_BUILD_STRING(m->error.name)), - SD_JSON_BUILD_PAIR("payload", SD_JSON_BUILD_VARIANT(v))); + SD_JSON_BUILD_PAIR_VARIANT("payload", v)); } diff --git a/src/libsystemd/sd-bus/bus-error.c b/src/libsystemd/sd-bus/bus-error.c index bc0164c1b4ebb..be64e4a6c8bc0 100644 --- a/src/libsystemd/sd-bus/bus-error.c +++ b/src/libsystemd/sd-bus/bus-error.c @@ -165,6 +165,8 @@ static int errno_to_bus_error_name_new(int error, char **ret) { const char *name; char *n; + assert(ret); + /* D-Bus names must not start with a digit. Thus, an name like System.Error.500 would not be legal. * Let's just return 0 if an unknown errno is encountered, which will cause the caller to fall back * to BUS_ERROR_FAILED. diff --git a/src/libsystemd/sd-bus/bus-error.h b/src/libsystemd/sd-bus/bus-error.h index ac3c90c0d317e..32d29b3c8ccce 100644 --- a/src/libsystemd/sd-bus/bus-error.h +++ b/src/libsystemd/sd-bus/bus-error.h @@ -31,12 +31,10 @@ const char* _bus_error_message(const sd_bus_error *e, int error, char buf[static * the error map is really added to the final binary. * * In addition, set the retain attribute so that the section cannot be - * discarded by ld --gc-sections -z start-stop-gc. Older compilers would - * warn for the unknown attribute, so just disable -Wattributes. + * discarded by ld --gc-sections -z start-stop-gc. */ #define BUS_ERROR_MAP_ELF_REGISTER \ - _Pragma("GCC diagnostic ignored \"-Wattributes\"") \ _section_("SYSTEMD_BUS_ERROR_MAP") \ _used_ \ _retain_ \ @@ -49,7 +47,7 @@ const char* _bus_error_message(const sd_bus_error *e, int error, char buf[static static const sd_bus_error_map * const CONCATENATE(errors ## _copy_, __COUNTER__) = errors; /* We use something exotic as end marker, to ensure people build the - * maps using the macsd-ros. */ + * maps using the macros. */ #define BUS_ERROR_MAP_END_MARKER -'x' BUS_ERROR_MAP_ELF_USE(bus_standard_errors); diff --git a/src/libsystemd/sd-bus/bus-internal.c b/src/libsystemd/sd-bus/bus-internal.c index 4bcebeedb22f9..3d28d885557a8 100644 --- a/src/libsystemd/sd-bus/bus-internal.c +++ b/src/libsystemd/sd-bus/bus-internal.c @@ -263,6 +263,8 @@ bool path_simple_pattern(const char *pattern, const char *value) { } int bus_message_type_from_string(const char *s, uint8_t *u) { + assert(u); + if (streq(s, "signal")) *u = SD_BUS_MESSAGE_SIGNAL; else if (streq(s, "method_call")) diff --git a/src/libsystemd/sd-bus/bus-introspect.c b/src/libsystemd/sd-bus/bus-introspect.c index cdc869ce0496a..d66c3a704db5d 100644 --- a/src/libsystemd/sd-bus/bus-introspect.c +++ b/src/libsystemd/sd-bus/bus-introspect.c @@ -175,6 +175,8 @@ static int introspect_write_arguments(BusIntrospect *i, const char *signature, c assert(i); assert(i->m.f); + assert(signature); + assert(names); for (;;) { size_t l; diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c index c300d511ab1c3..94be969f7f420 100644 --- a/src/libsystemd/sd-bus/bus-message.c +++ b/src/libsystemd/sd-bus/bus-message.c @@ -359,10 +359,12 @@ static int message_from_header( /* Note that we are happy with unknown flags in the flags header! */ a = ALIGN(sizeof(sd_bus_message)); + /* Silence static analyzers, ALIGN cannot overflow for sizeof() */ + assert(a != SIZE_MAX); if (label) { label_sz = strlen(label); - a += label_sz + 1; + assert_se(INC_SAFE(&a, label_sz + 1)); } m = malloc0(a); @@ -416,6 +418,8 @@ int bus_message_from_malloc( size_t sz; int r; + assert(ret); + r = message_from_header( bus, buffer, length, @@ -462,6 +466,8 @@ _public_ int sd_bus_message_new( /* Creation of messages with _SD_BUS_MESSAGE_TYPE_INVALID is allowed. */ assert_return(type < _SD_BUS_MESSAGE_TYPE_MAX, -EINVAL); + /* Silence static analyzers, ALIGN cannot overflow for sizeof() */ + assert(ALIGN(sizeof(sd_bus_message)) != SIZE_MAX); sd_bus_message *t = malloc0(ALIGN(sizeof(sd_bus_message)) + sizeof(BusMessageHeader)); if (!t) return -ENOMEM; @@ -1492,9 +1498,6 @@ _public_ int sd_bus_message_append_string_iovec( const struct iovec *iov, unsigned n /* should be size_t, but is API now… 😞 */) { - size_t size; - unsigned i; - char *p; int r; assert_return(m, -EINVAL); @@ -1502,13 +1505,16 @@ _public_ int sd_bus_message_append_string_iovec( assert_return(iov || n == 0, -EINVAL); assert_return(!m->poisoned, -ESTALE); - size = iovec_total_size(iov, n); + size_t size = iovec_total_size(iov, n); + if (size == SIZE_MAX) + return -ENOBUFS; + char *p; r = sd_bus_message_append_string_space(m, size, &p); if (r < 0) return r; - for (i = 0; i < n; i++) { + for (unsigned i = 0; i < n; i++) { if (iov[i].iov_base) memcpy(p, iov[i].iov_base, iov[i].iov_len); @@ -2154,9 +2160,6 @@ _public_ int sd_bus_message_append_array_iovec( const struct iovec *iov, unsigned n /* should be size_t, but is API now… 😞 */) { - size_t size; - unsigned i; - void *p; int r; assert_return(m, -EINVAL); @@ -2165,13 +2168,16 @@ _public_ int sd_bus_message_append_array_iovec( assert_return(iov || n == 0, -EINVAL); assert_return(!m->poisoned, -ESTALE); - size = iovec_total_size(iov, n); + size_t size = iovec_total_size(iov, n); + if (size == SIZE_MAX) + return -ENOBUFS; + void *p; r = sd_bus_message_append_array_space(m, type, size, &p); if (r < 0) return r; - for (i = 0; i < n; i++) { + for (unsigned i = 0; i < n; i++) { if (iov[i].iov_base) memcpy(p, iov[i].iov_base, iov[i].iov_len); diff --git a/src/libsystemd/sd-bus/bus-message.h b/src/libsystemd/sd-bus/bus-message.h index c15e947fbf8d1..94eff878e5648 100644 --- a/src/libsystemd/sd-bus/bus-message.h +++ b/src/libsystemd/sd-bus/bus-message.h @@ -153,6 +153,10 @@ static inline uint64_t BUS_MESSAGE_COOKIE(sd_bus_message *m) { } static inline size_t BUS_MESSAGE_SIZE(sd_bus_message *m) { + /* Silence static analyzers, fields_size is validated at message creation */ + assert(ALIGN8(m->fields_size) != SIZE_MAX); + assert(ALIGN8(m->fields_size) <= SIZE_MAX - sizeof(BusMessageHeader)); + assert(m->body_size <= SIZE_MAX - sizeof(BusMessageHeader) - ALIGN8(m->fields_size)); return sizeof(BusMessageHeader) + ALIGN8(m->fields_size) + @@ -160,6 +164,9 @@ static inline size_t BUS_MESSAGE_SIZE(sd_bus_message *m) { } static inline size_t BUS_MESSAGE_BODY_BEGIN(sd_bus_message *m) { + /* Silence static analyzers, fields_size is validated at message creation */ + assert(ALIGN8(m->fields_size) != SIZE_MAX); + assert(ALIGN8(m->fields_size) <= SIZE_MAX - sizeof(BusMessageHeader)); return sizeof(BusMessageHeader) + ALIGN8(m->fields_size); diff --git a/src/libsystemd/sd-bus/bus-objects.c b/src/libsystemd/sd-bus/bus-objects.c index 30dcc3a81f860..83ba3a523992b 100644 --- a/src/libsystemd/sd-bus/bus-objects.c +++ b/src/libsystemd/sd-bus/bus-objects.c @@ -1704,6 +1704,10 @@ typedef enum { static bool names_are_valid(const char *signature, const char **names, names_flags *flags) { int r; + assert(signature); + assert(names); + assert(flags); + if ((*flags & NAMES_FIRST_PART || *flags & NAMES_SINGLE_PART) && **names != '\0') *flags |= NAMES_PRESENT; @@ -2401,7 +2405,7 @@ static int object_added_append_all_prefix( * skip it on any of its parents. The child vtables * always fully override any conflicting vtables of * any parent node. */ - if (ordered_set_get(s, c->interface)) + if (ordered_set_contains(s, c->interface)) continue; r = ordered_set_put(s, c->interface); @@ -2616,7 +2620,7 @@ static int object_removed_append_all_prefix( * skip it on any of its parents. The child vtables * always fully override any conflicting vtables of * any parent node. */ - if (ordered_set_get(s, c->interface)) + if (ordered_set_contains(s, c->interface)) continue; r = node_vtable_get_userdata(bus, path, c, &u, &error); diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c index 4cac317dffca6..638d650336b11 100644 --- a/src/libsystemd/sd-bus/bus-socket.c +++ b/src/libsystemd/sd-bus/bus-socket.c @@ -35,6 +35,7 @@ #define SNDBUF_SIZE (8*1024*1024) static void iovec_advance(struct iovec iov[], unsigned *idx, size_t size) { + assert(idx); while (size > 0) { struct iovec *i = iov + *idx; @@ -676,6 +677,8 @@ static int bus_socket_read_auth(sd_bus *b) { return -ECONNRESET; } + /* Silence static analyzers, k is bounded by iov size: n - rbuffer_size */ + assert((size_t) k <= n - b->rbuffer_size); b->rbuffer_size += k; if (handle_cmsg) { @@ -786,7 +789,7 @@ int bus_socket_start_auth(sd_bus *b) { bus_get_peercred(b); bus_set_state(b, BUS_AUTHENTICATING); - b->auth_timeout = now(CLOCK_MONOTONIC) + BUS_AUTH_TIMEOUT; + b->auth_timeout = usec_add(now(CLOCK_MONOTONIC), BUS_AUTH_TIMEOUT); if (sd_is_socket(b->input_fd, AF_UNIX, 0, 0) <= 0) b->accept_fd = false; @@ -1235,7 +1238,6 @@ int bus_socket_take_fd(sd_bus *b) { int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) { struct iovec *iov; ssize_t k; - size_t n; unsigned j; int r; @@ -1251,9 +1253,8 @@ int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) { if (r < 0) return r; - n = m->n_iovec * sizeof(struct iovec); - iov = newa(struct iovec, n); - memcpy_safe(iov, m->iovec, n); + iov = newa(struct iovec, m->n_iovec); + memcpy_safe(iov, m->iovec, sizeof(struct iovec) * m->n_iovec); j = 0; iovec_advance(iov, &j, *idx); @@ -1453,6 +1454,8 @@ int bus_socket_read_message(sd_bus *bus) { return -EXFULL; } + /* Silence static analyzers, k is bounded by iov size: need - rbuffer_size */ + assert((size_t) k <= need - bus->rbuffer_size); bus->rbuffer_size += k; if (handle_cmsg) { diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c index 9fdd8cbd66bf7..27f788d995576 100644 --- a/src/libsystemd/sd-bus/sd-bus.c +++ b/src/libsystemd/sd-bus/sd-bus.c @@ -1480,7 +1480,7 @@ int bus_set_address_system_remote(sd_bus *b, const char *host) { got_forward_slash = true; } - if (!in_charset(p, "0123456789") || *p == '\0') { + if (!in_charset(p, DIGITS) || *p == '\0') { if (!hostname_is_valid(p, 0) || got_forward_slash) return -EINVAL; @@ -1496,7 +1496,7 @@ int bus_set_address_system_remote(sd_bus *b, const char *host) { interpret_port_as_machine_old_syntax: /* Let's make sure this is not a port of some kind, * and is a valid machine name. */ - if (!in_charset(m, "0123456789") && hostname_is_valid(m, 0)) + if (!in_charset(m, DIGITS) && hostname_is_valid(m, 0)) c = strjoina(",argv", p ? "7" : "5", "=--machine=", m); } @@ -2021,6 +2021,7 @@ static int bus_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) { assert(bus); assert(m); + assert(idx); r = bus_socket_write_message(bus, m, idx); if (r <= 0) diff --git a/src/libsystemd/sd-bus/test-bus-benchmark.c b/src/libsystemd/sd-bus/test-bus-benchmark.c index 4063d79159a55..f35056aa8e44e 100644 --- a/src/libsystemd/sd-bus/test-bus-benchmark.c +++ b/src/libsystemd/sd-bus/test-bus-benchmark.c @@ -27,6 +27,8 @@ typedef enum Type { static void server(sd_bus *b, size_t *result) { int r; + assert(result); + for (;;) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; diff --git a/src/libsystemd/sd-bus/test-bus-chat.c b/src/libsystemd/sd-bus/test-bus-chat.c index 3544b4580b7ba..1f358ccd3396e 100644 --- a/src/libsystemd/sd-bus/test-bus-chat.c +++ b/src/libsystemd/sd-bus/test-bus-chat.c @@ -567,11 +567,17 @@ TEST(ctrunc) { /* The very first message should be the one we expect */ ASSERT_OK(get_one_message(bus, &recvd)); - ASSERT_TRUE(sd_bus_message_is_method_call(recvd, "org.freedesktop.systemd.test", "SendFds")); /* This needs to succeed or the following tests are going to be unhappy... */ ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &orig_rl), 0); + /* dbus-daemon disconnects peers when FDs get truncated + * https://github.com/systemd/systemd/issues/41150 */ + if (sd_bus_message_is_signal(recvd, "org.freedesktop.DBus.Local", "Disconnected") > 0) + return (void) log_tests_skipped("Running with dbus-daemon, which doesn't support fd passing with truncation"); + + ASSERT_TRUE(sd_bus_message_is_method_call(recvd, "org.freedesktop.systemd.test", "SendFds")); + /* Try to read all the fds. We expect at least one to fail with -EBADMSG due to * truncation, and all subsequent reads must also fail with -EBADMSG. */ int i; diff --git a/src/libsystemd/sd-bus/test-bus-error.c b/src/libsystemd/sd-bus/test-bus-error.c index db41141811f84..3f89a907eb654 100644 --- a/src/libsystemd/sd-bus/test-bus-error.c +++ b/src/libsystemd/sd-bus/test-bus-error.c @@ -154,13 +154,13 @@ TEST(errno_mapping_standard) { assert_se(sd_bus_error_set(NULL, "System.Error.WHATSIT", NULL) == -EIO); } -BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map test_errors[] = { +BUS_ERROR_MAP_ELF_REGISTER static const sd_bus_error_map test_errors[] = { SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error", 5), SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-2", 52), SD_BUS_ERROR_MAP_END }; -BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map test_errors2[] = { +BUS_ERROR_MAP_ELF_REGISTER static const sd_bus_error_map test_errors2[] = { SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-3", 33), SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-4", 44), SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-33", 333), diff --git a/src/libsystemd/sd-bus/test-bus-objects.c b/src/libsystemd/sd-bus/test-bus-objects.c index 62050b103db23..4ad60f0d58225 100644 --- a/src/libsystemd/sd-bus/test-bus-objects.c +++ b/src/libsystemd/sd-bus/test-bus-objects.c @@ -177,6 +177,7 @@ static const sd_bus_vtable vtable2[] = { }; static int enumerator_callback(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *reterr_error) { + assert(nodes); if (object_path_startswith("/value", path)) ASSERT_NOT_NULL(*nodes = strv_new("/value/c", "/value/b", "/value/a")); @@ -185,6 +186,7 @@ static int enumerator_callback(sd_bus *bus, const char *path, void *userdata, ch } static int enumerator2_callback(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *reterr_error) { + assert(nodes); if (object_path_startswith("/value/a", path)) ASSERT_NOT_NULL(*nodes = strv_new("/value/a/z", "/value/a/x", "/value/a/y")); @@ -195,6 +197,8 @@ static int enumerator2_callback(sd_bus *bus, const char *path, void *userdata, c static int enumerator3_callback(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *reterr_error) { _cleanup_strv_free_ char **v = NULL; + assert(nodes); + if (!object_path_startswith("/value/b", path)) return 1; diff --git a/src/libsystemd/sd-bus/test-bus-vtable.c b/src/libsystemd/sd-bus/test-bus-vtable.c index ae2ead550f9e2..84e75cd1e31a1 100644 --- a/src/libsystemd/sd-bus/test-bus-vtable.c +++ b/src/libsystemd/sd-bus/test-bus-vtable.c @@ -22,6 +22,7 @@ static struct context c = {}; static int happy_finder_object = 0; static int happy_finder(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *reterr_error) { + assert(found); assert(userdata); assert(userdata == &c); diff --git a/src/libsystemd/sd-daemon/sd-daemon.c b/src/libsystemd/sd-daemon/sd-daemon.c index 2ab50287b4ffa..da5242c3b799d 100644 --- a/src/libsystemd/sd-daemon/sd-daemon.c +++ b/src/libsystemd/sd-daemon/sd-daemon.c @@ -603,6 +603,8 @@ static int pid_notify_with_fds_internal( return log_debug_errno(errno, "Failed to send notify message to '%s': %m", e); /* If that failed, try with our own ucred instead */ + /* Silence static analyzers */ + assert(msghdr.msg_controllen >= CMSG_SPACE(sizeof(struct ucred))); msghdr.msg_controllen -= CMSG_SPACE(sizeof(struct ucred)); if (msghdr.msg_controllen == 0) msghdr.msg_control = NULL; @@ -619,7 +621,7 @@ static int pid_notify_with_fds_internal( msghdr.msg_control = NULL; msghdr.msg_controllen = 0; } - } while (!iovec_increment(msghdr.msg_iov, msghdr.msg_iovlen, n)); + } while (!iovec_inc_many(msghdr.msg_iov, msghdr.msg_iovlen, n)); if (address.sockaddr.sa.sa_family == AF_VSOCK && IN_SET(type, SOCK_STREAM, SOCK_SEQPACKET)) { /* For AF_VSOCK, we need to close the socket to signal the end of the message. */ diff --git a/src/libsystemd/sd-device/device-monitor.c b/src/libsystemd/sd-device/device-monitor.c index 67d68bb07f9ab..962329a865ebd 100644 --- a/src/libsystemd/sd-device/device-monitor.c +++ b/src/libsystemd/sd-device/device-monitor.c @@ -783,6 +783,8 @@ int device_monitor_send( static void bpf_stmt(struct sock_filter *ins, unsigned *i, unsigned short code, unsigned data) { + assert(i); + ins[(*i)++] = (struct sock_filter) { .code = code, .k = data, @@ -792,6 +794,8 @@ static void bpf_stmt(struct sock_filter *ins, unsigned *i, static void bpf_jmp(struct sock_filter *ins, unsigned *i, unsigned short code, unsigned data, unsigned short jt, unsigned short jf) { + assert(i); + ins[(*i)++] = (struct sock_filter) { .code = code, .jt = jt, diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index 526f183b1e9ec..ef67c649d1ebd 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -30,6 +30,7 @@ #include "string-util.h" #include "strv.h" #include "time-util.h" +#include "utf8.h" int device_new_aux(sd_device **ret) { sd_device *device; @@ -81,18 +82,40 @@ static sd_device* device_free(sd_device *device) { DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_device, sd_device, device_free); +static bool property_is_valid(const char *key, const char *value) { + /* Device properties may be saved to database file, then may be parsed from the file. When if a + * property contains spurious characters, then the parser may be confused. Let's refuse spurious + * properties, even if it is internal, which will not be saved to database file, for consistency. */ + + if (isempty(key) || !in_charset(key, ALPHANUMERICAL "_.")) + return false; + + /* an empty value means unset the property, hence that's fine. */ + if (isempty(value)) + return true; + + /* refuse invalid UTF8 and control characters */ + if (!utf8_is_valid(value) || string_has_cc(value, /* ok= */ NULL)) + return false; + + return true; +} + int device_add_property_aux(sd_device *device, const char *key, const char *value, bool db) { OrderedHashmap **properties; assert(device); assert(key); + if (!property_is_valid(key, value)) + return -EINVAL; + if (db) properties = &device->properties_db; else properties = &device->properties; - if (value) { + if (!isempty(value)) { _unused_ _cleanup_free_ char *old_value = NULL; _cleanup_free_ char *new_key = NULL, *new_value = NULL, *old_key = NULL; int r; @@ -1628,7 +1651,6 @@ bool device_has_devlink(sd_device *device, const char *devlink) { static int device_add_property_internal_from_string(sd_device *device, const char *str) { _cleanup_free_ char *key = NULL; char *value; - int r; assert(device); assert(str); @@ -1648,11 +1670,7 @@ static int device_add_property_internal_from_string(sd_device *device, const cha /* Add the property to both sd_device::properties and sd_device::properties_db, * as this is called by only handle_db_line(). */ - r = device_add_property_aux(device, key, value, false); - if (r < 0) - return r; - - return device_add_property_aux(device, key, value, true); + return device_add_property(device, key, value); } int device_set_usec_initialized(sd_device *device, usec_t when) { diff --git a/src/libsystemd/sd-device/test-sd-device-monitor.c b/src/libsystemd/sd-device/test-sd-device-monitor.c index c1c720b364b08..8d88f3eb6727a 100644 --- a/src/libsystemd/sd-device/test-sd-device-monitor.c +++ b/src/libsystemd/sd-device/test-sd-device-monitor.c @@ -20,6 +20,8 @@ static void prepare_loopback(sd_device **ret) { _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + assert(ret); + ASSERT_OK(sd_device_new_from_syspath(&dev, "/sys/class/net/lo")); ASSERT_OK(device_add_property(dev, "ACTION", "add")); ASSERT_OK(device_add_property(dev, "SEQNUM", "10")); @@ -32,6 +34,8 @@ static int prepare_sda(sd_device **ret) { _cleanup_(sd_device_unrefp) sd_device *dev = NULL; int r; + assert(ret); + r = sd_device_new_from_subsystem_sysname(&dev, "block", "sda"); if (r < 0) return r; @@ -55,6 +59,9 @@ static int monitor_handler(sd_device_monitor *m, sd_device *d, void *userdata) { static void prepare_monitor(sd_device_monitor **ret_server, sd_device_monitor **ret_client, union sockaddr_union *ret_address) { _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor_server = NULL, *monitor_client = NULL; + assert(ret_server); + assert(ret_client); + ASSERT_OK(device_monitor_new_full(&monitor_server, MONITOR_GROUP_NONE, -EBADF)); ASSERT_OK(sd_device_monitor_set_description(monitor_server, "sender")); ASSERT_OK(sd_device_monitor_start(monitor_server, NULL, NULL)); diff --git a/src/libsystemd/sd-device/test-sd-device.c b/src/libsystemd/sd-device/test-sd-device.c index db7c9420cc605..bf62e9082b56e 100644 --- a/src/libsystemd/sd-device/test-sd-device.c +++ b/src/libsystemd/sd-device/test-sd-device.c @@ -351,6 +351,9 @@ static void test_sd_device_enumerator_filter_subsystem_one( unsigned n_new_dev = 0, n_removed_dev = 0; sd_device *dev; + assert(ret_n_new_dev); + assert(ret_n_removed_dev); + ASSERT_OK(sd_device_enumerator_new(&e)); ASSERT_OK(sd_device_enumerator_add_match_subsystem(e, subsystem, true)); exclude_problematic_devices(e); @@ -841,13 +844,61 @@ TEST(devname_from_devnum) { } } +TEST(device_add_property) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + const char *val; + + ASSERT_OK(sd_device_new_from_syspath(&dev, "/sys/class/net/lo")); + + /* add a property */ + ASSERT_OK(device_add_property(dev, "hoge", "foo")); + ASSERT_OK(sd_device_get_property_value(dev, "hoge", &val)); + ASSERT_STREQ(val, "foo"); + + /* update an existing property */ + ASSERT_OK(device_add_property(dev, "hoge", "bar")); + ASSERT_OK(sd_device_get_property_value(dev, "hoge", &val)); + ASSERT_STREQ(val, "bar"); + + /* remove an existing property */ + ASSERT_OK(device_add_property(dev, "hoge", NULL)); + ASSERT_ERROR(sd_device_get_property_value(dev, "hoge", &val), ENOENT); + + /* add a property again */ + ASSERT_OK(device_add_property(dev, "hoge", "foo")); + ASSERT_OK(sd_device_get_property_value(dev, "hoge", &val)); + ASSERT_STREQ(val, "foo"); + + /* remove it with an empty string */ + ASSERT_OK(device_add_property(dev, "hoge", "")); + ASSERT_ERROR(sd_device_get_property_value(dev, "hoge", &val), ENOENT); + + /* check internal property (starting with dot) */ + ASSERT_OK(device_add_property(dev, ".hoge", "baz")); + ASSERT_OK(sd_device_get_property_value(dev, ".hoge", &val)); + ASSERT_STREQ(val, "baz"); + + /* refuse invalid property names */ + ASSERT_ERROR(device_add_property(dev, "hoge-hoge", "aaa"), EINVAL); + ASSERT_ERROR(device_add_property(dev, "hoge=hoge", "aaa"), EINVAL); + ASSERT_ERROR(device_add_property(dev, "hoge hoge", "aaa"), EINVAL); + ASSERT_ERROR(device_add_property(dev, "hoge\nhoge", "aaa"), EINVAL); + ASSERT_ERROR(device_add_property(dev, "hoge\rhoge", "aaa"), EINVAL); + ASSERT_ERROR(device_add_property(dev, "hoge\thoge", "aaa"), EINVAL); + + /* refuse invalid property values */ + ASSERT_ERROR(device_add_property(dev, "hoge", "aaa\naaa"), EINVAL); + ASSERT_ERROR(device_add_property(dev, "hoge", "aaa\raaa"), EINVAL); + ASSERT_ERROR(device_add_property(dev, "hoge", "aaa\taaa"), EINVAL); +} + static int intro(void) { int r; if (path_is_mount_point("/sys") <= 0) return log_tests_skipped("/sys/ is not mounted"); - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0) return log_tests_skipped("libmount not available."); diff --git a/src/libsystemd/sd-event/event-source.h b/src/libsystemd/sd-event/event-source.h index e4dc456fae8ea..8487c966ab409 100644 --- a/src/libsystemd/sd-event/event-source.h +++ b/src/libsystemd/sd-event/event-source.h @@ -26,6 +26,8 @@ typedef enum EventSourceType { SOURCE_WATCHDOG, SOURCE_INOTIFY, SOURCE_MEMORY_PRESSURE, + SOURCE_CPU_PRESSURE, + SOURCE_IO_PRESSURE, _SOURCE_EVENT_SOURCE_TYPE_MAX, _SOURCE_EVENT_SOURCE_TYPE_INVALID = -EINVAL, } EventSourceType; @@ -144,7 +146,7 @@ struct sd_event_source { size_t write_buffer_size; uint32_t events, revents; LIST_FIELDS(sd_event_source, write_list); - } memory_pressure; + } pressure; }; }; diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index b78cfe86fa40e..9e7ba7813cde9 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -76,6 +76,8 @@ static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] [SOURCE_WATCHDOG] = "watchdog", [SOURCE_INOTIFY] = "inotify", [SOURCE_MEMORY_PRESSURE] = "memory-pressure", + [SOURCE_CPU_PRESSURE] = "cpu-pressure", + [SOURCE_IO_PRESSURE] = "io-pressure", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); @@ -99,7 +101,9 @@ DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); SOURCE_SIGNAL, \ SOURCE_DEFER, \ SOURCE_INOTIFY, \ - SOURCE_MEMORY_PRESSURE) + SOURCE_MEMORY_PRESSURE, \ + SOURCE_CPU_PRESSURE, \ + SOURCE_IO_PRESSURE) /* This is used to assert that we didn't pass an unexpected source type to event_source_time_prioq_put(). * Time sources and ratelimited sources can be passed, so effectively this is the same as the @@ -144,8 +148,8 @@ struct sd_event { /* A list of inotify objects that already have events buffered which aren't processed yet */ LIST_HEAD(InotifyData, buffered_inotify_data_list); - /* A list of memory pressure event sources that still need their subscription string written */ - LIST_HEAD(sd_event_source, memory_pressure_write_list); + /* A list of pressure event sources that still need their subscription string written */ + LIST_HEAD(sd_event_source, pressure_write_list); uint64_t origin_id; @@ -564,63 +568,65 @@ static int source_child_pidfd_register(sd_event_source *s, int enabled) { return 0; } -static void source_memory_pressure_unregister(sd_event_source *s) { +#define EVENT_SOURCE_IS_PRESSURE(s) IN_SET((s)->type, SOURCE_MEMORY_PRESSURE, SOURCE_CPU_PRESSURE, SOURCE_IO_PRESSURE) + +static void source_pressure_unregister(sd_event_source *s) { assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); if (event_origin_changed(s->event)) return; - if (!s->memory_pressure.registered) + if (!s->pressure.registered) return; - if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->memory_pressure.fd, NULL) < 0) + if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->pressure.fd, NULL) < 0) log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll, ignoring: %m", strna(s->description), event_source_type_to_string(s->type)); - s->memory_pressure.registered = false; + s->pressure.registered = false; } -static int source_memory_pressure_register(sd_event_source *s, int enabled) { +static int source_pressure_register(sd_event_source *s, int enabled) { assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); assert(enabled != SD_EVENT_OFF); struct epoll_event ev = { - .events = s->memory_pressure.write_buffer_size > 0 ? EPOLLOUT : - (s->memory_pressure.events | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0)), + .events = s->pressure.write_buffer_size > 0 ? EPOLLOUT : + (s->pressure.events | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0)), .data.ptr = s, }; if (epoll_ctl(s->event->epoll_fd, - s->memory_pressure.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, - s->memory_pressure.fd, &ev) < 0) + s->pressure.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, + s->pressure.fd, &ev) < 0) return -errno; - s->memory_pressure.registered = true; + s->pressure.registered = true; return 0; } -static void source_memory_pressure_add_to_write_list(sd_event_source *s) { +static void source_pressure_add_to_write_list(sd_event_source *s) { assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); - if (s->memory_pressure.in_write_list) + if (s->pressure.in_write_list) return; - LIST_PREPEND(memory_pressure.write_list, s->event->memory_pressure_write_list, s); - s->memory_pressure.in_write_list = true; + LIST_PREPEND(pressure.write_list, s->event->pressure_write_list, s); + s->pressure.in_write_list = true; } -static void source_memory_pressure_remove_from_write_list(sd_event_source *s) { +static void source_pressure_remove_from_write_list(sd_event_source *s) { assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); - if (!s->memory_pressure.in_write_list) + if (!s->pressure.in_write_list) return; - LIST_REMOVE(memory_pressure.write_list, s->event->memory_pressure_write_list, s); - s->memory_pressure.in_write_list = false; + LIST_REMOVE(pressure.write_list, s->event->pressure_write_list, s); + s->pressure.in_write_list = false; } static clockid_t event_source_type_to_clock(EventSourceType t) { @@ -1047,8 +1053,10 @@ static void source_disconnect(sd_event_source *s) { } case SOURCE_MEMORY_PRESSURE: - source_memory_pressure_remove_from_write_list(s); - source_memory_pressure_unregister(s); + case SOURCE_CPU_PRESSURE: + case SOURCE_IO_PRESSURE: + source_pressure_remove_from_write_list(s); + source_pressure_unregister(s); break; default: @@ -1111,9 +1119,9 @@ static sd_event_source* source_free(sd_event_source *s) { s->child.pidfd = safe_close(s->child.pidfd); } - if (s->type == SOURCE_MEMORY_PRESSURE) { - s->memory_pressure.fd = safe_close(s->memory_pressure.fd); - s->memory_pressure.write_buffer = mfree(s->memory_pressure.write_buffer); + if (EVENT_SOURCE_IS_PRESSURE(s)) { + s->pressure.fd = safe_close(s->pressure.fd); + s->pressure.write_buffer = mfree(s->pressure.write_buffer); } if (s->destroy_callback) @@ -1191,7 +1199,9 @@ static sd_event_source* source_new(sd_event *e, bool floating, EventSourceType t [SOURCE_POST] = endoffsetof_field(sd_event_source, post), [SOURCE_EXIT] = endoffsetof_field(sd_event_source, exit), [SOURCE_INOTIFY] = endoffsetof_field(sd_event_source, inotify), - [SOURCE_MEMORY_PRESSURE] = endoffsetof_field(sd_event_source, memory_pressure), + [SOURCE_MEMORY_PRESSURE] = endoffsetof_field(sd_event_source, pressure), + [SOURCE_CPU_PRESSURE] = endoffsetof_field(sd_event_source, pressure), + [SOURCE_IO_PRESSURE] = endoffsetof_field(sd_event_source, pressure), }; sd_event_source *s; @@ -1917,17 +1927,21 @@ static int memory_pressure_callback(sd_event_source *s, void *userdata) { return 0; } -_public_ int sd_event_add_memory_pressure( +static int event_add_pressure( sd_event *e, sd_event_source **ret, sd_event_handler_t callback, - void *userdata) { + void *userdata, + EventSourceType type, + sd_event_handler_t default_callback, + PressureResource resource) { _cleanup_free_ char *w = NULL; _cleanup_(source_freep) sd_event_source *s = NULL; _cleanup_close_ int path_fd = -EBADF, fd = -EBADF; _cleanup_free_ void *write_buffer = NULL; - const char *watch, *watch_fallback = NULL, *env; + _cleanup_free_ char *watch_fallback = NULL; + const char *watch, *env; size_t write_buffer_size = 0; struct stat st; uint32_t events; @@ -1939,32 +1953,34 @@ _public_ int sd_event_add_memory_pressure( assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); assert_return(!event_origin_changed(e), -ECHILD); + const PressureResourceInfo *info = pressure_resource_get_info(resource); + if (!callback) - callback = memory_pressure_callback; + callback = default_callback; - s = source_new(e, !ret, SOURCE_MEMORY_PRESSURE); + s = source_new(e, !ret, type); if (!s) return -ENOMEM; s->wakeup = WAKEUP_EVENT_SOURCE; - s->memory_pressure.callback = callback; + s->pressure.callback = callback; s->userdata = userdata; s->enabled = SD_EVENT_ON; - s->memory_pressure.fd = -EBADF; + s->pressure.fd = -EBADF; - env = secure_getenv("MEMORY_PRESSURE_WATCH"); + env = secure_getenv(info->env_watch); if (env) { if (isempty(env) || path_equal(env, "/dev/null")) return log_debug_errno(SYNTHETIC_ERRNO(EHOSTDOWN), - "Memory pressure logic is explicitly disabled via $MEMORY_PRESSURE_WATCH."); + "Pressure logic is explicitly disabled via $%s.", info->env_watch); if (!path_is_absolute(env) || !path_is_normalized(env)) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), - "$MEMORY_PRESSURE_WATCH set to invalid path: %s", env); + "$%s set to invalid path: %s", info->env_watch, env); watch = env; - env = secure_getenv("MEMORY_PRESSURE_WRITE"); + env = secure_getenv(info->env_write); if (env) { r = unbase64mem(env, &write_buffer, &write_buffer_size); if (r < 0) @@ -1980,8 +1996,8 @@ _public_ int sd_event_add_memory_pressure( if (r == 0) return -EOPNOTSUPP; - /* By default we want to watch memory pressure on the local cgroup, but we'll fall back on - * the system wide pressure if for some reason we cannot (which could be: memory controller + /* By default we want to watch pressure on the local cgroup, but we'll fall back on + * the system wide pressure if for some reason we cannot (which could be: controller * not delegated to us, or PSI simply not available in the kernel). */ _cleanup_free_ char *cg = NULL; @@ -1989,12 +2005,19 @@ _public_ int sd_event_add_memory_pressure( if (r < 0) return r; - w = path_join("/sys/fs/cgroup", cg, "memory.pressure"); + _cleanup_free_ char *cgroup_file = strjoin(info->name, ".pressure"); + if (!cgroup_file) + return -ENOMEM; + + w = path_join("/sys/fs/cgroup", cg, cgroup_file); if (!w) return -ENOMEM; watch = w; - watch_fallback = "/proc/pressure/memory"; + + watch_fallback = strjoin("/proc/pressure/", info->name); + if (!watch_fallback) + return -ENOMEM; /* Android uses three levels in its userspace low memory killer logic: * some 70000 1000000 @@ -2011,9 +2034,9 @@ _public_ int sd_event_add_memory_pressure( * kernel will allow us to do unprivileged, also in the future. */ if (asprintf((char**) &write_buffer, "%s " USEC_FMT " " USEC_FMT, - MEMORY_PRESSURE_DEFAULT_TYPE, - MEMORY_PRESSURE_DEFAULT_THRESHOLD_USEC, - MEMORY_PRESSURE_DEFAULT_WINDOW_USEC) < 0) + PRESSURE_DEFAULT_TYPE, + PRESSURE_DEFAULT_THRESHOLD_USEC, + PRESSURE_DEFAULT_WINDOW_USEC) < 0) return -ENOMEM; write_buffer_size = strlen(write_buffer) + 1; @@ -2025,11 +2048,13 @@ _public_ int sd_event_add_memory_pressure( if (errno != ENOENT) return -errno; - /* We got ENOENT. Three options now: try the fallback if we have one, or return the error as - * is (if based on user/env config), or return -EOPNOTSUPP (because we picked the path, and - * the PSI service apparently is not supported) */ - if (!watch_fallback) - return locked ? -ENOENT : -EOPNOTSUPP; + /* We got ENOENT. Two options now: try the fallback if we have one, or return the error as is + * (when based on user/env config). */ + + if (!watch_fallback) { + assert(locked); + return -ENOENT; + } path_fd = open(watch_fallback, O_PATH|O_CLOEXEC); if (path_fd < 0) { @@ -2080,24 +2105,24 @@ _public_ int sd_event_add_memory_pressure( else return -EBADF; - s->memory_pressure.fd = TAKE_FD(fd); - s->memory_pressure.write_buffer = TAKE_PTR(write_buffer); - s->memory_pressure.write_buffer_size = write_buffer_size; - s->memory_pressure.events = events; - s->memory_pressure.locked = locked; + s->pressure.fd = TAKE_FD(fd); + s->pressure.write_buffer = TAKE_PTR(write_buffer); + s->pressure.write_buffer_size = write_buffer_size; + s->pressure.events = events; + s->pressure.locked = locked; /* So here's the thing: if we are talking to PSI we need to write the watch string before adding the * fd to epoll (if we ignore this, then the watch won't work). Hence we'll not actually register the - * fd with the epoll right-away. Instead, we just add the event source to a list of memory pressure - * event sources on which writes must be executed before the first event loop iteration is - * executed. (We could also write the data here, right away, but we want to give the caller the - * freedom to call sd_event_source_set_memory_pressure_type() and - * sd_event_source_set_memory_pressure_rate() before we write it. */ - - if (s->memory_pressure.write_buffer_size > 0) - source_memory_pressure_add_to_write_list(s); + * fd with the epoll right-away. Instead, we just add the event source to a list of pressure event + * sources on which writes must be executed before the first event loop iteration is executed. (We + * could also write the data here, right away, but we want to give the caller the freedom to call + * sd_event_source_set_{memory,cpu,io}_pressure_type() and + * sd_event_source_set_{memory,cpu,io}_pressure_period() before we write it. */ + + if (s->pressure.write_buffer_size > 0) + source_pressure_add_to_write_list(s); else { - r = source_memory_pressure_register(s, s->enabled); + r = source_pressure_register(s, s->enabled); if (r < 0) return r; } @@ -2109,6 +2134,57 @@ _public_ int sd_event_add_memory_pressure( return 0; } +_public_ int sd_event_add_memory_pressure( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + return event_add_pressure( + e, ret, callback, userdata, + SOURCE_MEMORY_PRESSURE, + memory_pressure_callback, + PRESSURE_MEMORY); +} + +static int cpu_pressure_callback(sd_event_source *s, void *userdata) { + assert(s); + + return 0; +} + +_public_ int sd_event_add_cpu_pressure( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + return event_add_pressure( + e, ret, callback, userdata, + SOURCE_CPU_PRESSURE, + cpu_pressure_callback, + PRESSURE_CPU); +} + +static int io_pressure_callback(sd_event_source *s, void *userdata) { + assert(s); + + return 0; +} + +_public_ int sd_event_add_io_pressure( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + return event_add_pressure( + e, ret, callback, userdata, + SOURCE_IO_PRESSURE, + io_pressure_callback, + PRESSURE_IO); +} + static void event_free_inotify_data(sd_event *e, InotifyData *d) { assert(e); @@ -2910,7 +2986,9 @@ static int event_source_offline( break; case SOURCE_MEMORY_PRESSURE: - source_memory_pressure_unregister(s); + case SOURCE_CPU_PRESSURE: + case SOURCE_IO_PRESSURE: + source_pressure_unregister(s); break; case SOURCE_TIME_REALTIME: @@ -3001,10 +3079,12 @@ static int event_source_online( break; case SOURCE_MEMORY_PRESSURE: - /* As documented in sd_event_add_memory_pressure(), we can only register the PSI fd with - * epoll after writing the watch string. */ - if (s->memory_pressure.write_buffer_size == 0) { - r = source_memory_pressure_register(s, enabled); + case SOURCE_CPU_PRESSURE: + case SOURCE_IO_PRESSURE: + /* As documented in sd_event_add_{memory,cpu,io}_pressure(), we can only register the PSI fd + * with epoll after writing the watch string. */ + if (s->pressure.write_buffer_size == 0) { + r = source_pressure_register(s, enabled); if (r < 0) return r; } @@ -3804,6 +3884,9 @@ static int process_signal(sd_event *e, struct signal_data *d, uint32_t events, i if (_unlikely_(n != sizeof(si))) return -EIO; + if (_unlikely_(si.ssi_signo > INT_MAX)) /* Ensure value fits in int before casting */ + return -EIO; + if (_unlikely_(!SIGNAL_VALID(si.ssi_signo))) return -EIO; @@ -3983,30 +4066,30 @@ static int process_inotify(sd_event *e) { return done; } -static int process_memory_pressure(sd_event_source *s, uint32_t revents) { +static int process_pressure(sd_event_source *s, uint32_t revents) { assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); if (s->pending) - s->memory_pressure.revents |= revents; + s->pressure.revents |= revents; else - s->memory_pressure.revents = revents; + s->pressure.revents = revents; return source_set_pending(s, true); } -static int source_memory_pressure_write(sd_event_source *s) { +static int source_pressure_write(sd_event_source *s) { ssize_t n; int r; assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); /* once we start writing, the buffer is locked, we allow no further changes. */ - s->memory_pressure.locked = true; + s->pressure.locked = true; - if (s->memory_pressure.write_buffer_size > 0) { - n = write(s->memory_pressure.fd, s->memory_pressure.write_buffer, s->memory_pressure.write_buffer_size); + if (s->pressure.write_buffer_size > 0) { + n = write(s->pressure.fd, s->pressure.write_buffer, s->pressure.write_buffer_size); if (n < 0) { if (!ERRNO_IS_TRANSIENT(errno)) { /* If kernel is built with CONFIG_PSI_DEFAULT_DISABLED it will expose PSI @@ -4015,7 +4098,7 @@ static int source_memory_pressure_write(sd_event_source *s) { * so late. Let's make the best of it, and turn off the event source like we * do for failed event source handlers. */ - log_debug_errno(errno, "Writing memory pressure settings to kernel failed, disabling memory pressure event source: %m"); + log_debug_errno(errno, "Writing pressure settings to kernel failed, disabling pressure event source: %m"); assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0); return 0; } @@ -4027,41 +4110,41 @@ static int source_memory_pressure_write(sd_event_source *s) { assert(n >= 0); - if ((size_t) n == s->memory_pressure.write_buffer_size) { - s->memory_pressure.write_buffer = mfree(s->memory_pressure.write_buffer); + if ((size_t) n == s->pressure.write_buffer_size) { + s->pressure.write_buffer = mfree(s->pressure.write_buffer); if (n > 0) { - s->memory_pressure.write_buffer_size = 0; + s->pressure.write_buffer_size = 0; /* Update epoll events mask, since we have now written everything and don't care for EPOLLOUT anymore */ - r = source_memory_pressure_register(s, s->enabled); + r = source_pressure_register(s, s->enabled); if (r < 0) return r; } } else if (n > 0) { _cleanup_free_ void *c = NULL; - assert((size_t) n < s->memory_pressure.write_buffer_size); + assert((size_t) n < s->pressure.write_buffer_size); - c = memdup((uint8_t*) s->memory_pressure.write_buffer + n, s->memory_pressure.write_buffer_size - n); + c = memdup((uint8_t*) s->pressure.write_buffer + n, s->pressure.write_buffer_size - n); if (!c) return -ENOMEM; - free_and_replace(s->memory_pressure.write_buffer, c); - s->memory_pressure.write_buffer_size -= n; + free_and_replace(s->pressure.write_buffer, c); + s->pressure.write_buffer_size -= n; return 1; } return 0; } -static int source_memory_pressure_initiate_dispatch(sd_event_source *s) { +static int source_pressure_initiate_dispatch(sd_event_source *s) { int r; assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); - r = source_memory_pressure_write(s); + r = source_pressure_write(s); if (r < 0) return r; if (r > 0) @@ -4069,22 +4152,22 @@ static int source_memory_pressure_initiate_dispatch(sd_event_source *s) { * function. Instead, shortcut it so that we wait for next EPOLLOUT immediately. */ /* No pending incoming IO? Then let's not continue further */ - if ((s->memory_pressure.revents & (EPOLLIN|EPOLLPRI)) == 0) { + if ((s->pressure.revents & (EPOLLIN|EPOLLPRI)) == 0) { /* Treat IO errors on the notifier the same ways errors returned from a callback */ - if ((s->memory_pressure.revents & (EPOLLHUP|EPOLLERR|EPOLLRDHUP)) != 0) + if ((s->pressure.revents & (EPOLLHUP|EPOLLERR|EPOLLRDHUP)) != 0) return -EIO; return 1; /* leave dispatch, we already processed everything */ } - if (s->memory_pressure.revents & EPOLLIN) { + if (s->pressure.revents & EPOLLIN) { uint8_t pipe_buf[PIPE_BUF]; ssize_t n; /* If the fd is readable, then flush out anything that might be queued */ - n = read(s->memory_pressure.fd, pipe_buf, sizeof(pipe_buf)); + n = read(s->pressure.fd, pipe_buf, sizeof(pipe_buf)); if (n < 0 && !ERRNO_IS_TRANSIENT(errno)) return -errno; } @@ -4155,8 +4238,8 @@ static int source_dispatch(sd_event_source *s) { if (r < 0) return r; - if (s->type == SOURCE_MEMORY_PRESSURE) { - r = source_memory_pressure_initiate_dispatch(s); + if (EVENT_SOURCE_IS_PRESSURE(s)) { + r = source_pressure_initiate_dispatch(s); if (r == -EIO) /* handle EIO errors similar to callback errors */ goto finish; if (r < 0) @@ -4251,7 +4334,9 @@ static int source_dispatch(sd_event_source *s) { } case SOURCE_MEMORY_PRESSURE: - r = s->memory_pressure.callback(s, s->userdata); + case SOURCE_CPU_PRESSURE: + case SOURCE_IO_PRESSURE: + r = s->pressure.callback(s, s->userdata); break; case SOURCE_WATCHDOG: @@ -4419,7 +4504,7 @@ static void event_close_inode_data_fds(sd_event *e) { } } -static int event_memory_pressure_write_list(sd_event *e) { +static int event_pressure_write_list(sd_event *e) { int r; assert(e); @@ -4427,15 +4512,15 @@ static int event_memory_pressure_write_list(sd_event *e) { for (;;) { sd_event_source *s; - s = LIST_POP(memory_pressure.write_list, e->memory_pressure_write_list); + s = LIST_POP(pressure.write_list, e->pressure_write_list); if (!s) break; - assert(s->type == SOURCE_MEMORY_PRESSURE); - assert(s->memory_pressure.write_buffer_size > 0); - s->memory_pressure.in_write_list = false; + assert(EVENT_SOURCE_IS_PRESSURE(s)); + assert(s->pressure.write_buffer_size > 0); + s->pressure.in_write_list = false; - r = source_memory_pressure_write(s); + r = source_pressure_write(s); if (r < 0) return r; } @@ -4496,7 +4581,7 @@ _public_ int sd_event_prepare(sd_event *e) { if (r < 0) return r; - r = event_memory_pressure_write_list(e); + r = event_pressure_write_list(e); if (r < 0) return r; @@ -4665,7 +4750,9 @@ static int process_epoll(sd_event *e, usec_t timeout, int64_t threshold, int64_t break; case SOURCE_MEMORY_PRESSURE: - r = process_memory_pressure(s, i->events); + case SOURCE_CPU_PRESSURE: + case SOURCE_IO_PRESSURE: + r = process_pressure(s, i->events); break; default: @@ -5074,6 +5161,7 @@ _public_ int sd_event_get_watchdog(sd_event *e) { _public_ int sd_event_get_iteration(sd_event *e, uint64_t *ret) { assert_return(e, -EINVAL); assert_return(e = event_resolve(e), -ENOPKG); + assert_return(ret, -EINVAL); assert_return(!event_origin_changed(e), -ECHILD); *ret = e->iteration; @@ -5302,27 +5390,27 @@ _public_ int sd_event_get_exit_on_idle(sd_event *e) { return e->exit_on_idle; } -_public_ int sd_event_source_set_memory_pressure_type(sd_event_source *s, const char *ty) { +static int event_source_set_pressure_type(sd_event_source *s, const char *ty) { _cleanup_free_ char *b = NULL; _cleanup_free_ void *w = NULL; assert_return(s, -EINVAL); - assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM); + assert_return(EVENT_SOURCE_IS_PRESSURE(s), -EDOM); assert_return(ty, -EINVAL); assert_return(!event_origin_changed(s->event), -ECHILD); if (!STR_IN_SET(ty, "some", "full")) return -EINVAL; - if (s->memory_pressure.locked) /* Refuse adjusting parameters, if caller told us how to watch for events */ + if (s->pressure.locked) /* Refuse adjusting parameters, if caller told us how to watch for events */ return -EBUSY; - char* space = memchr(s->memory_pressure.write_buffer, ' ', s->memory_pressure.write_buffer_size); + char* space = memchr(s->pressure.write_buffer, ' ', s->pressure.write_buffer_size); if (!space) return -EINVAL; - size_t l = space - (char*) s->memory_pressure.write_buffer; - b = memdup_suffix0(s->memory_pressure.write_buffer, l); + size_t l = space - (char*) s->pressure.write_buffer; + b = memdup_suffix0(s->pressure.write_buffer, l); if (!b) return -ENOMEM; if (!STR_IN_SET(b, "some", "full")) @@ -5331,26 +5419,47 @@ _public_ int sd_event_source_set_memory_pressure_type(sd_event_source *s, const if (streq(b, ty)) return 0; - size_t nl = strlen(ty) + (s->memory_pressure.write_buffer_size - l); + size_t nl = strlen(ty) + (s->pressure.write_buffer_size - l); w = new(char, nl); if (!w) return -ENOMEM; - memcpy(stpcpy(w, ty), space, (s->memory_pressure.write_buffer_size - l)); + memcpy(stpcpy(w, ty), space, (s->pressure.write_buffer_size - l)); - free_and_replace(s->memory_pressure.write_buffer, w); - s->memory_pressure.write_buffer_size = nl; - s->memory_pressure.locked = false; + free_and_replace(s->pressure.write_buffer, w); + s->pressure.write_buffer_size = nl; + s->pressure.locked = false; return 1; } -_public_ int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) { +_public_ int sd_event_source_set_memory_pressure_type(sd_event_source *s, const char *ty) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM); + + return event_source_set_pressure_type(s, ty); +} + +_public_ int sd_event_source_set_cpu_pressure_type(sd_event_source *s, const char *ty) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CPU_PRESSURE, -EDOM); + + return event_source_set_pressure_type(s, ty); +} + +_public_ int sd_event_source_set_io_pressure_type(sd_event_source *s, const char *ty) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO_PRESSURE, -EDOM); + + return event_source_set_pressure_type(s, ty); +} + +static int event_source_set_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) { _cleanup_free_ char *b = NULL; _cleanup_free_ void *w = NULL; assert_return(s, -EINVAL); - assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM); + assert_return(EVENT_SOURCE_IS_PRESSURE(s), -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); if (threshold_usec <= 0 || threshold_usec >= UINT64_MAX) @@ -5360,15 +5469,15 @@ _public_ int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint if (threshold_usec > window_usec) return -EINVAL; - if (s->memory_pressure.locked) /* Refuse adjusting parameters, if caller told us how to watch for events */ + if (s->pressure.locked) /* Refuse adjusting parameters, if caller told us how to watch for events */ return -EBUSY; - char* space = memchr(s->memory_pressure.write_buffer, ' ', s->memory_pressure.write_buffer_size); + char* space = memchr(s->pressure.write_buffer, ' ', s->pressure.write_buffer_size); if (!space) return -EINVAL; - size_t l = space - (char*) s->memory_pressure.write_buffer; - b = memdup_suffix0(s->memory_pressure.write_buffer, l); + size_t l = space - (char*) s->pressure.write_buffer; + b = memdup_suffix0(s->pressure.write_buffer, l); if (!b) return -ENOMEM; if (!STR_IN_SET(b, "some", "full")) @@ -5382,12 +5491,33 @@ _public_ int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint return -EINVAL; l = strlen(w) + 1; - if (memcmp_nn(s->memory_pressure.write_buffer, s->memory_pressure.write_buffer_size, w, l) == 0) + if (memcmp_nn(s->pressure.write_buffer, s->pressure.write_buffer_size, w, l) == 0) return 0; - free_and_replace(s->memory_pressure.write_buffer, w); - s->memory_pressure.write_buffer_size = l; - s->memory_pressure.locked = false; + free_and_replace(s->pressure.write_buffer, w); + s->pressure.write_buffer_size = l; + s->pressure.locked = false; return 1; } + +_public_ int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM); + + return event_source_set_pressure_period(s, threshold_usec, window_usec); +} + +_public_ int sd_event_source_set_cpu_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CPU_PRESSURE, -EDOM); + + return event_source_set_pressure_period(s, threshold_usec, window_usec); +} + +_public_ int sd_event_source_set_io_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO_PRESSURE, -EDOM); + + return event_source_set_pressure_period(s, threshold_usec, window_usec); +} diff --git a/src/libsystemd/sd-id128/id128-util.c b/src/libsystemd/sd-id128/id128-util.c index 9d406a45d1316..700c9268edac5 100644 --- a/src/libsystemd/sd-id128/id128-util.c +++ b/src/libsystemd/sd-id128/id128-util.c @@ -199,6 +199,8 @@ int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id) { } void id128_hash_func(const sd_id128_t *p, struct siphash *state) { + assert(p); + siphash24_compress_typesafe(*p, state); } diff --git a/src/libsystemd/sd-id128/sd-id128.c b/src/libsystemd/sd-id128/sd-id128.c index f2a7209a257dc..852b01ab1a3f5 100644 --- a/src/libsystemd/sd-id128/sd-id128.c +++ b/src/libsystemd/sd-id128/sd-id128.c @@ -147,7 +147,7 @@ int id128_get_machine_at(int rfd, sd_id128_t *ret) { return sd_id128_get_machine(ret); _cleanup_close_ int fd = - chase_and_openat(rfd, "/etc/machine-id", CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC|O_NOCTTY, /* ret_path= */ NULL); + chase_and_openat(rfd, rfd, "/etc/machine-id", CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC|O_NOCTTY, /* ret_path= */ NULL); if (fd < 0) return fd; diff --git a/src/libsystemd/sd-journal/catalog.c b/src/libsystemd/sd-journal/catalog.c index 9ddbc11089a65..5d91b6a0e5344 100644 --- a/src/libsystemd/sd-journal/catalog.c +++ b/src/libsystemd/sd-journal/catalog.c @@ -28,7 +28,7 @@ #include "strv.h" #include "tmpfile-util.h" -const char * const catalog_file_dirs[] = { +static const char * const catalog_file_dirs[] = { "/usr/local/lib/systemd/catalog/", "/usr/lib/systemd/catalog/", NULL @@ -451,7 +451,7 @@ int catalog_update(const char *database, const char *root, const char* const *di ConfFile **files = NULL; size_t n_files = 0; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); r = conf_files_list_strv_full(".catalog", root, CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED | CONF_FILES_WARN, diff --git a/src/libsystemd/sd-journal/journal-def.h b/src/libsystemd/sd-journal/journal-def.h index f20c9a357a195..84849a1e9506d 100644 --- a/src/libsystemd/sd-journal/journal-def.h +++ b/src/libsystemd/sd-journal/journal-def.h @@ -6,6 +6,21 @@ #include "sd-forward.h" #include "sparse-endian.h" +/* Make sure not to make this smaller than the maximum coredump size. + * See JOURNAL_SIZE_MAX in coredump-config.h */ +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +#define ENTRY_SIZE_MAX (1024*1024*770u) +#define ENTRY_SIZE_UNPRIV_MAX (1024*1024*32u) +#define DATA_SIZE_MAX (1024*1024*768u) +#else +#define ENTRY_SIZE_MAX (1024*1024*13u) +#define ENTRY_SIZE_UNPRIV_MAX (1024*1024*8u) +#define DATA_SIZE_MAX (1024*1024*11u) +#endif + +/* The maximum number of fields in an entry */ +#define ENTRY_FIELD_COUNT_MAX 1024u + /* * If you change this file you probably should also change its documentation: * diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index de5d7075e0cca..a4b270ceb728d 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -370,7 +370,7 @@ static Compression getenv_compression(void) { if (r >= 0) return r ? DEFAULT_COMPRESSION : COMPRESSION_NONE; - c = compression_from_string(e); + c = compression_from_string_harder(e); if (c < 0) { log_debug_errno(c, "Failed to parse SYSTEMD_JOURNAL_COMPRESS value, ignoring: %s", e); return DEFAULT_COMPRESSION; @@ -1965,9 +1965,13 @@ static int maybe_decompress_payload( *ret_size = 0; return 0; } + + /* Caller only wants to check field existence, skip full decompression */ + if (!ret_data && !ret_size) + return 1; } - r = decompress_blob(compression, payload, size, &f->compress_buffer, &rsize, 0); + r = decompress_blob(compression, payload, size, &f->compress_buffer, &rsize, DATA_SIZE_MAX); if (r < 0) return r; @@ -2107,6 +2111,8 @@ static int link_entry_into_array( assert(f->header); assert(first); assert(idx); + POINTER_MAY_BE_NULL(tail); + POINTER_MAY_BE_NULL(tidx); assert(p > 0); a = tail ? le32toh(*tail) : le64toh(*first); @@ -4058,6 +4064,8 @@ static void journal_default_metrics(JournalMetrics *m, int fd, bool compact) { if (m->max_size < JOURNAL_FILE_SIZE_MIN) m->max_size = JOURNAL_FILE_SIZE_MIN; + /* Silence static analyzers */ + assert(m->max_size <= UINT64_MAX / 2); if (m->max_use != 0 && m->max_size*2 > m->max_use) m->max_use = m->max_size*2; } diff --git a/src/libsystemd/sd-journal/journal-send.c b/src/libsystemd/sd-journal/journal-send.c index 5c7b007b131b2..931e669a08926 100644 --- a/src/libsystemd/sd-journal/journal-send.c +++ b/src/libsystemd/sd-journal/journal-send.c @@ -273,13 +273,11 @@ _public_ int sd_journal_sendv(const struct iovec *iov, int n) { } if (!have_syslog_identifier && - string_is_safe(program_invocation_short_name)) { + string_is_safe(program_invocation_short_name, /* flags= */ 0)) { - /* Implicitly add program_invocation_short_name, if it - * is not set explicitly. We only do this for - * program_invocation_short_name, and nothing else - * since everything else is much nicer to retrieve - * from the outside. */ + /* Implicitly add program_invocation_short_name, if it is not set explicitly. We only do this + * for program_invocation_short_name, and nothing else since everything else is much nicer to + * retrieve from the outside. */ w[j++] = IOVEC_MAKE_STRING("SYSLOG_IDENTIFIER="); w[j++] = IOVEC_MAKE_STRING(program_invocation_short_name); diff --git a/src/libsystemd/sd-journal/journal-vacuum.c b/src/libsystemd/sd-journal/journal-vacuum.c index fade159820d87..a49c073b3e572 100644 --- a/src/libsystemd/sd-journal/journal-vacuum.c +++ b/src/libsystemd/sd-journal/journal-vacuum.c @@ -21,7 +21,7 @@ #include "time-util.h" #include "xattr-util.h" -typedef struct vacuum_info { +typedef struct VacuumInfo { uint64_t usage; char *filename; @@ -30,9 +30,9 @@ typedef struct vacuum_info { sd_id128_t seqnum_id; uint64_t seqnum; bool have_seqnum; -} vacuum_info; +} VacuumInfo; -static int vacuum_info_compare(const vacuum_info *a, const vacuum_info *b) { +static int vacuum_info_compare(const VacuumInfo *a, const VacuumInfo *b) { int r; if (a->have_seqnum && b->have_seqnum && @@ -49,16 +49,13 @@ static int vacuum_info_compare(const vacuum_info *a, const vacuum_info *b) { return strcmp(a->filename, b->filename); } -static void vacuum_info_array_free(vacuum_info *list, size_t n) { - if (!list) - return; - - FOREACH_ARRAY(i, list, n) - free(i->filename); - - free(list); +static void vacuum_info_done(VacuumInfo *info) { + assert(info); + info->filename = mfree(info->filename); } +static DEFINE_ARRAY_FREE_FUNC(vacuum_info_array_free, VacuumInfo, vacuum_info_done); + static void patch_realtime( int fd, const char *fn, @@ -137,7 +134,7 @@ int journal_directory_vacuum( uint64_t sum = 0, freed = 0, n_active_files = 0; size_t n_list = 0, i; _cleanup_closedir_ DIR *d = NULL; - vacuum_info *list = NULL; + VacuumInfo *list = NULL; usec_t retention_limit = 0; int r; @@ -280,7 +277,7 @@ int journal_directory_vacuum( if (!GREEDY_REALLOC(list, n_list + 1)) return -ENOMEM; - list[n_list++] = (vacuum_info) { + list[n_list++] = (VacuumInfo) { .filename = TAKE_PTR(p), .usage = size, .seqnum = seqnum, diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index 0afb664896aca..11532c5f8a809 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -23,6 +23,8 @@ static void draw_progress(uint64_t p, usec_t *last_usec) { unsigned n, i, j, k; usec_t z, x; + assert(last_usec); + if (!on_tty()) return; @@ -124,7 +126,7 @@ static int hash_payload(JournalFile *f, Object *o, uint64_t offset, const uint8_ _cleanup_free_ void *b = NULL; size_t b_size; - r = decompress_blob(c, src, size, &b, &b_size, 0); + r = decompress_blob(c, src, size, &b, &b_size, DATA_SIZE_MAX); if (r < 0) { error_errno(offset, r, "%s decompression failed: %m", compression_to_string(c)); diff --git a/src/libsystemd/sd-journal/sd-journal.c b/src/libsystemd/sd-journal/sd-journal.c index befa1945176f3..52b3e616a172e 100644 --- a/src/libsystemd/sd-journal/sd-journal.c +++ b/src/libsystemd/sd-journal/sd-journal.c @@ -2456,7 +2456,6 @@ _public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int fla _public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; - struct stat st; bool take_fd; int r; @@ -2464,11 +2463,9 @@ _public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) { assert_return(fd >= 0, -EBADF); assert_return((flags & ~OPEN_DIRECTORY_FD_ALLOWED_FLAGS) == 0, -EINVAL); - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode)) - return -EBADFD; + r = fd_verify_directory(fd); + if (r < 0) + return r; take_fd = FLAGS_SET(flags, SD_JOURNAL_TAKE_DIRECTORY_FD); j = journal_new(flags & ~SD_JOURNAL_TAKE_DIRECTORY_FD, NULL, NULL); @@ -2822,8 +2819,6 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void ** assert_return(j, -EINVAL); assert_return(!journal_origin_changed(j), -ECHILD); assert_return(field, -EINVAL); - assert_return(ret_data, -EINVAL); - assert_return(ret_size, -EINVAL); assert_return(field_is_valid(field), -EINVAL); f = j->current_file; @@ -2846,7 +2841,8 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void ** size_t l; p = journal_file_entry_item_object_offset(f, o, i); - r = journal_file_data_payload(f, NULL, p, field, field_length, j->data_threshold, &d, &l); + r = journal_file_data_payload(f, NULL, p, field, field_length, j->data_threshold, + ret_data ? &d : NULL, ret_size ? &l : NULL); if (r == 0) continue; if (IN_SET(r, -EADDRNOTAVAIL, -EBADMSG)) { @@ -2856,8 +2852,10 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void ** if (r < 0) return r; - *ret_data = d; - *ret_size = l; + if (ret_data) + *ret_data = d; + if (ret_size) + *ret_size = l; return 0; } diff --git a/src/libsystemd/sd-journal/test-catalog.c b/src/libsystemd/sd-journal/test-catalog.c index 51e113b3fc61b..09f05a6b90528 100644 --- a/src/libsystemd/sd-journal/test-catalog.c +++ b/src/libsystemd/sd-journal/test-catalog.c @@ -27,11 +27,10 @@ static OrderedHashmap* test_import(const char* contents, ssize_t size, int code) if (size < 0) size = strlen(contents); - fd = mkostemp_safe(name); - assert_se(fd >= 0); - assert_se(write(fd, contents, size) == size); + ASSERT_OK(fd = mkostemp_safe(name)); + ASSERT_EQ(write(fd, contents, size), size); - assert_se(catalog_import_file(&h, fd, name) == code); + ASSERT_EQ(catalog_import_file(&h, fd, name), code); return h; } @@ -40,7 +39,7 @@ static void test_catalog_import_invalid(void) { _cleanup_ordered_hashmap_free_ OrderedHashmap *h = NULL; h = test_import("xxx", -1, -EINVAL); - assert_se(ordered_hashmap_isempty(h)); + ASSERT_TRUE(ordered_hashmap_isempty(h)); } static void test_catalog_import_badid(void) { @@ -68,12 +67,12 @@ static void test_catalog_import_one(void) { "payload\n"; h = test_import(input, -1, 0); - assert_se(ordered_hashmap_size(h) == 1); + ASSERT_EQ(ordered_hashmap_size(h), 1u); ORDERED_HASHMAP_FOREACH(payload, h) { printf("expect: %s\n", expect); printf("actual: %s\n", payload); - assert_se(streq(expect, payload)); + ASSERT_STREQ(expect, payload); } } @@ -103,10 +102,10 @@ static void test_catalog_import_merge(void) { "override payload\n"; h = test_import(input, -1, 0); - assert_se(ordered_hashmap_size(h) == 1); + ASSERT_EQ(ordered_hashmap_size(h), 1u); ORDERED_HASHMAP_FOREACH(payload, h) - assert_se(streq(combined, payload)); + ASSERT_STREQ(combined, payload); } static void test_catalog_import_merge_no_body(void) { @@ -134,62 +133,56 @@ static void test_catalog_import_merge_no_body(void) { "payload\n"; h = test_import(input, -1, 0); - assert_se(ordered_hashmap_size(h) == 1); + ASSERT_EQ(ordered_hashmap_size(h), 1u); ORDERED_HASHMAP_FOREACH(payload, h) - assert_se(streq(combined, payload)); + ASSERT_STREQ(combined, payload); } static void test_catalog_update(const char *database) { - int r; - /* Test what happens if there are no files. */ - r = catalog_update(database, NULL, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(catalog_update(database, NULL, NULL)); /* Test what happens if there are no files in the directory. */ - r = catalog_update(database, NULL, no_catalog_dirs); - assert_se(r == 0); + ASSERT_OK_ZERO(catalog_update(database, NULL, no_catalog_dirs)); /* Make sure that we at least have some files loaded or the * catalog_list below will fail. */ - r = catalog_update(database, NULL, (const char * const *) catalog_dirs); - assert_se(r == 0); + ASSERT_OK_ZERO(catalog_update(database, NULL, (const char * const *) catalog_dirs)); } static void test_catalog_file_lang(void) { _cleanup_free_ char *lang = NULL, *lang2 = NULL, *lang3 = NULL, *lang4 = NULL; - assert_se(catalog_file_lang("systemd.de_DE.catalog", &lang) == 1); - assert_se(streq(lang, "de_DE")); + ASSERT_EQ(catalog_file_lang("systemd.de_DE.catalog", &lang), 1); + ASSERT_STREQ(lang, "de_DE"); - assert_se(catalog_file_lang("systemd..catalog", &lang2) == 0); - assert_se(lang2 == NULL); + ASSERT_OK_ZERO(catalog_file_lang("systemd..catalog", &lang2)); + ASSERT_NULL(lang2); - assert_se(catalog_file_lang("systemd.fr.catalog", &lang2) == 1); - assert_se(streq(lang2, "fr")); + ASSERT_EQ(catalog_file_lang("systemd.fr.catalog", &lang2), 1); + ASSERT_STREQ(lang2, "fr"); - assert_se(catalog_file_lang("systemd.fr.catalog.gz", &lang3) == 0); - assert_se(lang3 == NULL); + ASSERT_OK_ZERO(catalog_file_lang("systemd.fr.catalog.gz", &lang3)); + ASSERT_NULL(lang3); - assert_se(catalog_file_lang("systemd.01234567890123456789012345678901.catalog", &lang3) == 0); - assert_se(lang3 == NULL); + ASSERT_OK_ZERO(catalog_file_lang("systemd.01234567890123456789012345678901.catalog", &lang3)); + ASSERT_NULL(lang3); - assert_se(catalog_file_lang("systemd.0123456789012345678901234567890.catalog", &lang3) == 1); - assert_se(streq(lang3, "0123456789012345678901234567890")); + ASSERT_EQ(catalog_file_lang("systemd.0123456789012345678901234567890.catalog", &lang3), 1); + ASSERT_STREQ(lang3, "0123456789012345678901234567890"); - assert_se(catalog_file_lang("/x/y/systemd.catalog", &lang4) == 0); - assert_se(lang4 == NULL); + ASSERT_OK_ZERO(catalog_file_lang("/x/y/systemd.catalog", &lang4)); + ASSERT_NULL(lang4); - assert_se(catalog_file_lang("/x/y/systemd.ru_RU.catalog", &lang4) == 1); - assert_se(streq(lang4, "ru_RU")); + ASSERT_EQ(catalog_file_lang("/x/y/systemd.ru_RU.catalog", &lang4), 1); + ASSERT_STREQ(lang4, "ru_RU"); } int main(int argc, char *argv[]) { _cleanup_(unlink_tempfilep) char database[] = "/tmp/test-catalog.XXXXXX"; _cleanup_close_ int fd = -EBADF; _cleanup_free_ char *text = NULL; - int r; setlocale(LC_ALL, "de_DE.UTF-8"); @@ -199,7 +192,7 @@ int main(int argc, char *argv[]) { * If it is not, e.g. installed by systemd-tests package, then use installed catalogs. */ catalog_dirs = STRV_MAKE(get_catalog_dir()); - assert_se(access(catalog_dirs[0], F_OK) >= 0); + ASSERT_OK_ERRNO(access(catalog_dirs[0], F_OK)); log_notice("Using catalog directory '%s'", catalog_dirs[0]); test_catalog_file_lang(); @@ -210,17 +203,15 @@ int main(int argc, char *argv[]) { test_catalog_import_merge(); test_catalog_import_merge_no_body(); - assert_se((fd = mkostemp_safe(database)) >= 0); + ASSERT_OK(fd = mkostemp_safe(database)); test_catalog_update(database); - r = catalog_list(NULL, database, true); - assert_se(r >= 0); + ASSERT_OK(catalog_list(NULL, database, true)); - r = catalog_list(NULL, database, false); - assert_se(r >= 0); + ASSERT_OK(catalog_list(NULL, database, false)); - assert_se(catalog_get(database, SD_MESSAGE_COREDUMP, &text) >= 0); + ASSERT_OK(catalog_get(database, SD_MESSAGE_COREDUMP, &text)); printf(">>>%s<<<\n", text); return 0; diff --git a/src/libsystemd/sd-journal/test-journal-append.c b/src/libsystemd/sd-journal/test-journal-append.c index b155a7fd7ebef..a07634a249c00 100644 --- a/src/libsystemd/sd-journal/test-journal-append.c +++ b/src/libsystemd/sd-journal/test-journal-append.c @@ -1,15 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include "chattr-util.h" +#include "format-table.h" #include "iovec-util.h" #include "journal-file-util.h" #include "log.h" #include "mmap-cache.h" +#include "options.h" #include "parse-util.h" #include "random-util.h" #include "rm-rf.h" @@ -45,15 +46,14 @@ static int journal_corrupt_and_append(uint64_t start_offset, uint64_t step) { uint64_t start, end; int r; - mmap_cache = mmap_cache_new(); - assert_se(mmap_cache); + ASSERT_NOT_NULL(mmap_cache = mmap_cache_new()); /* journal_file_open() requires a valid machine id */ if (sd_id128_get_machine(NULL) < 0) return log_tests_skipped("No valid machine ID found"); - assert_se(mkdtemp_malloc("/tmp/journal-append-XXXXXX", &tempdir) >= 0); - assert_se(chdir(tempdir) >= 0); + ASSERT_OK(mkdtemp_malloc("/tmp/journal-append-XXXXXX", &tempdir)); + ASSERT_OK_ERRNO(chdir(tempdir)); (void) chattr_path(tempdir, FS_NOCOW_FL, FS_NOCOW_FL); log_debug("Opening journal %s/system.journal", tempdir); @@ -72,13 +72,13 @@ static int journal_corrupt_and_append(uint64_t start_offset, uint64_t step) { if (r < 0) return log_error_errno(r, "Failed to open the journal: %m"); - assert_se(mj); + ASSERT_NOT_NULL(mj); /* Add a couple of initial messages */ for (int i = 0; i < 10; i++) { _cleanup_free_ char *message = NULL; - assert_se(asprintf(&message, "MESSAGE=Initial message %d", i) >= 0); + ASSERT_OK_ERRNO(asprintf(&message, "MESSAGE=Initial message %d", i)); r = journal_append_message(mj, message); if (r < 0) return log_error_errno(r, "Failed to write to the journal: %m"); @@ -101,11 +101,9 @@ static int journal_corrupt_and_append(uint64_t start_offset, uint64_t step) { uint8_t b; /* Flip a bit in the journal file */ - r = pread(mj->fd, &b, 1, offset); - assert_se(r == 1); + ASSERT_EQ(pread(mj->fd, &b, 1, offset), 1); b |= 0x1; - r = pwrite(mj->fd, &b, 1, offset); - assert_se(r == 1); + ASSERT_EQ(pwrite(mj->fd, &b, 1, offset), 1); /* Close and reopen the journal to flush all caches and remap * the corrupted journal */ @@ -130,7 +128,7 @@ static int journal_corrupt_and_append(uint64_t start_offset, uint64_t step) { } /* Try to write something to the (possibly corrupted) journal */ - assert_se(asprintf(&message, "MESSAGE=Hello world %" PRIu64, offset) >= 0); + ASSERT_OK_ERRNO(asprintf(&message, "MESSAGE=Hello world %" PRIu64, offset)); r = journal_append_message(mj, message); if (r < 0) { /* We care only about crashes or sanitizer errors, @@ -149,98 +147,81 @@ int main(int argc, char *argv[]) { uint64_t iteration_step = 1; uint64_t corrupt_step = 31; bool sequential = false, run_one = false; - int c, r; + int r; test_setup_logging(LOG_DEBUG); - enum { - ARG_START_OFFSET = 0x1000, - ARG_ITERATIONS, - ARG_ITERATION_STEP, - ARG_CORRUPT_STEP, - ARG_SEQUENTIAL, - ARG_RUN_ONE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "start-offset", required_argument, NULL, ARG_START_OFFSET }, - { "iterations", required_argument, NULL, ARG_ITERATIONS }, - { "iteration-step", required_argument, NULL, ARG_ITERATION_STEP }, - { "corrupt-step", required_argument, NULL, ARG_CORRUPT_STEP }, - { "sequential", no_argument, NULL, ARG_SEQUENTIAL }, - { "run-one", required_argument, NULL, ARG_RUN_ONE }, - {} - }; - - assert_se(argc >= 0); - assert_se(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: { + _cleanup_(table_unrefp) Table *options = NULL; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("Syntax:\n" " %s [OPTION...]\n" - "Options:\n" - " --start-offset=OFFSET Offset at which to start corrupting the journal\n" - " (default: random offset is picked, unless\n" - " --sequential is used - in that case we use 0 + iteration)\n" - " --iterations=ITER Number of iterations to perform before exiting\n" - " (default: 100)\n" - " --iteration-step=STEP Iteration step (default: 1)\n" - " --corrupt-step=STEP Corrupt every n-th byte starting from OFFSET (default: 31)\n" - " --sequential Go through offsets sequentially instead of picking\n" - " a random one on each iteration. If set, we go through\n" - " offsets <0; ITER), or = 0); + ASSERT_OK(sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE)); - assert_se(sd_journal_add_match(j, "_TRANSPORT=syslog", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "_UID=0", SIZE_MAX) >= 0); + ASSERT_OK(sd_journal_add_match(j, "_TRANSPORT=syslog", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "_UID=0", SIZE_MAX)); SD_JOURNAL_FOREACH_BACKWARDS(j) { const void *d; size_t l; - assert_se(sd_journal_get_data(j, "MESSAGE", &d, &l) >= 0); + ASSERT_OK(sd_journal_get_data(j, "MESSAGE", &d, &l)); printf("%.*s\n", (int) l, (char*) d); diff --git a/src/libsystemd/sd-journal/test-journal-file.c b/src/libsystemd/sd-journal/test-journal-file.c index 52b1328fb08e9..79d1caf15d3a9 100644 --- a/src/libsystemd/sd-journal/test-journal-file.c +++ b/src/libsystemd/sd-journal/test-journal-file.c @@ -16,11 +16,11 @@ static void test_journal_file_parse_uid_from_filename_simple( log_info("testing %s", path); r = journal_file_parse_uid_from_filename(path, &uid); - assert_se(r == expected_error); + ASSERT_EQ(r, expected_error); if (r < 0) - assert_se(uid == UID_INVALID); + ASSERT_EQ(uid, UID_INVALID); else - assert_se(uid == expected_uid); + ASSERT_EQ(uid, expected_uid); } TEST(journal_file_parse_uid_from_filename) { diff --git a/src/libsystemd/sd-journal/test-journal-flush.c b/src/libsystemd/sd-journal/test-journal-flush.c index 0301dd8f69ded..04c92416a5cdf 100644 --- a/src/libsystemd/sd-journal/test-journal-flush.c +++ b/src/libsystemd/sd-journal/test-journal-flush.c @@ -108,14 +108,13 @@ static void test_journal_flush_one(int argc, char *argv[]) { unsigned n, limit; int r; - assert_se(m = mmap_cache_new()); - assert_se(mkdtemp_malloc("/var/tmp/test-journal-flush.XXXXXX", &dn) >= 0); + ASSERT_NOT_NULL(m = mmap_cache_new()); + ASSERT_OK(mkdtemp_malloc("/var/tmp/test-journal-flush.XXXXXX", &dn)); (void) chattr_path(dn, FS_NOCOW_FL, FS_NOCOW_FL); - assert_se(fn = path_join(dn, "test.journal")); + ASSERT_NOT_NULL(fn = path_join(dn, "test.journal")); - r = journal_file_open(-EBADF, fn, O_CREAT|O_RDWR, 0, 0644, 0, NULL, m, NULL, &new_journal); - assert_se(r >= 0); + ASSERT_OK(journal_file_open(-EBADF, fn, O_CREAT|O_RDWR, 0, 0644, 0, NULL, m, NULL, &new_journal)); if (argc > 1) r = sd_journal_open_files(&j, (const char **) strv_skip(argv, 1), SD_JOURNAL_ASSUME_IMMUTABLE); @@ -124,7 +123,7 @@ static void test_journal_flush_one(int argc, char *argv[]) { if (r < 0) r = sd_journal_open(&j, SD_JOURNAL_ASSUME_IMMUTABLE); } - assert_se(r == 0); + ASSERT_OK_ZERO(r); sd_journal_set_data_threshold(j, 0); @@ -135,21 +134,21 @@ static void test_journal_flush_one(int argc, char *argv[]) { JournalFile *f; f = j->current_file; - assert_se(f && f->current_offset > 0); + ASSERT_TRUE(f && f->current_offset > 0); r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); if (r < 0) log_error_errno(r, "journal_file_move_to_object failed: %m"); - assert_se(r >= 0); + ASSERT_OK(r); r = journal_file_copy_entry(f, new_journal, o, f->current_offset, NULL, NULL); if (r < 0) log_warning_errno(r, "journal_file_copy_entry failed: %m"); - assert_se(r >= 0 || - IN_SET(r, -EBADMSG, /* corrupted file */ - -EPROTONOSUPPORT, /* unsupported compression */ - -EIO, /* file rotated */ - -EREMCHG)); /* clock rollback */ + ASSERT_TRUE(r >= 0 || + IN_SET(r, -EBADMSG, /* corrupted file */ + -EPROTONOSUPPORT, /* unsupported compression */ + -EIO, /* file rotated */ + -EREMCHG)); /* clock rollback */ if (++n >= limit) break; @@ -160,43 +159,43 @@ static void test_journal_flush_one(int argc, char *argv[]) { /* Open the new journal before archiving and offlining the file. */ sd_journal_close(j); - assert_se(sd_journal_open_directory(&j, dn, SD_JOURNAL_ASSUME_IMMUTABLE) >= 0); + ASSERT_OK(sd_journal_open_directory(&j, dn, SD_JOURNAL_ASSUME_IMMUTABLE)); /* Read the online journal. */ - assert_se(sd_journal_seek_tail(j) >= 0); - assert_se(sd_journal_step_one(j, 0) > 0); + ASSERT_OK(sd_journal_seek_tail(j)); + ASSERT_OK_POSITIVE(sd_journal_step_one(j, 0)); printf("current_journal: %s (%i)\n", j->current_file->path, j->current_file->fd); - assert_se(show_journal_entry(stdout, j, OUTPUT_EXPORT, 0, 0, NULL, NULL, NULL, &(dual_timestamp) {}, &(sd_id128_t) {}) >= 0); + ASSERT_OK(show_journal_entry(stdout, j, OUTPUT_EXPORT, 0, 0, NULL, NULL, NULL, &(dual_timestamp) {}, &(sd_id128_t) {})); uint64_t p; - assert_se(journal_file_tail_end_by_mmap(j->current_file, &p) >= 0); + ASSERT_OK(journal_file_tail_end_by_mmap(j->current_file, &p)); for (uint64_t q = ALIGN64(p + 1); q < (uint64_t) j->current_file->last_stat.st_size; q = ALIGN64(q + 1)) { Object *o; r = journal_file_move_to_object(j->current_file, OBJECT_UNUSED, q, &o); - assert_se(IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)); + ASSERT_TRUE(IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)); } /* Archive and offline file. */ - assert_se(journal_file_archive(new_journal, NULL) >= 0); - assert_se(journal_file_set_offline(new_journal, /* wait= */ true) >= 0); + ASSERT_OK(journal_file_archive(new_journal, NULL)); + ASSERT_OK(journal_file_set_offline(new_journal, /* wait= */ true)); /* Read the archived and offline journal. */ for (uint64_t q = ALIGN64(p + 1); q < (uint64_t) j->current_file->last_stat.st_size; q = ALIGN64(q + 1)) { Object *o; r = journal_file_move_to_object(j->current_file, OBJECT_UNUSED, q, &o); - assert_se(IN_SET(r, -EBADMSG, -EADDRNOTAVAIL, -EIDRM)); + ASSERT_TRUE(IN_SET(r, -EBADMSG, -EADDRNOTAVAIL, -EIDRM)); } } TEST(journal_flush) { - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); test_journal_flush_one(saved_argc, saved_argv); } TEST(journal_flush_compact) { - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); test_journal_flush_one(saved_argc, saved_argv); } diff --git a/src/libsystemd/sd-journal/test-journal-init.c b/src/libsystemd/sd-journal/test-journal-init.c index 11f510642076f..47ab2a72ad38f 100644 --- a/src/libsystemd/sd-journal/test-journal-init.c +++ b/src/libsystemd/sd-journal/test-journal-init.c @@ -28,41 +28,39 @@ int main(int argc, char *argv[]) { log_info("Running %d loops", I); - assert_se(mkdtemp(t)); + ASSERT_NOT_NULL(mkdtemp(t)); (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL); for (i = 0; i < I; i++) { - r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE); - assert_se(r == 0); + ASSERT_OK_ZERO(sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE)); sd_journal_close(j); - r = sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE); - assert_se(r == 0); + ASSERT_OK_ZERO(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); - assert_se(sd_journal_seek_head(j) == 0); - assert_se(j->current_location.type == LOCATION_HEAD); + ASSERT_OK_ZERO(sd_journal_seek_head(j)); + ASSERT_EQ(j->current_location.type, (LocationType) LOCATION_HEAD); r = pidref_safe_fork("(journal-fork-test)", FORK_WAIT|FORK_LOG, NULL); if (r == 0) { - assert_se(j); + ASSERT_NOT_NULL(j); ASSERT_RETURN_EXPECTED_SE(sd_journal_get_realtime_usec(j, NULL) == -ECHILD); ASSERT_RETURN_EXPECTED_SE(sd_journal_seek_tail(j) == -ECHILD); - assert_se(j->current_location.type == LOCATION_HEAD); + ASSERT_EQ(j->current_location.type, (LocationType) LOCATION_HEAD); sd_journal_close(j); _exit(EXIT_SUCCESS); } - assert_se(r >= 0); + ASSERT_OK(r); sd_journal_close(j); j = NULL; - ASSERT_RETURN_EXPECTED(assert_se(sd_journal_open_directory(&j, t, SD_JOURNAL_LOCAL_ONLY) == -EINVAL)); - assert_se(j == NULL); + ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_journal_open_directory(&j, t, SD_JOURNAL_LOCAL_ONLY), EINVAL)); + ASSERT_NULL(j); } - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + ASSERT_OK(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL)); return 0; } diff --git a/src/libsystemd/sd-journal/test-journal-interleaving.c b/src/libsystemd/sd-journal/test-journal-interleaving.c index fbbaa82850db6..5cf65d89ed95d 100644 --- a/src/libsystemd/sd-journal/test-journal-interleaving.c +++ b/src/libsystemd/sd-journal/test-journal-interleaving.c @@ -222,6 +222,8 @@ static void setup_unreferenced_data(void) { static void mkdtemp_chdir_chattr(const char *template, char **ret) { _cleanup_(rm_rf_physical_and_freep) char *path = NULL; + assert(ret); + ASSERT_OK(mkdtemp_malloc(template, &path)); ASSERT_OK_ERRNO(chdir(path)); @@ -591,6 +593,8 @@ TEST(sequence_numbers) { } static int expected_result(uint64_t needle, const uint64_t *candidates, const uint64_t *offset, size_t n, direction_t direction, uint64_t *ret) { + assert(ret); + switch (direction) { case DIRECTION_DOWN: for (size_t i = 0; i < n; i++) { @@ -625,6 +629,8 @@ static int expected_result(uint64_t needle, const uint64_t *candidates, const ui } static int expected_result_next(uint64_t needle, const uint64_t *candidates, const uint64_t *offset, size_t n, direction_t direction, uint64_t *ret) { + assert(ret); + switch (direction) { case DIRECTION_DOWN: for (size_t i = 0; i < n; i++) diff --git a/src/libsystemd/sd-journal/test-journal-match.c b/src/libsystemd/sd-journal/test-journal-match.c index 2b3886445de86..0c6b2946ef752 100644 --- a/src/libsystemd/sd-journal/test-journal-match.c +++ b/src/libsystemd/sd-journal/test-journal-match.c @@ -14,46 +14,46 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); - assert_se(sd_journal_open(&j, SD_JOURNAL_ASSUME_IMMUTABLE) >= 0); + ASSERT_OK(sd_journal_open(&j, SD_JOURNAL_ASSUME_IMMUTABLE)); - assert_se(sd_journal_add_match(j, "foobar", SIZE_MAX) < 0); - assert_se(sd_journal_add_match(j, "foobar=waldo", SIZE_MAX) < 0); - assert_se(sd_journal_add_match(j, "", SIZE_MAX) < 0); - assert_se(sd_journal_add_match(j, "=", SIZE_MAX) < 0); - assert_se(sd_journal_add_match(j, "=xxxxx", SIZE_MAX) < 0); - assert_se(sd_journal_add_match(j, (uint8_t[4]){'A', '=', '\1', '\2'}, 4) >= 0); - assert_se(sd_journal_add_match(j, (uint8_t[5]){'B', '=', 'C', '\0', 'D'}, 5) >= 0); - assert_se(sd_journal_add_match(j, "HALLO=WALDO", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=mmmm", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=xxxxx", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "HALLO=", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=xxxxx", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=yyyyy", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "PIFF=paff", SIZE_MAX) >= 0); + ASSERT_FAIL(sd_journal_add_match(j, "foobar", SIZE_MAX)); + ASSERT_FAIL(sd_journal_add_match(j, "foobar=waldo", SIZE_MAX)); + ASSERT_FAIL(sd_journal_add_match(j, "", SIZE_MAX)); + ASSERT_FAIL(sd_journal_add_match(j, "=", SIZE_MAX)); + ASSERT_FAIL(sd_journal_add_match(j, "=xxxxx", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, (uint8_t[4]){'A', '=', '\1', '\2'}, 4)); + ASSERT_OK(sd_journal_add_match(j, (uint8_t[5]){'B', '=', 'C', '\0', 'D'}, 5)); + ASSERT_OK(sd_journal_add_match(j, "HALLO=WALDO", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "QUUX=mmmm", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "QUUX=xxxxx", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "HALLO=", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "QUUX=xxxxx", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "QUUX=yyyyy", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "PIFF=paff", SIZE_MAX)); - assert_se(sd_journal_add_disjunction(j) >= 0); + ASSERT_OK(sd_journal_add_disjunction(j)); - assert_se(sd_journal_add_match(j, "ONE=one", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "ONE=two", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "TWO=two", SIZE_MAX) >= 0); + ASSERT_OK(sd_journal_add_match(j, "ONE=one", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "ONE=two", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "TWO=two", SIZE_MAX)); - assert_se(sd_journal_add_conjunction(j) >= 0); + ASSERT_OK(sd_journal_add_conjunction(j)); - assert_se(sd_journal_add_match(j, "L4_1=yes", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "L4_1=ok", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "L4_2=yes", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "L4_2=ok", SIZE_MAX) >= 0); + ASSERT_OK(sd_journal_add_match(j, "L4_1=yes", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "L4_1=ok", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "L4_2=yes", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "L4_2=ok", SIZE_MAX)); - assert_se(sd_journal_add_disjunction(j) >= 0); + ASSERT_OK(sd_journal_add_disjunction(j)); - assert_se(sd_journal_add_match(j, "L3=yes", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "L3=ok", SIZE_MAX) >= 0); + ASSERT_OK(sd_journal_add_match(j, "L3=yes", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "L3=ok", SIZE_MAX)); - assert_se(t = journal_make_match_string(j)); + ASSERT_NOT_NULL(t = journal_make_match_string(j)); printf("resulting match expression is: %s\n", t); - assert_se(streq(t, "(((L3=ok OR L3=yes) OR ((L4_2=ok OR L4_2=yes) AND (L4_1=ok OR L4_1=yes))) AND ((TWO=two AND (ONE=two OR ONE=one)) OR (PIFF=paff AND (QUUX=yyyyy OR QUUX=xxxxx OR QUUX=mmmm) AND (HALLO= OR HALLO=WALDO) AND B=C\\000D AND A=\\001\\002)))")); + ASSERT_STREQ(t, "(((L3=ok OR L3=yes) OR ((L4_2=ok OR L4_2=yes) AND (L4_1=ok OR L4_1=yes))) AND ((TWO=two AND (ONE=two OR ONE=one)) OR (PIFF=paff AND (QUUX=yyyyy OR QUUX=xxxxx OR QUUX=mmmm) AND (HALLO= OR HALLO=WALDO) AND B=C\\000D AND A=\\001\\002)))"); return 0; } diff --git a/src/libsystemd/sd-journal/test-journal-send.c b/src/libsystemd/sd-journal/test-journal-send.c index e4959521f6ac5..7f3a5024dc2bf 100644 --- a/src/libsystemd/sd-journal/test-journal-send.c +++ b/src/libsystemd/sd-journal/test-journal-send.c @@ -10,18 +10,18 @@ #include "tests.h" TEST(journal_print) { - assert_se(sd_journal_print(LOG_INFO, "XXX") == 0); - assert_se(sd_journal_print(LOG_INFO, "%s", "YYY") == 0); - assert_se(sd_journal_print(LOG_INFO, "X%4094sY", "ZZZ") == 0); - assert_se(sd_journal_print(LOG_INFO, "X%*sY", (int) LONG_LINE_MAX - 8 - 3, "ZZZ") == 0); - assert_se(sd_journal_print(LOG_INFO, "X%*sY", (int) LONG_LINE_MAX - 8 - 2, "ZZZ") == -ENOBUFS); + ASSERT_OK_ZERO(sd_journal_print(LOG_INFO, "XXX")); + ASSERT_OK_ZERO(sd_journal_print(LOG_INFO, "%s", "YYY")); + ASSERT_OK_ZERO(sd_journal_print(LOG_INFO, "X%4094sY", "ZZZ")); + ASSERT_OK_ZERO(sd_journal_print(LOG_INFO, "X%*sY", (int) LONG_LINE_MAX - 8 - 3, "ZZZ")); + ASSERT_ERROR(sd_journal_print(LOG_INFO, "X%*sY", (int) LONG_LINE_MAX - 8 - 2, "ZZZ"), ENOBUFS); } TEST(journal_send) { _cleanup_free_ char *huge = NULL; #define HUGE_SIZE (4096*1024) - assert_se(huge = malloc(HUGE_SIZE)); + ASSERT_NOT_NULL(huge = malloc(HUGE_SIZE)); /* utf-8 and non-utf-8, message-less and message-ful iovecs */ struct iovec graph1[] = { @@ -37,59 +37,59 @@ TEST(journal_send) { {(char*) "MESSAGE=graph\n", STRLEN("MESSAGE=graph\n")} }; - assert_se(sd_journal_print(LOG_INFO, "piepapo") == 0); + ASSERT_OK_ZERO(sd_journal_print(LOG_INFO, "piepapo")); - assert_se(sd_journal_send("MESSAGE=foobar", - "VALUE=%i", 7, - NULL) == 0); + ASSERT_OK_ZERO(sd_journal_send("MESSAGE=foobar", + "VALUE=%i", 7, + NULL)); errno = ENOENT; - assert_se(sd_journal_perror("Foobar") == 0); + ASSERT_OK_ZERO(sd_journal_perror("Foobar")); - assert_se(sd_journal_perror("") == 0); + ASSERT_OK_ZERO(sd_journal_perror("")); memcpy(huge, "HUGE=", STRLEN("HUGE=")); memset(&huge[STRLEN("HUGE=")], 'x', HUGE_SIZE - STRLEN("HUGE=") - 1); huge[HUGE_SIZE - 1] = '\0'; - assert_se(sd_journal_send("MESSAGE=Huge field attached", - huge, - NULL) == 0); + ASSERT_OK_ZERO(sd_journal_send("MESSAGE=Huge field attached", + huge, + NULL)); - assert_se(sd_journal_send("MESSAGE=uiui", - "VALUE=A", - "VALUE=B", - "VALUE=C", - "SINGLETON=1", - "OTHERVALUE=X", - "OTHERVALUE=Y", - "WITH_BINARY=this is a binary value \a", - NULL) == 0); + ASSERT_OK_ZERO(sd_journal_send("MESSAGE=uiui", + "VALUE=A", + "VALUE=B", + "VALUE=C", + "SINGLETON=1", + "OTHERVALUE=X", + "OTHERVALUE=Y", + "WITH_BINARY=this is a binary value \a", + NULL)); syslog(LOG_NOTICE, "Hello World!"); - assert_se(sd_journal_print(LOG_NOTICE, "Hello World") == 0); + ASSERT_OK_ZERO(sd_journal_print(LOG_NOTICE, "Hello World")); - assert_se(sd_journal_send("MESSAGE=Hello World!", - "MESSAGE_ID=52fb62f99e2c49d89cfbf9d6de5e3555", - "PRIORITY=5", - "HOME=%s", getenv("HOME"), - "TERM=%s", getenv("TERM"), - "PAGE_SIZE=%li", sysconf(_SC_PAGESIZE), - "N_CPUS=%li", sysconf(_SC_NPROCESSORS_ONLN), - NULL) == 0); + ASSERT_OK_ZERO(sd_journal_send("MESSAGE=Hello World!", + "MESSAGE_ID=52fb62f99e2c49d89cfbf9d6de5e3555", + "PRIORITY=5", + "HOME=%s", getenv("HOME"), + "TERM=%s", getenv("TERM"), + "PAGE_SIZE=%li", sysconf(_SC_PAGESIZE), + "N_CPUS=%li", sysconf(_SC_NPROCESSORS_ONLN), + NULL)); - assert_se(sd_journal_sendv(graph1, 1) == 0); - assert_se(sd_journal_sendv(graph2, 1) == 0); - assert_se(sd_journal_sendv(message1, 1) == 0); - assert_se(sd_journal_sendv(message2, 1) == 0); + ASSERT_OK_ZERO(sd_journal_sendv(graph1, 1)); + ASSERT_OK_ZERO(sd_journal_sendv(graph2, 1)); + ASSERT_OK_ZERO(sd_journal_sendv(message1, 1)); + ASSERT_OK_ZERO(sd_journal_sendv(message2, 1)); /* test without location fields */ #undef sd_journal_sendv - assert_se(sd_journal_sendv(graph1, 1) == 0); - assert_se(sd_journal_sendv(graph2, 1) == 0); - assert_se(sd_journal_sendv(message1, 1) == 0); - assert_se(sd_journal_sendv(message2, 1) == 0); + ASSERT_OK_ZERO(sd_journal_sendv(graph1, 1)); + ASSERT_OK_ZERO(sd_journal_sendv(graph2, 1)); + ASSERT_OK_ZERO(sd_journal_sendv(message1, 1)); + ASSERT_OK_ZERO(sd_journal_sendv(message2, 1)); /* The above syslog() opens a fd which is stored in libc, and the valgrind reports the fd is * leaked when we do not call closelog(). */ diff --git a/src/libsystemd/sd-journal/test-journal-stream.c b/src/libsystemd/sd-journal/test-journal-stream.c index efd4eb0a630b9..de576962ccef2 100644 --- a/src/libsystemd/sd-journal/test-journal-stream.c +++ b/src/libsystemd/sd-journal/test-journal-stream.c @@ -15,12 +15,12 @@ #include "tests.h" #include "time-util.h" -#define N_ENTRIES 200 +#define N_ENTRIES 200u static void verify_contents(sd_journal *j, unsigned skip) { unsigned i; - assert_se(j); + ASSERT_NOT_NULL(j); i = 0; SD_JOURNAL_FOREACH(j) { @@ -29,32 +29,32 @@ static void verify_contents(sd_journal *j, unsigned skip) { size_t l; unsigned u = 0; - assert_se(sd_journal_get_cursor(j, &k) >= 0); + ASSERT_OK(sd_journal_get_cursor(j, &k)); printf("cursor: %s\n", k); free(k); - assert_se(sd_journal_get_data(j, "MAGIC", &d, &l) >= 0); + ASSERT_OK(sd_journal_get_data(j, "MAGIC", &d, &l)); printf("\t%.*s\n", (int) l, (const char*) d); - assert_se(sd_journal_get_data(j, "NUMBER", &d, &l) >= 0); - assert_se(k = strndup(d, l)); + ASSERT_OK(sd_journal_get_data(j, "NUMBER", &d, &l)); + ASSERT_NOT_NULL(k = strndup(d, l)); printf("\t%s\n", k); if (skip > 0) { - assert_se(safe_atou(k + 7, &u) >= 0); - assert_se(i == u); + ASSERT_OK(safe_atou(k + 7, &u)); + ASSERT_EQ(i, u); i += skip; } free(k); - assert_se(sd_journal_get_cursor(j, &c) >= 0); - assert_se(sd_journal_test_cursor(j, c) > 0); + ASSERT_OK(sd_journal_get_cursor(j, &c)); + ASSERT_OK_POSITIVE(sd_journal_test_cursor(j, c)); free(c); } if (skip > 0) - assert_se(i == N_ENTRIES); + ASSERT_EQ(i, N_ENTRIES); } static void run_test(void) { @@ -68,16 +68,15 @@ static void run_test(void) { size_t l; dual_timestamp previous_ts = DUAL_TIMESTAMP_NULL; - m = mmap_cache_new(); - assert_se(m != NULL); + ASSERT_NOT_NULL(m = mmap_cache_new()); - assert_se(mkdtemp(t)); - assert_se(chdir(t) >= 0); + ASSERT_NOT_NULL(mkdtemp(t)); + ASSERT_OK_ERRNO(chdir(t)); (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL); - assert_se(journal_file_open(-EBADF, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &one) == 0); - assert_se(journal_file_open(-EBADF, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &two) == 0); - assert_se(journal_file_open(-EBADF, "three.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &three) == 0); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &one)); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &two)); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "three.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &three)); for (i = 0; i < N_ENTRIES; i++) { char *p, *q; @@ -94,20 +93,20 @@ static void run_test(void) { previous_ts = ts; - assert_se(asprintf(&p, "NUMBER=%u", i) >= 0); + ASSERT_OK_ERRNO(asprintf(&p, "NUMBER=%u", i)); iovec[0] = IOVEC_MAKE(p, strlen(p)); - assert_se(asprintf(&q, "MAGIC=%s", i % 5 == 0 ? "quux" : "waldo") >= 0); + ASSERT_OK_ERRNO(asprintf(&q, "MAGIC=%s", i % 5 == 0 ? "quux" : "waldo")); iovec[1] = IOVEC_MAKE(q, strlen(q)); if (i % 10 == 0) - assert_se(journal_file_append_entry(three, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL) == 0); + ASSERT_OK_ZERO(journal_file_append_entry(three, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL)); else { if (i % 3 == 0) - assert_se(journal_file_append_entry(two, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL) == 0); + ASSERT_OK_ZERO(journal_file_append_entry(two, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL)); - assert_se(journal_file_append_entry(one, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL) == 0); + ASSERT_OK_ZERO(journal_file_append_entry(one, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL)); } free(p); @@ -118,27 +117,27 @@ static void run_test(void) { (void) journal_file_offline_close(two); (void) journal_file_offline_close(three); - assert_se(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE) >= 0); + ASSERT_OK(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); - assert_se(sd_journal_add_match(j, "MAGIC=quux", SIZE_MAX) >= 0); + ASSERT_OK(sd_journal_add_match(j, "MAGIC=quux", SIZE_MAX)); SD_JOURNAL_FOREACH_BACKWARDS(j) { _cleanup_free_ char *c; - assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0); + ASSERT_OK(sd_journal_get_data(j, "NUMBER", &data, &l)); printf("\t%.*s\n", (int) l, (const char*) data); - assert_se(sd_journal_get_cursor(j, &c) >= 0); - assert_se(sd_journal_test_cursor(j, c) > 0); + ASSERT_OK(sd_journal_get_cursor(j, &c)); + ASSERT_OK_POSITIVE(sd_journal_test_cursor(j, c)); } SD_JOURNAL_FOREACH(j) { _cleanup_free_ char *c; - assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0); + ASSERT_OK(sd_journal_get_data(j, "NUMBER", &data, &l)); printf("\t%.*s\n", (int) l, (const char*) data); - assert_se(sd_journal_get_cursor(j, &c) >= 0); - assert_se(sd_journal_test_cursor(j, c) > 0); + ASSERT_OK(sd_journal_get_cursor(j, &c)); + ASSERT_OK_POSITIVE(sd_journal_test_cursor(j, c)); } sd_journal_flush_matches(j); @@ -146,9 +145,9 @@ static void run_test(void) { verify_contents(j, 1); printf("NEXT TEST\n"); - assert_se(sd_journal_add_match(j, "MAGIC=quux", SIZE_MAX) >= 0); + ASSERT_OK(sd_journal_add_match(j, "MAGIC=quux", SIZE_MAX)); - assert_se(z = journal_make_match_string(j)); + ASSERT_NOT_NULL(z = journal_make_match_string(j)); printf("resulting match expression is: %s\n", z); free(z); @@ -156,22 +155,22 @@ static void run_test(void) { printf("NEXT TEST\n"); sd_journal_flush_matches(j); - assert_se(sd_journal_add_match(j, "MAGIC=waldo", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=10", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=11", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=12", SIZE_MAX) >= 0); + ASSERT_OK(sd_journal_add_match(j, "MAGIC=waldo", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "NUMBER=10", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "NUMBER=11", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "NUMBER=12", SIZE_MAX)); - assert_se(z = journal_make_match_string(j)); + ASSERT_NOT_NULL(z = journal_make_match_string(j)); printf("resulting match expression is: %s\n", z); free(z); verify_contents(j, 0); - assert_se(sd_journal_query_unique(j, "NUMBER") >= 0); + ASSERT_OK(sd_journal_query_unique(j, "NUMBER")); SD_JOURNAL_FOREACH_UNIQUE(j, data, l) printf("%.*s\n", (int) l, (const char*) data); - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + ASSERT_OK(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL)); } int main(int argc, char *argv[]) { @@ -184,16 +183,16 @@ int main(int argc, char *argv[]) { /* Run this test multiple times with different configurations of features. */ - assert_se(setenv("SYSTEMD_JOURNAL_KEYED_HASH", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_KEYED_HASH", "0", 1)); run_test(); - assert_se(setenv("SYSTEMD_JOURNAL_KEYED_HASH", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_KEYED_HASH", "1", 1)); run_test(); - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); run_test(); - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); run_test(); return 0; diff --git a/src/libsystemd/sd-journal/test-journal-verify.c b/src/libsystemd/sd-journal/test-journal-verify.c index bc66a96aee008..2d797a18f3d3f 100644 --- a/src/libsystemd/sd-journal/test-journal-verify.c +++ b/src/libsystemd/sd-journal/test-journal-verify.c @@ -22,19 +22,15 @@ static void bit_toggle(const char *fn, uint64_t p) { uint8_t b; - ssize_t r; int fd; - fd = open(fn, O_RDWR|O_CLOEXEC); - assert_se(fd >= 0); + ASSERT_OK_ERRNO(fd = open(fn, O_RDWR|O_CLOEXEC)); - r = pread(fd, &b, 1, p/8); - assert_se(r == 1); + ASSERT_EQ(pread(fd, &b, 1, p/8), 1); b ^= 1 << (p % 8); - r = pwrite(fd, &b, 1, p/8); - assert_se(r == 1); + ASSERT_EQ(pwrite(fd, &b, 1, p/8), 1); safe_close(fd); } @@ -44,8 +40,7 @@ static int raw_verify(const char *fn, const char *verification_key) { JournalFile *f; int r; - m = mmap_cache_new(); - assert_se(m != NULL); + ASSERT_NOT_NULL(m = mmap_cache_new()); r = journal_file_open( /* fd= */ -EBADF, @@ -77,8 +72,7 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { uint64_t start, end; int r; - m = mmap_cache_new(); - assert_se(m != NULL); + ASSERT_NOT_NULL(m = mmap_cache_new()); /* journal_file_open() requires a valid machine id */ if (sd_id128_get_machine(NULL) < 0) @@ -86,13 +80,13 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { test_setup_logging(LOG_DEBUG); - assert_se(mkdtemp(t)); - assert_se(chdir(t) >= 0); + ASSERT_NOT_NULL(mkdtemp(t)); + ASSERT_OK_ERRNO(chdir(t)); (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL); log_info("Generating a test journal"); - assert_se(journal_file_open( + ASSERT_OK_ZERO(journal_file_open( /* fd= */ -EBADF, "test.journal", O_RDWR|O_CREAT, @@ -102,7 +96,7 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { /* metrics= */ NULL, m, /* template= */ NULL, - &df) == 0); + &df)); for (size_t n = 0; n < N_ENTRIES; n++) { _cleanup_free_ char *test = NULL; @@ -110,9 +104,9 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { struct dual_timestamp ts; dual_timestamp_now(&ts); - assert_se(asprintf(&test, "RANDOM=%li", random() % RANDOM_RANGE)); + ASSERT_OK_ERRNO(asprintf(&test, "RANDOM=%li", random() % RANDOM_RANGE)); iovec = IOVEC_MAKE_STRING(test); - assert_se(journal_file_append_entry( + ASSERT_OK_ZERO(journal_file_append_entry( df, &ts, /* boot_id= */ NULL, @@ -121,14 +115,14 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { /* seqnum= */ NULL, /* seqnum_id= */ NULL, /* ret_object= */ NULL, - /* ret_offset= */ NULL) == 0); + /* ret_offset= */ NULL)); } (void) journal_file_offline_close(df); log_info("Verifying with key: %s", strna(verification_key)); - assert_se(journal_file_open( + ASSERT_OK_ZERO(journal_file_open( /* fd= */ -EBADF, "test.journal", O_RDONLY, @@ -138,11 +132,11 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { /* metrics= */ NULL, m, /* template= */ NULL, - &f) == 0); + &f)); journal_file_print_header(f); journal_file_dump(f); - assert_se(journal_file_verify(f, verification_key, &from, &to, &total, true) >= 0); + ASSERT_OK(journal_file_verify(f, verification_key, &from, &to, &total, true)); if (verification_key && JOURNAL_HEADER_SEALED(f->header)) log_info("=> Validated from %s to %s, %s missing", @@ -151,7 +145,7 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { FORMAT_TIMESPAN(total > to ? total - to : 0, 0)); (void) journal_file_close(f); - assert_se(stat("test.journal", &st) >= 0); + ASSERT_OK_ERRNO(stat("test.journal", &st)); start = 38448 * 8 + 0; end = max_iterations < 0 ? (uint64_t)st.st_size * 8 : start + max_iterations; @@ -171,7 +165,7 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { bit_toggle("test.journal", p); } - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + ASSERT_OK(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL)); return 0; } @@ -187,10 +181,10 @@ int main(int argc, char *argv[]) { max_iterations = -1; } - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); run_test(verification_key, max_iterations); - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); run_test(verification_key, max_iterations); #if HAVE_GCRYPT @@ -199,10 +193,10 @@ int main(int argc, char *argv[]) { if (argc <= 1) { verification_key = "c262bd-85187f-0b1b04-877cc5/1c7af8-35a4e900"; - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); run_test(verification_key, max_iterations); - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); run_test(verification_key, max_iterations); } #endif diff --git a/src/libsystemd/sd-journal/test-journal.c b/src/libsystemd/sd-journal/test-journal.c index fb0a02d9bfbbf..b3eaac28aa79b 100644 --- a/src/libsystemd/sd-journal/test-journal.c +++ b/src/libsystemd/sd-journal/test-journal.c @@ -17,8 +17,8 @@ static bool arg_keep = false; static void mkdtemp_chdir_chattr(char *path) { - assert_se(mkdtemp(path)); - assert_se(chdir(path) >= 0); + ASSERT_NOT_NULL(mkdtemp(path)); + ASSERT_OK_ERRNO(chdir(path)); /* Speed up things a bit on btrfs, ensuring that CoW is turned off for all files created in our * directory during the test run */ @@ -36,71 +36,70 @@ static void test_non_empty_one(void) { sd_id128_t fake_boot_id; char t[] = "/var/tmp/journal-XXXXXX"; - m = mmap_cache_new(); - assert_se(m != NULL); + ASSERT_NOT_NULL(m = mmap_cache_new()); mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f) == 0); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f)); - assert_se(dual_timestamp_now(&ts)); - assert_se(sd_id128_randomize(&fake_boot_id) == 0); + ASSERT_NOT_NULL(dual_timestamp_now(&ts)); + ASSERT_OK_ZERO(sd_id128_randomize(&fake_boot_id)); iovec = IOVEC_MAKE_STRING(test); - assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL) == 0); + ASSERT_OK_ZERO(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL)); iovec = IOVEC_MAKE_STRING(test2); - assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL) == 0); + ASSERT_OK_ZERO(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL)); iovec = IOVEC_MAKE_STRING(test); - assert_se(journal_file_append_entry(f, &ts, &fake_boot_id, &iovec, 1, NULL, NULL, NULL, NULL) == 0); + ASSERT_OK_ZERO(journal_file_append_entry(f, &ts, &fake_boot_id, &iovec, 1, NULL, NULL, NULL, NULL)); #if HAVE_GCRYPT journal_file_append_tag(f); #endif journal_file_dump(f); - assert_se(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); + ASSERT_EQ(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(1)); - assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); + ASSERT_EQ(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(2)); - assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 1); - assert_se(le64toh(o->entry.seqnum) == 3); - assert_se(sd_id128_equal(o->entry.boot_id, fake_boot_id)); + ASSERT_EQ(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(3)); + ASSERT_EQ_ID128(o->entry.boot_id, fake_boot_id); - assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 0); + ASSERT_OK_ZERO(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p)); - assert_se(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); + ASSERT_EQ(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(1)); - assert_se(journal_file_find_data_object(f, test, strlen(test), &d, NULL) == 1); - assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); + ASSERT_EQ(journal_file_find_data_object(f, test, strlen(test), &d, NULL), 1); + ASSERT_EQ(journal_file_move_to_entry_for_data(f, d, DIRECTION_DOWN, &o, NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(1)); - assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_UP, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 3); + ASSERT_EQ(journal_file_move_to_entry_for_data(f, d, DIRECTION_UP, &o, NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(3)); - assert_se(journal_file_find_data_object(f, test2, strlen(test2), &d, NULL) == 1); - assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_UP, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); + ASSERT_EQ(journal_file_find_data_object(f, test2, strlen(test2), &d, NULL), 1); + ASSERT_EQ(journal_file_move_to_entry_for_data(f, d, DIRECTION_UP, &o, NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(2)); - assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); + ASSERT_EQ(journal_file_move_to_entry_for_data(f, d, DIRECTION_DOWN, &o, NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(2)); - assert_se(journal_file_find_data_object(f, "quux", 4, &d, NULL) == 0); + ASSERT_OK_ZERO(journal_file_find_data_object(f, "quux", 4, &d, NULL)); - assert_se(journal_file_move_to_entry_by_seqnum(f, 1, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); + ASSERT_EQ(journal_file_move_to_entry_by_seqnum(f, 1, DIRECTION_DOWN, &o, NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(1)); - assert_se(journal_file_move_to_entry_by_seqnum(f, 3, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 3); + ASSERT_EQ(journal_file_move_to_entry_by_seqnum(f, 3, DIRECTION_DOWN, &o, NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(3)); - assert_se(journal_file_move_to_entry_by_seqnum(f, 2, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); + ASSERT_EQ(journal_file_move_to_entry_by_seqnum(f, 2, DIRECTION_DOWN, &o, NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(2)); - assert_se(journal_file_move_to_entry_by_seqnum(f, 10, DIRECTION_DOWN, &o, NULL) == 0); + ASSERT_OK_ZERO(journal_file_move_to_entry_by_seqnum(f, 10, DIRECTION_DOWN, &o, NULL)); journal_file_rotate(&f, m, JOURNAL_SEAL|JOURNAL_COMPRESS, UINT64_MAX, NULL); journal_file_rotate(&f, m, JOURNAL_SEAL|JOURNAL_COMPRESS, UINT64_MAX, NULL); @@ -114,17 +113,17 @@ static void test_non_empty_one(void) { else { journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + ASSERT_OK(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL)); } puts("------------------------------------------------------------"); } TEST(non_empty) { - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); test_non_empty_one(); - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); test_non_empty_one(); } @@ -133,15 +132,14 @@ static void test_empty_one(void) { JournalFile *f1, *f2, *f3, *f4; char t[] = "/var/tmp/journal-XXXXXX"; - m = mmap_cache_new(); - assert_se(m != NULL); + ASSERT_NOT_NULL(m = mmap_cache_new()); mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, 0, 0666, UINT64_MAX, NULL, m, NULL, &f1) == 0); - assert_se(journal_file_open(-EBADF, "test-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &f2) == 0); - assert_se(journal_file_open(-EBADF, "test-seal.journal", O_RDWR|O_CREAT, JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f3) == 0); - assert_se(journal_file_open(-EBADF, "test-seal-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f4) == 0); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, 0, 0666, UINT64_MAX, NULL, m, NULL, &f1)); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "test-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &f2)); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "test-seal.journal", O_RDWR|O_CREAT, JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f3)); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "test-seal-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f4)); journal_file_print_header(f1); puts(""); @@ -159,7 +157,7 @@ static void test_empty_one(void) { else { journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + ASSERT_OK(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL)); } (void) journal_file_offline_close(f1); @@ -169,10 +167,10 @@ static void test_empty_one(void) { } TEST(empty) { - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); test_empty_one(); - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); test_empty_one(); } @@ -187,21 +185,19 @@ static bool check_compressed(uint64_t compress_threshold, uint64_t data_size) { char t[] = "/var/tmp/journal-XXXXXX"; char data[2048] = "FIELD="; bool is_compressed; - int r; - assert_se(data_size <= sizeof(data)); + ASSERT_LE(data_size, sizeof(data)); - m = mmap_cache_new(); - assert_se(m != NULL); + ASSERT_NOT_NULL(m = mmap_cache_new()); mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, compress_threshold, NULL, m, NULL, &f) == 0); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, compress_threshold, NULL, m, NULL, &f)); dual_timestamp_now(&ts); iovec = IOVEC_MAKE(data, data_size); - assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL) == 0); + ASSERT_OK_ZERO(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL)); #if HAVE_GCRYPT journal_file_append_tag(f); @@ -212,12 +208,11 @@ static bool check_compressed(uint64_t compress_threshold, uint64_t data_size) { * decompression for us. */ p = le64toh(f->header->header_size); for (;;) { - r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o); - assert_se(r == 0); + ASSERT_OK_ZERO(journal_file_move_to_object(f, OBJECT_UNUSED, p, &o)); if (o->object.type == OBJECT_DATA) break; - assert_se(p < le64toh(f->header->tail_object_offset)); + ASSERT_LT(p, le64toh(f->header->tail_object_offset)); p = p + ALIGN64(le64toh(o->object.size)); } @@ -232,7 +227,7 @@ static bool check_compressed(uint64_t compress_threshold, uint64_t data_size) { else { journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + ASSERT_OK(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL)); } puts("------------------------------------------------------------"); @@ -245,26 +240,26 @@ static void test_min_compress_size_one(void) { * carefully */ /* DEFAULT_MIN_COMPRESS_SIZE is 512 */ - assert_se(!check_compressed(UINT64_MAX, 255)); - assert_se(check_compressed(UINT64_MAX, 513)); + ASSERT_FALSE(check_compressed(UINT64_MAX, 255)); + ASSERT_TRUE(check_compressed(UINT64_MAX, 513)); /* compress everything */ - assert_se(check_compressed(0, 96)); - assert_se(check_compressed(8, 96)); + ASSERT_TRUE(check_compressed(0, 96)); + ASSERT_TRUE(check_compressed(8, 96)); /* Ensure we don't try to compress less than 8 bytes */ - assert_se(!check_compressed(0, 7)); + ASSERT_FALSE(check_compressed(0, 7)); /* check boundary conditions */ - assert_se(check_compressed(256, 256)); - assert_se(!check_compressed(256, 255)); + ASSERT_TRUE(check_compressed(256, 256)); + ASSERT_FALSE(check_compressed(256, 255)); } TEST(min_compress_size) { - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); test_min_compress_size_one(); - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); test_min_compress_size_one(); } #endif diff --git a/src/libsystemd/sd-journal/test-mmap-cache.c b/src/libsystemd/sd-journal/test-mmap-cache.c index dc7e334247836..b59d5177f6dc8 100644 --- a/src/libsystemd/sd-journal/test-mmap-cache.c +++ b/src/libsystemd/sd-journal/test-mmap-cache.c @@ -10,49 +10,41 @@ int main(int argc, char *argv[]) { MMapFileDescriptor *fx; - int x, y, z, r; + int x, y, z; char px[] = "/tmp/testmmapXXXXXXX", py[] = "/tmp/testmmapYXXXXXX", pz[] = "/tmp/testmmapZXXXXXX"; MMapCache *m; void *p, *q; test_setup_logging(LOG_DEBUG); - assert_se(m = mmap_cache_new()); + ASSERT_NOT_NULL(m = mmap_cache_new()); - x = mkostemp_safe(px); - assert_se(x >= 0); + ASSERT_OK(x = mkostemp_safe(px)); (void) unlink(px); - assert_se(mmap_cache_add_fd(m, x, PROT_READ, &fx) > 0); + ASSERT_OK_POSITIVE(mmap_cache_add_fd(m, x, PROT_READ, &fx)); - y = mkostemp_safe(py); - assert_se(y >= 0); + ASSERT_OK(y = mkostemp_safe(py)); (void) unlink(py); - z = mkostemp_safe(pz); - assert_se(z >= 0); + ASSERT_OK(z = mkostemp_safe(pz)); (void) unlink(pz); - r = mmap_cache_fd_get(fx, 0, false, 1, 2, NULL, &p); - assert_se(r >= 0); + ASSERT_OK(mmap_cache_fd_get(fx, 0, false, 1, 2, NULL, &p)); - r = mmap_cache_fd_get(fx, 0, false, 2, 2, NULL, &q); - assert_se(r >= 0); + ASSERT_OK(mmap_cache_fd_get(fx, 0, false, 2, 2, NULL, &q)); - assert_se((uint8_t*) p + 1 == (uint8_t*) q); + ASSERT_PTR_EQ((uint8_t*) p + 1, (uint8_t*) q); - r = mmap_cache_fd_get(fx, 1, false, 3, 2, NULL, &q); - assert_se(r >= 0); + ASSERT_OK(mmap_cache_fd_get(fx, 1, false, 3, 2, NULL, &q)); - assert_se((uint8_t*) p + 2 == (uint8_t*) q); + ASSERT_PTR_EQ((uint8_t*) p + 2, (uint8_t*) q); - r = mmap_cache_fd_get(fx, 0, false, 16ULL*1024ULL*1024ULL, 2, NULL, &p); - assert_se(r >= 0); + ASSERT_OK(mmap_cache_fd_get(fx, 0, false, 16ULL*1024ULL*1024ULL, 2, NULL, &p)); - r = mmap_cache_fd_get(fx, 1, false, 16ULL*1024ULL*1024ULL+1, 2, NULL, &q); - assert_se(r >= 0); + ASSERT_OK(mmap_cache_fd_get(fx, 1, false, 16ULL*1024ULL*1024ULL+1, 2, NULL, &q)); - assert_se((uint8_t*) p + 1 == (uint8_t*) q); + ASSERT_PTR_EQ((uint8_t*) p + 1, (uint8_t*) q); mmap_cache_fd_free(fx); mmap_cache_unref(m); diff --git a/src/libsystemd/sd-json/json-stream.c b/src/libsystemd/sd-json/json-stream.c new file mode 100644 index 0000000000000..3775691d47f67 --- /dev/null +++ b/src/libsystemd/sd-json/json-stream.c @@ -0,0 +1,1382 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include + +#include "sd-event.h" +#include "sd-json.h" + +#include "alloc-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "iovec-util.h" +#include "json-stream.h" +#include "list.h" +#include "log.h" +#include "memory-util.h" +#include "process-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "time-util.h" +#include "user-util.h" + +#define JSON_STREAM_BUFFER_MAX_DEFAULT (16U * 1024U * 1024U) +#define JSON_STREAM_READ_SIZE_DEFAULT (64U * 1024U) +#define JSON_STREAM_QUEUE_MAX_DEFAULT (64U * 1024U) +#define JSON_STREAM_FDS_MAX (16U * 1024U) + +struct JsonStreamQueueItem { + LIST_FIELDS(JsonStreamQueueItem, queue); + sd_json_variant *data; + size_t n_fds; + int fds[]; +}; + +/* Returns the size of the framing delimiter in bytes: strlen(delimiter) for multi-char + * delimiters (e.g. "\r\n"), or 1 for the default NUL-byte delimiter (delimiter == NULL). */ +static size_t json_stream_delimiter_size(const JsonStream *s) { + return strlen_ptr(s->delimiter) ?: 1; +} + +static usec_t json_stream_now(const JsonStream *s) { + usec_t t; + + if (s->event && sd_event_now(s->event, CLOCK_MONOTONIC, &t) >= 0) + return t; + + return now(CLOCK_MONOTONIC); +} + +static JsonStreamQueueItem* json_stream_queue_item_free(JsonStreamQueueItem *q) { + if (!q) + return NULL; + + sd_json_variant_unref(q->data); + close_many(q->fds, q->n_fds); + + return mfree(q); +} + +static JsonStreamQueueItem* json_stream_queue_item_new(sd_json_variant *m, const int fds[], size_t n_fds) { + JsonStreamQueueItem *q; + + assert(m); + assert(fds || n_fds == 0); + + size_t sz = sizeof(int); + if (!MUL_SAFE(&sz, sz, n_fds) || + !INC_SAFE(&sz, offsetof(JsonStreamQueueItem, fds))) + return NULL; + + q = malloc(sz); + if (!q) + return NULL; + + *q = (JsonStreamQueueItem) { + .data = sd_json_variant_ref(m), + .n_fds = n_fds, + }; + + memcpy_safe(q->fds, fds, n_fds * sizeof(int)); + + return TAKE_PTR(q); +} + +int json_stream_init(JsonStream *s, const JsonStreamParams *params) { + assert(s); + assert(params); + assert(params->phase); + assert(params->dispatch); + + char *delimiter = NULL; + if (params->delimiter) { + delimiter = strdup(params->delimiter); + if (!delimiter) + return -ENOMEM; + } + + *s = (JsonStream) { + .delimiter = delimiter, + .buffer_max = params->buffer_max > 0 ? params->buffer_max : JSON_STREAM_BUFFER_MAX_DEFAULT, + .read_chunk = params->read_chunk > 0 ? params->read_chunk : JSON_STREAM_READ_SIZE_DEFAULT, + .queue_max = params->queue_max > 0 ? params->queue_max : JSON_STREAM_QUEUE_MAX_DEFAULT, + .phase_cb = params->phase, + .dispatch_cb = params->dispatch, + .userdata = params->userdata, + .input_fd = -EBADF, + .output_fd = -EBADF, + .timeout = USEC_INFINITY, + .last_activity = USEC_INFINITY, + .ucred = UCRED_INVALID, + .peer_pidfd = -EBADF, + .af = -1, + }; + + return 0; +} + +static void json_stream_clear(JsonStream *s) { + if (!s) + return; + + json_stream_detach_event(s); + + s->delimiter = mfree(s->delimiter); + s->description = mfree(s->description); + + if (s->input_fd != s->output_fd) { + s->input_fd = safe_close(s->input_fd); + s->output_fd = safe_close(s->output_fd); + } else + s->output_fd = s->input_fd = safe_close(s->input_fd); + + s->peer_pidfd = safe_close(s->peer_pidfd); + s->ucred_acquired = false; + s->af = -1; + + close_many(s->input_fds, s->n_input_fds); + s->input_fds = mfree(s->input_fds); + s->n_input_fds = 0; + + s->input_buffer = FLAGS_SET(s->flags, JSON_STREAM_INPUT_SENSITIVE) ? erase_and_free(s->input_buffer) : mfree(s->input_buffer); + s->input_buffer_index = s->input_buffer_size = s->input_buffer_unscanned = 0; + + s->output_buffer = FLAGS_SET(s->flags, JSON_STREAM_OUTPUT_BUFFER_SENSITIVE) ? erase_and_free(s->output_buffer) : mfree(s->output_buffer); + s->output_buffer_index = s->output_buffer_size = 0; + s->flags &= ~JSON_STREAM_OUTPUT_BUFFER_SENSITIVE; + + s->input_control_buffer = mfree(s->input_control_buffer); + s->input_control_buffer_size = 0; + + close_many(s->output_fds, s->n_output_fds); + s->output_fds = mfree(s->output_fds); + s->n_output_fds = 0; + + LIST_CLEAR(queue, s->output_queue, json_stream_queue_item_free); + s->output_queue_tail = NULL; + s->n_output_queue = 0; +} + +void json_stream_done(JsonStream *s) { + if (!s) + return; + + json_stream_clear(s); +} + +int json_stream_set_description(JsonStream *s, const char *description) { + assert(s); + return free_and_strdup(&s->description, description); +} + +const char* json_stream_get_description(const JsonStream *s) { + assert(s); + return s->description; +} + +int json_stream_connect_address(JsonStream *s, const char *address) { + union sockaddr_union sockaddr; + int r; + + assert(s); + assert(address); + + _cleanup_close_ int sock_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (sock_fd < 0) + return json_stream_log_errno(s, errno, "Failed to create AF_UNIX socket: %m"); + + sock_fd = fd_move_above_stdio(sock_fd); + + r = sockaddr_un_set_path(&sockaddr.un, address); + if (r < 0) { + if (r != -ENAMETOOLONG) + return json_stream_log_errno(s, r, "Failed to set socket address '%s': %m", address); + + /* Path too long to fit into sockaddr_un, connect via O_PATH instead. */ + r = connect_unix_path(sock_fd, AT_FDCWD, address); + } else + r = RET_NERRNO(connect(sock_fd, &sockaddr.sa, r)); + + if (r < 0) { + if (!IN_SET(r, -EAGAIN, -EINPROGRESS)) + return json_stream_log_errno(s, r, "Failed to connect to %s: %m", address); + + /* The connect() is being processed in the background. As long as that's the + * case the socket is in a special state: we can poll it for POLLOUT, but + * write()s before POLLOUT will fail with ENOTCONN (rather than EAGAIN). Since + * ENOTCONN can mean two different things (not yet connected vs. already + * disconnected), we track this as a separate flag. */ + s->flags |= JSON_STREAM_CONNECTING; + } + + int fd = TAKE_FD(sock_fd); + return json_stream_attach_fds(s, fd, fd); +} + +int json_stream_attach_fds(JsonStream *s, int input_fd, int output_fd) { + struct stat st; + + assert(s); + + /* NB: input_fd and output_fd are donated to the JsonStream instance! */ + + if (s->input_fd != s->output_fd) { + safe_close(s->input_fd); + safe_close(s->output_fd); + } else + safe_close(s->input_fd); + + s->input_fd = input_fd; + s->output_fd = output_fd; + s->flags &= ~(JSON_STREAM_PREFER_READ|JSON_STREAM_PREFER_WRITE); + + /* Detect non-socket fds up front so the read/write paths use read()/write() for + * non-socket fds and send()/recv() for sockets (mostly for MSG_NOSIGNAL). */ + if (input_fd >= 0) { + if (fstat(input_fd, &st) < 0) + return -errno; + if (!S_ISSOCK(st.st_mode)) + s->flags |= JSON_STREAM_PREFER_READ; + } + + if (output_fd >= 0 && output_fd != input_fd) { + if (fstat(output_fd, &st) < 0) + return -errno; + if (!S_ISSOCK(st.st_mode)) + s->flags |= JSON_STREAM_PREFER_WRITE; + } else if (FLAGS_SET(s->flags, JSON_STREAM_PREFER_READ)) + s->flags |= JSON_STREAM_PREFER_WRITE; + + return 0; +} + +int json_stream_connect_fd_pair(JsonStream *s, int input_fd, int output_fd) { + int r; + + assert(s); + assert(input_fd >= 0); + assert(output_fd >= 0); + + r = fd_nonblock(input_fd, true); + if (r < 0) + return json_stream_log_errno(s, r, "Failed to make input fd %d nonblocking: %m", input_fd); + + if (input_fd != output_fd) { + r = fd_nonblock(output_fd, true); + if (r < 0) + return json_stream_log_errno(s, r, "Failed to make output fd %d nonblocking: %m", output_fd); + } + + return json_stream_attach_fds(s, input_fd, output_fd); +} + +bool json_stream_flags_set(const JsonStream *s, JsonStreamFlags flags) { + assert(s); + assert((flags & ~(JSON_STREAM_BOUNDED_READS|JSON_STREAM_INPUT_SENSITIVE|JSON_STREAM_ALLOW_FD_PASSING_INPUT|JSON_STREAM_ALLOW_FD_PASSING_OUTPUT)) == 0); + + return FLAGS_SET(s->flags, flags); +} + +/* Multiple flags may be passed — all are set or cleared together. */ +void json_stream_set_flags(JsonStream *s, JsonStreamFlags flags, bool b) { + assert(s); + assert((flags & ~(JSON_STREAM_BOUNDED_READS|JSON_STREAM_INPUT_SENSITIVE)) == 0); + + SET_FLAG(s->flags, flags, b); +} + +bool json_stream_has_buffered_input(const JsonStream *s) { + assert(s); + return s->input_buffer_size > 0; +} + +/* Query the consumer's current phase. The callback is mandatory (asserted at construction + * time), so we can call it unconditionally. */ +static JsonStreamPhase json_stream_current_phase(const JsonStream *s) { + assert(s); + return s->phase_cb(s->userdata); +} + +/* Both READING and AWAITING_REPLY mean "we want POLLIN and would lose if the read side + * died" — they only differ in whether the idle timeout is in force. */ +static bool phase_is_reading(JsonStreamPhase p) { + return IN_SET(p, JSON_STREAM_PHASE_READING, JSON_STREAM_PHASE_AWAITING_REPLY); +} + +bool json_stream_should_disconnect(const JsonStream *s) { + assert(s); + + /* Carefully decide when the consumer should initiate a teardown. We err on the side + * of staying around so half-open connections can flush remaining data and reads can + * surface buffered messages before we tear everything down. */ + + /* Wait until any in-flight async connect() completes — there's nothing reasonable + * to do until we know whether the socket is connected or not. */ + if (FLAGS_SET(s->flags, JSON_STREAM_CONNECTING)) + return false; + + /* Still bytes to write and we can write? Stay around so the flush can complete. */ + if (s->output_buffer_size > 0 && !FLAGS_SET(s->flags, JSON_STREAM_WRITE_DISCONNECTED)) + return false; + + /* Both sides gone already? Then there's no point in lingering. */ + if (FLAGS_SET(s->flags, JSON_STREAM_READ_DISCONNECTED|JSON_STREAM_WRITE_DISCONNECTED)) + return true; + + JsonStreamPhase phase = json_stream_current_phase(s); + + /* Caller is waiting for input but the read side is shut down — we'll never see + * another message. */ + if (phase_is_reading(phase) && FLAGS_SET(s->flags, JSON_STREAM_READ_DISCONNECTED)) + return true; + + /* Idle client whose write side has died, or we saw POLLHUP. We explicitly check for + * POLLHUP because we likely won't notice the write side being down via send() if we + * never wrote anything in the first place. */ + if (phase == JSON_STREAM_PHASE_IDLE_CLIENT && + (s->flags & (JSON_STREAM_WRITE_DISCONNECTED|JSON_STREAM_GOT_POLLHUP))) + return true; + + /* Caller has more output to send but the peer hung up, and we're either out of + * bytes or already saw a write error. Nothing left to do. */ + if (phase == JSON_STREAM_PHASE_PENDING_OUTPUT && + (FLAGS_SET(s->flags, JSON_STREAM_WRITE_DISCONNECTED) || s->output_buffer_size == 0) && + FLAGS_SET(s->flags, JSON_STREAM_GOT_POLLHUP)) + return true; + + return false; +} + +int json_stream_get_events(const JsonStream *s) { + int ret = 0; + + assert(s); + + /* While an asynchronous connect() is still in flight we only ask for POLLOUT, which + * tells us once the connection is fully established. We must not read or write before + * that. */ + if (FLAGS_SET(s->flags, JSON_STREAM_CONNECTING)) + return POLLOUT; + + if (phase_is_reading(json_stream_current_phase(s)) && + !FLAGS_SET(s->flags, JSON_STREAM_READ_DISCONNECTED) && + s->input_buffer_unscanned == 0) + ret |= POLLIN; + + if (!FLAGS_SET(s->flags, JSON_STREAM_WRITE_DISCONNECTED) && (s->output_queue || s->output_buffer_size > 0)) + ret |= POLLOUT; + + return ret; +} + +static void json_stream_handle_revents(JsonStream *s, int revents) { + assert(s); + + if (FLAGS_SET(s->flags, JSON_STREAM_CONNECTING)) { + /* If we have seen POLLOUT or POLLHUP on a socket we are asynchronously waiting a + * connect() to complete on, we know we are ready. We don't read the connection + * error here though — we'll get it on the next read() or write(). */ + if ((revents & (POLLOUT|POLLHUP)) == 0) + return; + + json_stream_log(s, "Asynchronous connection completed."); + s->flags &= ~JSON_STREAM_CONNECTING; + return; + } + + /* Note that we don't care much about POLLIN/POLLOUT here, we'll just try reading and + * writing what we can. However, we do care about POLLHUP to detect connection + * termination even if we momentarily don't want to read nor write anything. */ + if (FLAGS_SET(revents, POLLHUP)) { + json_stream_log(s, "Got POLLHUP from socket."); + s->flags |= JSON_STREAM_GOT_POLLHUP; + } +} + +int json_stream_wait(JsonStream *s, usec_t timeout) { + int events, r; + + assert(s); + + events = json_stream_get_events(s); + if (events < 0) + return events; + + /* MIN the caller's timeout with our own deadline (if any) so that we wake up to + * fire the idle timeout. */ + usec_t deadline = json_stream_get_timeout(s); + if (deadline != USEC_INFINITY) + timeout = MIN(timeout, usec_sub_unsigned(deadline, now(CLOCK_MONOTONIC))); + + struct pollfd pollfd[2]; + size_t n_poll_fd = 0; + + if (s->input_fd == s->output_fd) { + pollfd[n_poll_fd++] = (struct pollfd) { + .fd = s->input_fd, + .events = events, + }; + } else { + pollfd[n_poll_fd++] = (struct pollfd) { + .fd = s->input_fd, + .events = events & POLLIN, + }; + pollfd[n_poll_fd++] = (struct pollfd) { + .fd = s->output_fd, + .events = events & POLLOUT, + }; + } + + r = ppoll_usec(pollfd, n_poll_fd, timeout); + if (ERRNO_IS_NEG_TRANSIENT(r)) + /* Treat EINTR as not a timeout, but also nothing happened, and the caller gets + * a chance to call back into us. */ + return 1; + if (r <= 0) + return r; + + int revents = 0; + FOREACH_ARRAY(p, pollfd, n_poll_fd) + revents |= p->revents; + + json_stream_handle_revents(s, revents); + return 1; +} + +/* ===== Timeout management ===== */ + +static usec_t json_stream_get_deadline(const JsonStream *s) { + assert(s); + + return usec_add(s->last_activity, s->timeout); +} + +usec_t json_stream_get_timeout(const JsonStream *s) { + assert(s); + + /* The deadline is in force only when the consumer is in PHASE_AWAITING_REPLY. In + * other phases (idle server, between operations) we ignore the cached deadline even + * if it's still set from a previous operation. */ + if (json_stream_current_phase(s) != JSON_STREAM_PHASE_AWAITING_REPLY) + return USEC_INFINITY; + + return json_stream_get_deadline(s); +} + +static void json_stream_rearm_time_source(JsonStream *s) { + int r; + + assert(s); + + if (!s->time_event_source) + return; + + usec_t deadline = json_stream_get_timeout(s); + if (deadline == USEC_INFINITY) { + (void) sd_event_source_set_enabled(s->time_event_source, SD_EVENT_OFF); + return; + } + + r = sd_event_source_set_time(s->time_event_source, deadline); + if (r < 0) { + json_stream_log_errno(s, r, "Failed to set time source deadline: %m"); + return; + } + + (void) sd_event_source_set_enabled(s->time_event_source, SD_EVENT_ON); +} + +void json_stream_set_timeout(JsonStream *s, usec_t timeout) { + assert(s); + + s->timeout = timeout; + + /* If the configured timeout changes mid-flight, rearm the time source so the new + * deadline takes effect immediately rather than waiting for the next mark_activity + * or successful write. */ + json_stream_rearm_time_source(s); +} + +void json_stream_mark_activity(JsonStream *s) { + assert(s); + + s->last_activity = json_stream_now(s); + json_stream_rearm_time_source(s); +} + +static int json_stream_acquire_peer_ucred(JsonStream *s, struct ucred *ret) { + int r; + + assert(s); + assert(ret); + + if (!s->ucred_acquired) { + /* Peer credentials only make sense for a bidirectional socket. */ + if (s->input_fd != s->output_fd) + return -EBADF; + + r = getpeercred(s->input_fd, &s->ucred); + if (r < 0) + return r; + + s->ucred_acquired = true; + } + + *ret = s->ucred; + return 0; +} + +int json_stream_acquire_peer_uid(JsonStream *s, uid_t *ret) { + struct ucred ucred; + int r; + + assert(s); + assert(ret); + + r = json_stream_acquire_peer_ucred(s, &ucred); + if (r < 0) + return json_stream_log_errno(s, r, "Failed to acquire credentials: %m"); + + if (!uid_is_valid(ucred.uid)) + return json_stream_log_errno(s, SYNTHETIC_ERRNO(ENODATA), "Peer UID is invalid."); + + *ret = ucred.uid; + return 0; +} + +int json_stream_acquire_peer_gid(JsonStream *s, gid_t *ret) { + struct ucred ucred; + int r; + + assert(s); + assert(ret); + + r = json_stream_acquire_peer_ucred(s, &ucred); + if (r < 0) + return json_stream_log_errno(s, r, "Failed to acquire credentials: %m"); + + if (!gid_is_valid(ucred.gid)) + return json_stream_log_errno(s, SYNTHETIC_ERRNO(ENODATA), "Peer GID is invalid."); + + *ret = ucred.gid; + return 0; +} + +int json_stream_acquire_peer_pid(JsonStream *s, pid_t *ret) { + struct ucred ucred; + int r; + + assert(s); + assert(ret); + + r = json_stream_acquire_peer_ucred(s, &ucred); + if (r < 0) + return json_stream_log_errno(s, r, "Failed to acquire credentials: %m"); + + if (!pid_is_valid(ucred.pid)) + return json_stream_log_errno(s, SYNTHETIC_ERRNO(ENODATA), "Peer PID is invalid."); + + *ret = ucred.pid; + return 0; +} + +int json_stream_get_peer_ucred(const JsonStream *s, struct ucred *ret) { + assert(s); + assert(ret); + + if (!s->ucred_acquired) + return -ENODATA; + + *ret = s->ucred; + return 0; +} + +void json_stream_set_peer_ucred(JsonStream *s, const struct ucred *ucred) { + assert(s); + assert(ucred); + + s->ucred = *ucred; + s->ucred_acquired = true; +} + +int json_stream_acquire_peer_pidfd(JsonStream *s) { + assert(s); + + if (s->peer_pidfd >= 0) + return s->peer_pidfd; + + if (s->input_fd != s->output_fd) + return json_stream_log_errno(s, SYNTHETIC_ERRNO(EBADF), "Failed to acquire pidfd of peer: separate input/output fds"); + + s->peer_pidfd = getpeerpidfd(s->input_fd); + if (s->peer_pidfd < 0) + return json_stream_log_errno(s, s->peer_pidfd, "Failed to acquire pidfd of peer: %m"); + + return s->peer_pidfd; +} + +static int json_stream_verify_unix_socket(JsonStream *s) { + assert(s); + + /* Returns: + * • 0 if this is an AF_UNIX socket + * • -ENOTSOCK if this is not a socket at all + * • -ENOMEDIUM if this is a socket, but not an AF_UNIX socket + * + * The result is cached after the first call. af < 0 = unchecked, af == AF_UNSPEC = + * checked but not a socket, otherwise af is the resolved address family. */ + + if (s->af < 0) { + /* If we have distinct input + output fds, we don't consider ourselves to be + * connected via a regular AF_UNIX socket. */ + if (s->input_fd != s->output_fd) { + s->af = AF_UNSPEC; + return -ENOTSOCK; + } + + struct stat st; + + if (fstat(s->input_fd, &st) < 0) + return -errno; + if (!S_ISSOCK(st.st_mode)) { + s->af = AF_UNSPEC; + return -ENOTSOCK; + } + + s->af = socket_get_family(s->input_fd); + if (s->af < 0) + return s->af; + } + + if (s->af == AF_UNIX) + return 0; + if (s->af == AF_UNSPEC) + return -ENOTSOCK; + + return -ENOMEDIUM; +} + +int json_stream_set_allow_fd_passing_input(JsonStream *s, bool enabled, bool with_sockopt) { + int r; + + assert(s); + + if (FLAGS_SET(s->flags, JSON_STREAM_ALLOW_FD_PASSING_INPUT) == enabled) + return 0; + + r = json_stream_verify_unix_socket(s); + if (r < 0) { + /* If the caller is disabling, accept the verify failure silently — we just + * leave the flag as it was (or set it to false if currently true). */ + if (!enabled) { + s->flags &= ~JSON_STREAM_ALLOW_FD_PASSING_INPUT; + return 0; + } + return r; + } + + if (with_sockopt) { + r = setsockopt_int(s->input_fd, SOL_SOCKET, SO_PASSRIGHTS, enabled); + if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) + json_stream_log_errno(s, r, "Failed to set SO_PASSRIGHTS socket option: %m"); + } + + SET_FLAG(s->flags, JSON_STREAM_ALLOW_FD_PASSING_INPUT, enabled); + return 1; +} + +int json_stream_set_allow_fd_passing_output(JsonStream *s, bool enabled) { + int r; + + assert(s); + + if (FLAGS_SET(s->flags, JSON_STREAM_ALLOW_FD_PASSING_OUTPUT) == enabled) + return 0; + + r = json_stream_verify_unix_socket(s); + if (r < 0) + return r; + + SET_FLAG(s->flags, JSON_STREAM_ALLOW_FD_PASSING_OUTPUT, enabled); + return 1; +} + +/* ===== sd-event integration ===== */ + +static int json_stream_io_callback(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + JsonStream *s = ASSERT_PTR(userdata); + int r; + + json_stream_handle_revents(s, revents); + + r = s->dispatch_cb(s->userdata); + if (r < 0) + json_stream_log_errno(s, r, "Dispatch callback failed, ignoring: %m"); + + return 1; +} + +static int json_stream_time_callback(sd_event_source *source, uint64_t usec, void *userdata) { + JsonStream *s = ASSERT_PTR(userdata); + int r; + + /* Disable the source: it must not fire again until activity is marked. The consumer + * notices the timeout by comparing now() to json_stream_get_timeout() in its dispatch + * callback. */ + (void) sd_event_source_set_enabled(s->time_event_source, SD_EVENT_OFF); + + r = s->dispatch_cb(s->userdata); + if (r < 0) + json_stream_log_errno(s, r, "Dispatch callback failed, ignoring: %m"); + + return 1; +} + +static int json_stream_prepare_callback(sd_event_source *source, void *userdata) { + JsonStream *s = ASSERT_PTR(userdata); + int r, e; + + e = json_stream_get_events(s); + if (e < 0) + return e; + + if (s->input_event_source == s->output_event_source) + /* Same fd for input + output */ + r = sd_event_source_set_io_events(s->input_event_source, e); + else { + r = sd_event_source_set_io_events(s->input_event_source, e & POLLIN); + if (r >= 0) + r = sd_event_source_set_io_events(s->output_event_source, e & POLLOUT); + } + if (r < 0) + return json_stream_log_errno(s, r, "Failed to set io events: %m"); + + /* Rearm the timeout on every prepare cycle so that phase transitions (e.g. entering + * AWAITING_REPLY) are picked up without requiring the consumer to explicitly call + * mark_activity at every state change. */ + json_stream_rearm_time_source(s); + + return 1; +} + +void json_stream_detach_event(JsonStream *s) { + if (!s) + return; + + s->input_event_source = sd_event_source_disable_unref(s->input_event_source); + s->output_event_source = sd_event_source_disable_unref(s->output_event_source); + s->time_event_source = sd_event_source_disable_unref(s->time_event_source); + s->event = sd_event_unref(s->event); +} + +sd_event* json_stream_get_event(const JsonStream *s) { + assert(s); + return s->event; +} + +int json_stream_attach_event(JsonStream *s, sd_event *event, int64_t priority) { + int r; + + assert(s); + assert(!s->event); + assert(s->input_fd >= 0); + assert(s->output_fd >= 0); + + if (event) + s->event = sd_event_ref(event); + else { + r = sd_event_default(&s->event); + if (r < 0) + return json_stream_log_errno(s, r, "Failed to acquire default event loop: %m"); + } + + r = sd_event_add_io(s->event, &s->input_event_source, s->input_fd, 0, json_stream_io_callback, s); + if (r < 0) + goto fail; + + r = sd_event_source_set_prepare(s->input_event_source, json_stream_prepare_callback); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(s->input_event_source, priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(s->input_event_source, "json-stream-input"); + + if (s->input_fd == s->output_fd) + s->output_event_source = sd_event_source_ref(s->input_event_source); + else { + r = sd_event_add_io(s->event, &s->output_event_source, s->output_fd, 0, json_stream_io_callback, s); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(s->output_event_source, priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(s->output_event_source, "json-stream-output"); + } + + r = sd_event_add_time(s->event, &s->time_event_source, CLOCK_MONOTONIC, /* usec= */ 0, /* accuracy= */ 0, + json_stream_time_callback, s); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(s->time_event_source, priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(s->time_event_source, "json-stream-time"); + + /* Initially disabled — only enabled by mark_activity once a timeout is configured. */ + (void) sd_event_source_set_enabled(s->time_event_source, SD_EVENT_OFF); + json_stream_rearm_time_source(s); + + return 0; + +fail: + json_stream_log_errno(s, r, "Failed to attach event source: %m"); + json_stream_detach_event(s); + return r; +} + +int json_stream_flush(JsonStream *s) { + int ret = 0, r; + + assert(s); + + for (;;) { + if (s->output_buffer_size == 0 && !s->output_queue) + break; + if (FLAGS_SET(s->flags, JSON_STREAM_WRITE_DISCONNECTED)) + return -ECONNRESET; + + r = json_stream_write(s); + if (r < 0) + return r; + if (r > 0) { + ret = 1; + continue; + } + + r = json_stream_wait(s, USEC_INFINITY); + if (ERRNO_IS_NEG_TRANSIENT(r)) + continue; + if (r < 0) + return json_stream_log_errno(s, r, "Poll failed on fd: %m"); + assert(r > 0); + } + + return ret; +} + +int json_stream_peek_input_fd(const JsonStream *s, size_t i) { + assert(s); + + if (i >= s->n_input_fds) + return -ENXIO; + + return s->input_fds[i]; +} + +int json_stream_take_input_fd(JsonStream *s, size_t i) { + assert(s); + + if (i >= s->n_input_fds) + return -ENXIO; + + return TAKE_FD(s->input_fds[i]); +} + +size_t json_stream_get_n_input_fds(const JsonStream *s) { + assert(s); + return s->n_input_fds; +} + +void json_stream_close_input_fds(JsonStream *s) { + assert(s); + + close_many(s->input_fds, s->n_input_fds); + s->input_fds = mfree(s->input_fds); + s->n_input_fds = 0; +} + +/* ===== Output formatting ===== */ + +static int json_stream_format_json(JsonStream *s, sd_json_variant *m) { + _cleanup_(erase_and_freep) char *text = NULL; + ssize_t sz, r; + + assert(s); + assert(m); + + sz = sd_json_variant_format(m, /* flags= */ 0, &text); + if (sz < 0) + return sz; + assert(text[sz] == '\0'); + + size_t dsz = json_stream_delimiter_size(s); + + /* Append the framing delimiter after the formatted JSON. For varlink (delimiter == + * NULL) this keeps the trailing NUL already placed by sd_json_variant_format(); for + * multi-char delimiters (e.g. "\r\n") we grow the buffer and copy them in. */ + if (s->delimiter) { + if (!GREEDY_REALLOC(text, sz + dsz)) + return -ENOMEM; + memcpy(text + sz, s->delimiter, dsz); + } + + if (s->output_buffer_size + sz + dsz > s->buffer_max) + return -ENOBUFS; + + if (DEBUG_LOGGING) { + _cleanup_(erase_and_freep) char *censored_text = NULL; + + /* Suppress sensitive fields in the debug output */ + r = sd_json_variant_format(m, SD_JSON_FORMAT_CENSOR_SENSITIVE, &censored_text); + if (r >= 0) + json_stream_log(s, "Sending message: %s", censored_text); + } + + if (s->output_buffer_size == 0) { + if (FLAGS_SET(s->flags, JSON_STREAM_OUTPUT_BUFFER_SENSITIVE)) { + s->output_buffer = erase_and_free(s->output_buffer); + s->flags &= ~JSON_STREAM_OUTPUT_BUFFER_SENSITIVE; + } + + free_and_replace(s->output_buffer, text); + + s->output_buffer_size = sz + dsz; + s->output_buffer_index = 0; + + } else if (!FLAGS_SET(s->flags, JSON_STREAM_OUTPUT_BUFFER_SENSITIVE) && s->output_buffer_index == 0) { + if (!GREEDY_REALLOC(s->output_buffer, s->output_buffer_size + sz + dsz)) + return -ENOMEM; + + memcpy(s->output_buffer + s->output_buffer_size, text, sz + dsz); + s->output_buffer_size += sz + dsz; + } else { + const size_t new_size = s->output_buffer_size + sz + dsz; + + char *n = new(char, new_size); + if (!n) + return -ENOMEM; + + memcpy(mempcpy(n, s->output_buffer + s->output_buffer_index, s->output_buffer_size), text, sz + dsz); + + if (FLAGS_SET(s->flags, JSON_STREAM_OUTPUT_BUFFER_SENSITIVE)) + s->output_buffer = erase_and_free(s->output_buffer); + else + free(s->output_buffer); + s->output_buffer = n; + s->output_buffer_size = new_size; + s->output_buffer_index = 0; + } + + if (sd_json_variant_is_sensitive_recursive(m)) + s->flags |= JSON_STREAM_OUTPUT_BUFFER_SENSITIVE; + else + text = mfree(text); /* Skip the erase_and_free() destructor declared above */ + + return 0; +} + +static int json_stream_format_queue(JsonStream *s) { + int r; + + assert(s); + + /* Drain entries out of the output queue and format them into the output buffer. + * Stop if there are unwritten output_fds or if the next item carries fds but + * the output buffer is non-empty, since adding more would corrupt the fd boundary. */ + + while (s->output_queue) { + assert(s->n_output_queue > 0); + + if (s->n_output_fds > 0) + return 0; + + JsonStreamQueueItem *q = s->output_queue; + + /* If the next item carries fds but the output buffer still holds bytes from + * a prior fast-path enqueue or a partial write, we must not concatenate its + * JSON into that same buffer: the subsequent sendmsg() in json_stream_write() + * would attach the fds to the combined bytes and break the message-to-fd boundary. + * Stop here and let json_stream_write() drain the buffer first; the next write() + * call will pull this item into a clean buffer. + * + * Note: this only produces a difference on SOCK_SEQPACKET / SOCK_DGRAM, where + * each sendmsg() is its own datagram with its own SCM_RIGHTS cmsg. On AF_UNIX + * SOCK_STREAM the kernel absorbs a preceding non-scm skb forward into the + * next scm-bearing skb's recv, so per-sendmsg separation is invisible to the + * receiver anyway. Kept as cheap defensive sender hygiene that's necessary + * the moment a SEQPACKET/DGRAM consumer wires JsonStream up. */ + if (q->n_fds > 0 && s->output_buffer_size > 0) + return 0; + + _cleanup_free_ int *array = NULL; + if (q->n_fds > 0) { + array = newdup(int, q->fds, q->n_fds); + if (!array) + return -ENOMEM; + } + + r = json_stream_format_json(s, q->data); + if (r < 0) + return r; + + free_and_replace(s->output_fds, array); + s->n_output_fds = q->n_fds; + q->n_fds = 0; + + LIST_REMOVE(queue, s->output_queue, q); + if (!s->output_queue) + s->output_queue_tail = NULL; + s->n_output_queue--; + + json_stream_queue_item_free(q); + } + + return 0; +} + +int json_stream_enqueue_full(JsonStream *s, sd_json_variant *m, const int fds[], size_t n_fds) { + assert(s); + assert(m); + assert(fds || n_fds == 0); + + /* Fast path: no fds and no items currently queued — append directly into the + * output buffer to avoid the queue allocation. */ + if (n_fds == 0 && !s->output_queue) + return json_stream_format_json(s, m); + + if (s->n_output_queue >= s->queue_max) + return -ENOBUFS; + + JsonStreamQueueItem *q = json_stream_queue_item_new(m, fds, n_fds); + if (!q) + return -ENOMEM; + + LIST_INSERT_AFTER(queue, s->output_queue, s->output_queue_tail, q); + s->output_queue_tail = q; + s->n_output_queue++; + return 0; +} + +/* ===== Write side ===== */ + +int json_stream_write(JsonStream *s) { + ssize_t n; + int r; + + assert(s); + + if (FLAGS_SET(s->flags, JSON_STREAM_CONNECTING)) + return 0; + if (FLAGS_SET(s->flags, JSON_STREAM_WRITE_DISCONNECTED)) + return 0; + + /* Drain the deferred queue into the output buffer if possible */ + r = json_stream_format_queue(s); + if (r < 0) + return r; + + if (s->output_buffer_size == 0) + return 0; + + assert(s->output_fd >= 0); + + if (s->n_output_fds > 0) { + struct iovec iov = { + .iov_base = s->output_buffer + s->output_buffer_index, + .iov_len = s->output_buffer_size, + }; + struct msghdr mh = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_controllen = CMSG_SPACE(sizeof(int) * s->n_output_fds), + }; + + mh.msg_control = alloca0(mh.msg_controllen); + + struct cmsghdr *control = CMSG_FIRSTHDR(&mh); + control->cmsg_len = CMSG_LEN(sizeof(int) * s->n_output_fds); + control->cmsg_level = SOL_SOCKET; + control->cmsg_type = SCM_RIGHTS; + memcpy(CMSG_DATA(control), s->output_fds, sizeof(int) * s->n_output_fds); + + n = sendmsg(s->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + } else if (FLAGS_SET(s->flags, JSON_STREAM_PREFER_WRITE)) + n = write(s->output_fd, s->output_buffer + s->output_buffer_index, s->output_buffer_size); + else + n = send(s->output_fd, s->output_buffer + s->output_buffer_index, s->output_buffer_size, MSG_DONTWAIT|MSG_NOSIGNAL); + if (n < 0) { + if (ERRNO_IS_TRANSIENT(errno)) + return 0; + + if (ERRNO_IS_DISCONNECT(errno)) { + s->flags |= JSON_STREAM_WRITE_DISCONNECTED; + return 1; + } + + return -errno; + } + + if (FLAGS_SET(s->flags, JSON_STREAM_OUTPUT_BUFFER_SENSITIVE)) + explicit_bzero_safe(s->output_buffer + s->output_buffer_index, n); + + s->output_buffer_size -= n; + + if (s->output_buffer_size == 0) { + s->output_buffer_index = 0; + s->flags &= ~JSON_STREAM_OUTPUT_BUFFER_SENSITIVE; + } else + s->output_buffer_index += n; + + close_many(s->output_fds, s->n_output_fds); + s->n_output_fds = 0; + + /* Refresh activity timestamp on real progress (and rearm the time source if attached + * to an event loop). */ + s->last_activity = json_stream_now(s); + json_stream_rearm_time_source(s); + + return 1; +} + +/* ===== Read side ===== */ + +/* In bounded-reads mode, peek at the socket data to find the delimiter and return a read + * size that won't consume past it. This prevents over-reading data that belongs to whatever + * protocol the socket is being handed off to. Falls back to byte-by-byte for non-socket fds + * where MSG_PEEK is not available. */ +static ssize_t json_stream_peek_message_boundary(JsonStream *s, void *p, size_t rs) { + assert(s); + + if (!FLAGS_SET(s->flags, JSON_STREAM_BOUNDED_READS)) + return rs; + + if (FLAGS_SET(s->flags, JSON_STREAM_PREFER_READ)) + return 1; + + ssize_t peeked = recv(s->input_fd, p, rs, MSG_PEEK|MSG_DONTWAIT); + if (peeked < 0) { + if (!ERRNO_IS_TRANSIENT(errno)) + return -errno; + + /* Transient error: shouldn't happen but fall back to byte-by-byte */ + return 1; + } + /* EOF: the real recv() will also see it; what we return here doesn't matter */ + if (peeked == 0) + return rs; + + size_t dsz = json_stream_delimiter_size(s); + void *delim = memmem_safe(p, peeked, s->delimiter ?: "\0", dsz); + if (delim) + return (ssize_t) ((char*) delim - (char*) p) + dsz; + + return peeked; +} + +int json_stream_read(JsonStream *s) { + struct iovec iov; + struct msghdr mh; + ssize_t rs; + ssize_t n; + void *p; + + assert(s); + + if (FLAGS_SET(s->flags, JSON_STREAM_CONNECTING)) + return 0; + if (s->input_buffer_unscanned > 0) + return 0; + if (FLAGS_SET(s->flags, JSON_STREAM_READ_DISCONNECTED)) + return 0; + + if (s->input_buffer_size >= s->buffer_max) + return -ENOBUFS; + + assert(s->input_fd >= 0); + + if (MALLOC_SIZEOF_SAFE(s->input_buffer) <= s->input_buffer_index + s->input_buffer_size) { + size_t add; + + add = MIN(s->buffer_max - s->input_buffer_size, s->read_chunk); + + if (s->input_buffer_index == 0 && + (!FLAGS_SET(s->flags, JSON_STREAM_INPUT_SENSITIVE) || s->input_buffer_size == 0)) { + if (!GREEDY_REALLOC(s->input_buffer, s->input_buffer_size + add)) + return -ENOMEM; + } else { + char *b; + + b = new(char, s->input_buffer_size + add); + if (!b) + return -ENOMEM; + + memcpy(b, s->input_buffer + s->input_buffer_index, s->input_buffer_size); + + if (FLAGS_SET(s->flags, JSON_STREAM_INPUT_SENSITIVE)) + s->input_buffer = erase_and_free(s->input_buffer); + else + free(s->input_buffer); + s->input_buffer = b; + s->input_buffer_index = 0; + } + } + + p = s->input_buffer + s->input_buffer_index + s->input_buffer_size; + + rs = MALLOC_SIZEOF_SAFE(s->input_buffer) - (s->input_buffer_index + s->input_buffer_size); + + /* If a protocol upgrade may follow, ensure we don't consume any post-upgrade bytes by + * limiting the read to the next delimiter. Uses MSG_PEEK on sockets, single-byte reads + * otherwise. */ + rs = json_stream_peek_message_boundary(s, p, rs); + if (rs < 0) + return json_stream_log_errno(s, (int) rs, "Failed to peek message boundary: %m"); + + if (FLAGS_SET(s->flags, JSON_STREAM_ALLOW_FD_PASSING_INPUT)) { + iov = IOVEC_MAKE(p, rs); + + if (!s->input_control_buffer) { + s->input_control_buffer_size = CMSG_SPACE(sizeof(int) * JSON_STREAM_FDS_MAX); + s->input_control_buffer = malloc(s->input_control_buffer_size); + if (!s->input_control_buffer) + return -ENOMEM; + } + + mh = (struct msghdr) { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = s->input_control_buffer, + .msg_controllen = s->input_control_buffer_size, + }; + + n = recvmsg_safe(s->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + } else if (FLAGS_SET(s->flags, JSON_STREAM_PREFER_READ)) + n = RET_NERRNO(read(s->input_fd, p, rs)); + else + n = RET_NERRNO(recv(s->input_fd, p, rs, MSG_DONTWAIT)); + if (ERRNO_IS_NEG_TRANSIENT(n)) + return 0; + if (ERRNO_IS_NEG_DISCONNECT(n)) { + s->flags |= JSON_STREAM_READ_DISCONNECTED; + return 1; + } + if (n < 0) + return n; + if (n == 0) { /* EOF */ + if (FLAGS_SET(s->flags, JSON_STREAM_ALLOW_FD_PASSING_INPUT)) + cmsg_close_all(&mh); + + s->flags |= JSON_STREAM_READ_DISCONNECTED; + return 1; + } + + if (FLAGS_SET(s->flags, JSON_STREAM_ALLOW_FD_PASSING_INPUT)) { + struct cmsghdr *cmsg; + + cmsg = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, (socklen_t) -1); + if (cmsg) { + size_t add; + + /* fds are only allowed with the first byte of a message; receiving them + * mid-stream is a protocol violation. */ + if (s->input_buffer_size != 0) { + cmsg_close_all(&mh); + return -EPROTO; + } + + add = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + if (add > INT_MAX - s->n_input_fds) { + cmsg_close_all(&mh); + return -EBADF; + } + + if (!GREEDY_REALLOC(s->input_fds, s->n_input_fds + add)) { + cmsg_close_all(&mh); + return -ENOMEM; + } + + memcpy_safe(s->input_fds + s->n_input_fds, CMSG_TYPED_DATA(cmsg, int), add * sizeof(int)); + s->n_input_fds += add; + } + } + + s->input_buffer_size += n; + s->input_buffer_unscanned += n; + + return 1; +} + +/* ===== Parse ===== */ + +int json_stream_parse(JsonStream *s, sd_json_variant **ret) { + char *begin, *e; + size_t sz; + int r; + + assert(s); + assert(ret); + + if (s->input_buffer_unscanned == 0) { + *ret = NULL; + return 0; + } + + assert(s->input_buffer_unscanned <= s->input_buffer_size); + assert(s->input_buffer_index + s->input_buffer_size <= MALLOC_SIZEOF_SAFE(s->input_buffer)); + + begin = s->input_buffer + s->input_buffer_index; + + size_t dsz = json_stream_delimiter_size(s); + e = memmem_safe(begin + s->input_buffer_size - s->input_buffer_unscanned, s->input_buffer_unscanned, s->delimiter ?: "\0", dsz); + if (!e) { + s->input_buffer_unscanned = 0; + *ret = NULL; + return 0; + } + + sz = e - begin + dsz; + + /* For non-NUL delimiters (e.g. "\r\n" for QMP) sd_json_parse() needs a NUL-terminated + * string; overwrite the first delimiter byte with NUL in place. For NUL delimiters + * this is a no-op since the byte is already '\0'. */ + if (s->delimiter) + *e = '\0'; + + r = sd_json_parse(begin, SD_JSON_PARSE_MUST_BE_OBJECT, ret, /* reterr_line= */ NULL, /* reterr_column= */ NULL); + if (FLAGS_SET(s->flags, JSON_STREAM_INPUT_SENSITIVE)) + explicit_bzero_safe(begin, sz); + if (r < 0) { + /* Unrecoverable parse failure: drop all buffered data. */ + s->input_buffer_index = s->input_buffer_size = s->input_buffer_unscanned = 0; + return json_stream_log_errno(s, r, "Failed to parse JSON object: %m"); + } + + if (DEBUG_LOGGING) { + _cleanup_(erase_and_freep) char *censored_text = NULL; + + /* Suppress sensitive fields in the debug output */ + r = sd_json_variant_format(*ret, /* flags= */ SD_JSON_FORMAT_CENSOR_SENSITIVE, &censored_text); + if (r >= 0) + json_stream_log(s, "Received message: %s", censored_text); + } + + s->input_buffer_size -= sz; + + if (s->input_buffer_size == 0) + s->input_buffer_index = 0; + else + s->input_buffer_index += sz; + + s->input_buffer_unscanned = s->input_buffer_size; + return 1; +} diff --git a/src/libsystemd/sd-json/json-stream.h b/src/libsystemd/sd-json/json-stream.h new file mode 100644 index 0000000000000..b502c98676e12 --- /dev/null +++ b/src/libsystemd/sd-json/json-stream.h @@ -0,0 +1,270 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "sd-forward.h" + +#include "list.h" +#include "log.h" + +/* JsonStream provides the transport layer used by sd-varlink (and other consumers like + * the QMP client) for exchanging length-delimited JSON messages over a pair of file + * descriptors. It owns the input/output buffers, the file-descriptor passing machinery + * (SCM_RIGHTS), the deferred output queue, and the read/write/parse step functions. It + * does not implement any state machine, dispatch, callback or event-source plumbing — + * those concerns belong to the consumer. */ + +typedef struct JsonStreamQueueItem JsonStreamQueueItem; + +typedef enum JsonStreamFlags { + JSON_STREAM_BOUNDED_READS = 1u << 0, + JSON_STREAM_INPUT_SENSITIVE = 1u << 1, + JSON_STREAM_ALLOW_FD_PASSING_INPUT = 1u << 2, + JSON_STREAM_ALLOW_FD_PASSING_OUTPUT = 1u << 3, + JSON_STREAM_CONNECTING = 1u << 4, + JSON_STREAM_GOT_POLLHUP = 1u << 5, + JSON_STREAM_WRITE_DISCONNECTED = 1u << 6, + JSON_STREAM_READ_DISCONNECTED = 1u << 7, + JSON_STREAM_PREFER_READ = 1u << 8, + JSON_STREAM_PREFER_WRITE = 1u << 9, + JSON_STREAM_OUTPUT_BUFFER_SENSITIVE = 1u << 10, +} JsonStreamFlags; + +/* What the consumer's high-level state machine is currently doing — used by the various + * "what should I do right now?" APIs (get_events, wait, should_disconnect) to decide + * whether to ask for read events, whether transport death matters, and whether the idle + * timeout deadline is currently in force. */ +typedef enum JsonStreamPhase { + JSON_STREAM_PHASE_READING, /* waiting for the next inbound message, no deadline */ + JSON_STREAM_PHASE_AWAITING_REPLY, /* waiting for a reply with the idle timeout deadline */ + JSON_STREAM_PHASE_IDLE_CLIENT, /* idle client, no in-flight call */ + JSON_STREAM_PHASE_PENDING_OUTPUT, /* has more output queued, waiting to send */ + JSON_STREAM_PHASE_OTHER, /* none of the above */ +} JsonStreamPhase; + +/* Consumer hooks supplied at construction time: + * • phase — queried by get_events / wait / should_disconnect / attach_event's prepare + * callback whenever the consumer's current phase is needed. + * • dispatch — invoked by attach_event's io and time callbacks after the stream has + * consumed the revents, so the consumer can drive its state machine + * forward. Should return 0 on success or a negative errno; the stream logs + * the failure and continues running. */ +typedef JsonStreamPhase (*json_stream_phase_t)(void *userdata); +typedef int (*json_stream_dispatch_t)(void *userdata); + +typedef struct JsonStreamParams { + const char *delimiter; /* message delimiter; NULL → single NUL byte (varlink), e.g. "\r\n" for QMP */ + size_t buffer_max; /* maximum bytes buffered before -ENOBUFS; 0 = 16 MiB default */ + size_t read_chunk; /* per-read chunk size; 0 = 64 KiB default */ + size_t queue_max; /* maximum number of queued output items; 0 = 64 Ki default */ + + /* Consumer hooks (see typedefs above). */ + json_stream_phase_t phase; + json_stream_dispatch_t dispatch; + void *userdata; +} JsonStreamParams; + +typedef struct JsonStream { + char *delimiter; /* message delimiter; NULL → NUL byte (varlink), e.g. "\r\n" for QMP */ + size_t buffer_max; + size_t read_chunk; + size_t queue_max; + + char *description; + + int input_fd; + int output_fd; + + usec_t timeout; /* relative; USEC_INFINITY = no timeout */ + usec_t last_activity; /* CLOCK_MONOTONIC */ + + /* Cached peer credentials */ + struct ucred ucred; + bool ucred_acquired; + int peer_pidfd; + + /* Cached socket address family. -1 = unchecked, AF_UNSPEC = checked-not-socket, + * otherwise the resolved family. */ + int af; + + sd_event *event; + sd_event_source *input_event_source; + sd_event_source *output_event_source; + sd_event_source *time_event_source; + + json_stream_phase_t phase_cb; + json_stream_dispatch_t dispatch_cb; + void *userdata; + + char *input_buffer; + size_t input_buffer_index; + size_t input_buffer_size; + size_t input_buffer_unscanned; + + void *input_control_buffer; + size_t input_control_buffer_size; + + char *output_buffer; + size_t output_buffer_index; + size_t output_buffer_size; + + int *input_fds; + size_t n_input_fds; + + int *output_fds; + size_t n_output_fds; + + LIST_HEAD(JsonStreamQueueItem, output_queue); + JsonStreamQueueItem *output_queue_tail; + size_t n_output_queue; + + JsonStreamFlags flags; +} JsonStream; + +int json_stream_init(JsonStream *s, const JsonStreamParams *params); +void json_stream_done(JsonStream *s); + +/* Optional description used as the prefix for the stream's debug log lines (sent/received + * messages, POLLHUP detection, async connect completion, etc.). The string is duped. */ +int json_stream_set_description(JsonStream *s, const char *description); +const char* json_stream_get_description(const JsonStream *s); + +static inline const char* json_stream_description(const JsonStream *s) { + return (s ? s->description : NULL) ?: "json-stream"; +} + +#define json_stream_log(s, fmt, ...) \ + log_debug("%s: " fmt, json_stream_description(s), ##__VA_ARGS__) + +#define json_stream_log_errno(s, error, fmt, ...) \ + log_debug_errno((error), "%s: " fmt, json_stream_description(s), ##__VA_ARGS__) + +/* fd ownership */ +int json_stream_attach_fds(JsonStream *s, int input_fd, int output_fd); + +/* Open an AF_UNIX SOCK_STREAM socket and connect to the given filesystem path, attaching + * the resulting fd to the stream. Handles paths too long for sockaddr_un by routing through + * O_PATH (connect_unix_path()). If the connect() returns EAGAIN/EINPROGRESS the stream's + * connecting state is set so that the consumer waits for POLLOUT before treating the + * connection as established. Returns 0 on success or successfully started async connect, + * negative errno on failure. */ +int json_stream_connect_address(JsonStream *s, const char *address); + +/* Adopt a pre-connected pair of fds, ensuring both are non-blocking. Equivalent to + * json_stream_attach_fds() but does the fd_nonblock() dance up front, so the caller can + * pass in fds without having to know whether they were already configured. */ +int json_stream_connect_fd_pair(JsonStream *s, int input_fd, int output_fd); + +bool json_stream_flags_set(const JsonStream *s, JsonStreamFlags flags); +void json_stream_set_flags(JsonStream *s, JsonStreamFlags flags, bool b); + +/* Combines the transport-level disconnect signals (write/read disconnected, buffered + * output, POLLHUP, async connect) with the consumer's current phase (queried via the + * registered get_phase callback) to answer "should the consumer initiate teardown right + * now?". The decision logic mirrors what the original varlink transport did but stays + * generic enough for other JSON-line consumers. */ +bool json_stream_should_disconnect(const JsonStream *s); + +/* Enable/disable fd passing. These verify the underlying fd is an AF_UNIX socket and + * (for input) optionally set SO_PASSRIGHTS. */ +int json_stream_set_allow_fd_passing_input(JsonStream *s, bool enabled, bool with_sockopt); +int json_stream_set_allow_fd_passing_output(JsonStream *s, bool enabled); + +/* Output: enqueue a JSON variant together with an optional set of file descriptors. Fast + * path concatenates into the output buffer when fds is empty and the queue is empty; if fds + * are present or the queue is non-empty the message is queued instead, so that + * fd-to-message boundaries are preserved. The queue item copies the fd values; On success, + * ownership of the fd values transfers to the queue item (the caller must free its array + * without closing the fds). On failure, the fds remain untouched and the caller retains + * ownership. */ +int json_stream_enqueue_full(JsonStream *s, sd_json_variant *m, const int fds[], size_t n_fds); + +static inline int json_stream_enqueue(JsonStream *s, sd_json_variant *m) { + return json_stream_enqueue_full(s, m, NULL, 0); +} + +int json_stream_peek_input_fd(const JsonStream *s, size_t i); +int json_stream_take_input_fd(JsonStream *s, size_t i); +size_t json_stream_get_n_input_fds(const JsonStream *s); + +/* Close and free all currently received input fds (used after consuming a message). */ +void json_stream_close_input_fds(JsonStream *s); + +/* I/O steps. Same return-value contract as the original varlink_{write,read,parse_message}: + * 1 = made progress (call again), + * 0 = nothing to do (wait for I/O), + * <0 = error. */ +int json_stream_write(JsonStream *s); +int json_stream_read(JsonStream *s); + +/* Extract the next complete JSON message from the input buffer (delimited per + * params.delimiter). Returns 1 with *ret set on success, 0 if no full message is + * available yet (with *ret == NULL), <0 on parse error. The buffer slot occupied by the + * parsed message is erased if input_sensitive was set. */ +int json_stream_parse(JsonStream *s, sd_json_variant **ret); + +/* Status accessors used by the consumer's state machine. */ +bool json_stream_has_buffered_input(const JsonStream *s); + +/* Compute the poll events the consumer should wait for. The stream queries the consumer's + * phase via the registered get_phase callback. In JSON_STREAM_PHASE_READING the stream asks + * for POLLIN (provided the input buffer is empty and the read side is still alive); POLLOUT + * is added whenever there's pending output. When connecting we only ask for POLLOUT to + * learn when the non-blocking connect() completes. */ +int json_stream_get_events(const JsonStream *s); + +/* Block on poll() for the configured fds for at most `timeout` µs. Internally updates the + * connecting / got_pollhup state based on the seen revents. + * 1 = some event was observed (call us again), + * 0 = timeout, + * <0 = error (negative errno from ppoll_usec). */ +int json_stream_wait(JsonStream *s, usec_t timeout); + +/* Block until the output buffer is fully drained (or the write side disconnects). + * 1 = some bytes were written during the flush, + * 0 = nothing to flush, + * -ECONNRESET if the write side became disconnected before everything could be sent, + * <0 on other I/O errors. */ +int json_stream_flush(JsonStream *s); + +/* Peer credential helpers. All refuse if the stream uses different input/output fds, since + * peer credentials are only meaningful for a bidirectional socket. + * • acquire_peer_uid/gid/pid/pidfd() query the kernel on first use, cache the result, + * and log failures (using the stream's description). They each return 0 on success + * with the value in *ret, or a negative errno on failure (kernel error or invalid + * field). + * • get_peer_ucred() returns the *already-cached* ucred (set via a prior acquire or via + * set_peer_ucred()) without triggering a kernel query — returns -ENODATA if nothing is + * cached. Used by consumers that want to react to a previously-known ucred without + * forcing a fresh query (e.g. teardown bookkeeping). */ +int json_stream_acquire_peer_uid(JsonStream *s, uid_t *ret); +int json_stream_acquire_peer_gid(JsonStream *s, gid_t *ret); +int json_stream_acquire_peer_pid(JsonStream *s, pid_t *ret); +int json_stream_acquire_peer_pidfd(JsonStream *s); +int json_stream_get_peer_ucred(const JsonStream *s, struct ucred *ret); +void json_stream_set_peer_ucred(JsonStream *s, const struct ucred *ucred); + +/* Per-operation idle timeout. The deadline is computed as last_activity + timeout. + * Successful writes refresh last_activity automatically; the consumer should also call + * json_stream_mark_activity() at operation start (e.g. when initiating a method call) to + * reset the deadline. + * + * When the deadline elapses the time event source attached via json_stream_attach_event() + * fires and the consumer's dispatch callback is invoked. The consumer detects the timeout + * by comparing now(CLOCK_MONOTONIC) against json_stream_get_timeout(). */ +void json_stream_set_timeout(JsonStream *s, usec_t timeout); +void json_stream_mark_activity(JsonStream *s); + +/* Returns the absolute deadline (in CLOCK_MONOTONIC microseconds) currently in force for + * the consumer's phase, or USEC_INFINITY if no timeout applies (no timeout configured, no + * activity yet, or the current phase isn't AWAITING_REPLY). */ +usec_t json_stream_get_timeout(const JsonStream *s); + +/* sd-event integration. JsonStream owns the input/output io event sources and the time + * event source for its idle timeout, and installs its own internal prepare and io callbacks + * on them. The hooks (get_phase, io_dispatch) supplied via JsonStreamParams at construction + * are wired up automatically. */ +int json_stream_attach_event(JsonStream *s, sd_event *event, int64_t priority); +void json_stream_detach_event(JsonStream *s); +sd_event* json_stream_get_event(const JsonStream *s); diff --git a/src/libsystemd/sd-json/json-util.c b/src/libsystemd/sd-json/json-util.c index 7f90b7fc7930c..c321579ef5093 100644 --- a/src/libsystemd/sd-json/json-util.c +++ b/src/libsystemd/sd-json/json-util.c @@ -9,6 +9,7 @@ #include "errno-util.h" #include "fd-util.h" #include "glyph-util.h" +#include "in-addr-util.h" #include "iovec-util.h" #include "json-util.h" #include "log.h" @@ -189,12 +190,59 @@ int json_dispatch_in_addr(const char *name, sd_json_variant *variant, sd_json_di return 0; } + /* We support a more human readable string based encoding, and an array based encoding */ + if (sd_json_variant_is_string(variant)) { + union in_addr_union a; + r = in_addr_from_string(AF_INET, sd_json_variant_string(variant), &a); + if (r < 0) + return json_log(variant, flags, r, + "JSON field '%s' is not a valid IPv4 address string: %s", strna(name), sd_json_variant_string(variant)); + + *address = a.in; + return 0; + } + r = json_dispatch_byte_array_iovec(name, variant, flags, &iov); if (r < 0) return r; if (iov.iov_len != sizeof(struct in_addr)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name)); + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), + "Expected JSON field '%s' to be an array of %zu bytes.", strna(name), sizeof(struct in_addr)); + + memcpy(address, iov.iov_base, iov.iov_len); + return 0; +} + +int json_dispatch_in6_addr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + struct in6_addr *address = ASSERT_PTR(userdata); + _cleanup_(iovec_done) struct iovec iov = {}; + int r; + + if (sd_json_variant_is_null(variant)) { + *address = (struct in6_addr) {}; + return 0; + } + + /* We support both a more human readable string based encoding and an array based encoding */ + if (sd_json_variant_is_string(variant)) { + union in_addr_union a; + r = in_addr_from_string(AF_INET6, sd_json_variant_string(variant), &a); + if (r < 0) + return json_log(variant, flags, r, + "JSON field '%s' is not a valid IPv6 address string: %s", strna(name), sd_json_variant_string(variant)); + + *address = a.in6; + return 0; + } + + r = json_dispatch_byte_array_iovec(name, variant, flags, &iov); + if (r < 0) + return r; + + if (iov.iov_len != sizeof(struct in6_addr)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), + "Expected JSON field '%s' to be an array of %zu bytes.", strna(name), sizeof(struct in6_addr)); memcpy(address, iov.iov_base, iov.iov_len); return 0; diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index 847725a41e292..cea2d368b43db 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -115,6 +115,7 @@ int json_dispatch_user_group_name(const char *name, sd_json_variant *variant, sd int json_dispatch_const_user_group_name(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_const_unit_name(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_in_addr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); +int json_dispatch_in6_addr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_const_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_strv_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); @@ -169,6 +170,7 @@ enum { _JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL, _JSON_BUILD_PAIR_FINITE_USEC, _JSON_BUILD_PAIR_STRING_NON_EMPTY, + _JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY, _JSON_BUILD_PAIR_STRV_NON_EMPTY, _JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY, _JSON_BUILD_PAIR_VARIANT_NON_NULL, @@ -218,6 +220,7 @@ enum { #define JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL(name, u, eq) _JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL, (const char*) { name }, (uint64_t) { u }, (uint64_t) { eq } #define JSON_BUILD_PAIR_FINITE_USEC(name, u) _JSON_BUILD_PAIR_FINITE_USEC, (const char*) { name }, (usec_t) { u } #define JSON_BUILD_PAIR_STRING_NON_EMPTY(name, s) _JSON_BUILD_PAIR_STRING_NON_EMPTY, (const char*) { name }, (const char*) { s } +#define JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY(name, s) _JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY, (const char*) { name }, (const char*) { s } #define JSON_BUILD_PAIR_STRV_NON_EMPTY(name, l) _JSON_BUILD_PAIR_STRV_NON_EMPTY, (const char*) { name }, (char**) { l } #define JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY(name, l) _JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY, (const char*) { name }, (char**) { l } #define JSON_BUILD_PAIR_VARIANT_NON_NULL(name, v) _JSON_BUILD_PAIR_VARIANT_NON_NULL, (const char*) { name }, (sd_json_variant*) { v } @@ -258,6 +261,7 @@ enum { #define JSON_BUILD_PAIR_TRISTATE(name, i) SD_JSON_BUILD_PAIR(name, JSON_BUILD_TRISTATE(i)) #define JSON_BUILD_PAIR_PIDREF(name, p) SD_JSON_BUILD_PAIR(name, JSON_BUILD_PIDREF(p)) #define JSON_BUILD_PAIR_DEVNUM(name, d) SD_JSON_BUILD_PAIR(name, JSON_BUILD_DEVNUM(d)) +#define JSON_BUILD_PAIR_ENUM(name, s) SD_JSON_BUILD_PAIR(name, JSON_BUILD_STRING_UNDERSCORIFY(s)) #define JSON_BUILD_PAIR_YES_NO(name, b) SD_JSON_BUILD_PAIR(name, SD_JSON_BUILD_STRING(yes_no(b))) #define JSON_BUILD_PAIR_CONDITION_UNSIGNED(condition, name, value) \ diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 647b56555a7bb..4c541275c42c5 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -1003,7 +1003,7 @@ _public_ uint64_t sd_json_variant_unsigned(sd_json_variant *v) { if (!json_variant_is_regular(v)) goto mismatch; if (v->is_reference) - return sd_json_variant_integer(v->reference); + return sd_json_variant_unsigned(v->reference); switch (v->type) { @@ -1940,7 +1940,7 @@ _public_ int sd_json_variant_filter(sd_json_variant **v, char **to_remove) { if (strv_isempty(to_remove)) return 0; - for (size_t i = 0; i < sd_json_variant_elements(*v); i += 2) { + for (size_t i = 0, m = sd_json_variant_elements(*v); i < m; i += 2) { sd_json_variant *p; p = sd_json_variant_by_index(*v, i); @@ -1949,7 +1949,9 @@ _public_ int sd_json_variant_filter(sd_json_variant **v, char **to_remove) { if (strv_contains(to_remove, sd_json_variant_string(p))) { if (!array) { - array = new(sd_json_variant*, sd_json_variant_elements(*v) - 2); + /* Silence static analyzers */ + assert(m >= 2); + array = new(sd_json_variant*, m - 2); if (!array) return -ENOMEM; @@ -2761,21 +2763,21 @@ static int json_parse_number(const char **p, JsonValue *ret) { x = 10.0 * x + (*c - '0'); c++; - } while (strchr("0123456789", *c) && *c != 0); + } while (strchr(DIGITS, *c) && *c != 0); } if (*c == '.') { is_real = true; c++; - if (!strchr("0123456789", *c) || *c == 0) + if (!strchr(DIGITS, *c) || *c == 0) return -EINVAL; do { y = 10.0 * y + (*c - '0'); shift = 10.0 * shift; c++; - } while (strchr("0123456789", *c) && *c != 0); + } while (strchr(DIGITS, *c) && *c != 0); } if (IN_SET(*c, 'e', 'E')) { @@ -2788,13 +2790,13 @@ static int json_parse_number(const char **p, JsonValue *ret) { } else if (*c == '+') c++; - if (!strchr("0123456789", *c) || *c == 0) + if (!strchr(DIGITS, *c) || *c == 0) return -EINVAL; do { exponent = 10.0 * exponent + (*c - '0'); c++; - } while (strchr("0123456789", *c) && *c != 0); + } while (strchr(DIGITS, *c) && *c != 0); } *p = c; @@ -2904,7 +2906,7 @@ int json_tokenize( *state = INT_TO_PTR(STATE_VALUE_POST); goto finish; - } else if (strchr("-0123456789", *c)) { + } else if (strchr("-" DIGITS, *c)) { r = json_parse_number(&c, ret_value); if (r < 0) @@ -3039,7 +3041,6 @@ static int json_parse_internal( int r; assert_return(input, -EINVAL); - assert_return(ret, -EINVAL); p = *input; @@ -3111,12 +3112,22 @@ static int json_parse_internal( break; case JSON_TOKEN_OBJECT_OPEN: - if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { r = -EINVAL; goto finish; } + if (n_stack == 1 && FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_ARRAY) && !FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_OBJECT)) { + r = -EINVAL; + goto finish; + } + + /* n_stack includes the top level entry, hence > instead of >= */ + if (n_stack > DEPTH_MAX) { + r = -ELNRNG; + goto finish; + } + if (!GREEDY_REALLOC(stack, n_stack+1)) { r = -ENOMEM; goto finish; @@ -3168,6 +3179,17 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && !FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_ARRAY) && FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_OBJECT)) { + r = -EINVAL; + goto finish; + } + + /* n_stack includes the top level entry, hence > instead of >= */ + if (n_stack > DEPTH_MAX) { + r = -ELNRNG; + goto finish; + } + if (!GREEDY_REALLOC(stack, n_stack+1)) { r = -ENOMEM; goto finish; @@ -3217,6 +3239,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_string(&add, string); if (r < 0) goto finish; @@ -3240,6 +3267,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_real(&add, value.real); if (r < 0) goto finish; @@ -3261,6 +3293,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_integer(&add, value.integer); if (r < 0) goto finish; @@ -3282,6 +3319,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_unsigned(&add, value.unsig); if (r < 0) goto finish; @@ -3303,6 +3345,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_boolean(&add, value.boolean); if (r < 0) goto finish; @@ -3324,6 +3371,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_null(&add); if (r < 0) goto finish; @@ -3365,7 +3417,8 @@ static int json_parse_internal( assert(n_stack == 1); assert(stack[0].n_elements == 1); - *ret = sd_json_variant_ref(stack[0].elements[0]); + if (ret) + *ret = sd_json_variant_ref(stack[0].elements[0]); *input = p; r = 0; @@ -3542,7 +3595,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { if (current->n_suppress == 0) { _cleanup_free_ char *c = NULL; - if (command == _JSON_BUILD_STRING_UNDERSCORIFY) { + if (command == _JSON_BUILD_STRING_UNDERSCORIFY && p) { c = strdup(p); if (!c) { r = -ENOMEM; @@ -4182,8 +4235,8 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { if (ratelimit_configured(rl)) { r = sd_json_buildo( &add, - SD_JSON_BUILD_PAIR("intervalUSec", SD_JSON_BUILD_UNSIGNED(rl->interval)), - SD_JSON_BUILD_PAIR("burst", SD_JSON_BUILD_UNSIGNED(rl->burst))); + SD_JSON_BUILD_PAIR_UNSIGNED("intervalUSec", rl->interval), + SD_JSON_BUILD_PAIR_UNSIGNED("burst", rl->burst)); if (r < 0) goto finish; } else @@ -4520,7 +4573,8 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { break; } - case _JSON_BUILD_PAIR_STRING_NON_EMPTY: { + case _JSON_BUILD_PAIR_STRING_NON_EMPTY: + case _JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY: { const char *n, *s; if (current->expect != EXPECT_OBJECT_KEY) { @@ -4536,7 +4590,16 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { if (r < 0) goto finish; - r = sd_json_variant_new_string(&add_more, s); + if (command == _JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY) { + _cleanup_free_ char *c = strdup(s); + if (!c) { + r = -ENOMEM; + goto finish; + } + + r = sd_json_variant_new_string(&add_more, json_underscorify(c)); + } else + r = sd_json_variant_new_string(&add_more, s); if (r < 0) goto finish; } @@ -4805,8 +4868,8 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { goto finish; r = sd_json_buildo(&add_more, - SD_JSON_BUILD_PAIR("realtime", SD_JSON_BUILD_UNSIGNED(ts->realtime)), - SD_JSON_BUILD_PAIR("monotonic", SD_JSON_BUILD_UNSIGNED(ts->monotonic))); + SD_JSON_BUILD_PAIR_UNSIGNED("realtime", ts->realtime), + SD_JSON_BUILD_PAIR_UNSIGNED("monotonic", ts->monotonic)); if (r < 0) goto finish; } @@ -4835,8 +4898,8 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { goto finish; r = sd_json_buildo(&add_more, - SD_JSON_BUILD_PAIR("intervalUSec", SD_JSON_BUILD_UNSIGNED(rl->interval)), - SD_JSON_BUILD_PAIR("burst", SD_JSON_BUILD_UNSIGNED(rl->burst))); + SD_JSON_BUILD_PAIR_UNSIGNED("intervalUSec", rl->interval), + SD_JSON_BUILD_PAIR_UNSIGNED("burst", rl->burst)); if (r < 0) goto finish; } @@ -5589,7 +5652,7 @@ _public_ int sd_json_dispatch_const_string(const char *name, sd_json_variant *va if (!sd_json_variant_is_string(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); - if ((flags & SD_JSON_STRICT) && !string_is_safe(sd_json_variant_string(variant))) + if ((flags & SD_JSON_STRICT) && !string_is_safe(sd_json_variant_string(variant), STRING_ALLOW_EMPTY|STRING_ALLOW_GLOBS)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name)); *s = sd_json_variant_string(variant); @@ -5612,7 +5675,7 @@ _public_ int sd_json_dispatch_strv(const char *name, sd_json_variant *variant, s /* Let's be flexible here: accept a single string in place of a single-item array */ if (sd_json_variant_is_string(variant)) { - if ((flags & SD_JSON_STRICT) && !string_is_safe(sd_json_variant_string(variant))) + if ((flags & SD_JSON_STRICT) && !string_is_safe(sd_json_variant_string(variant), STRING_ALLOW_EMPTY|STRING_ALLOW_GLOBS)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name)); l = strv_new(sd_json_variant_string(variant)); @@ -5630,7 +5693,7 @@ _public_ int sd_json_dispatch_strv(const char *name, sd_json_variant *variant, s if (!sd_json_variant_is_string(e)) return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string."); - if ((flags & SD_JSON_STRICT) && !string_is_safe(sd_json_variant_string(e))) + if ((flags & SD_JSON_STRICT) && !string_is_safe(sd_json_variant_string(e), STRING_ALLOW_EMPTY|STRING_ALLOW_GLOBS)) return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name)); r = strv_extend(&l, sd_json_variant_string(e)); diff --git a/src/libsystemd/sd-netlink/netlink-message-nfnl.c b/src/libsystemd/sd-netlink/netlink-message-nfnl.c index a485fd096fd61..51983e515e3a3 100644 --- a/src/libsystemd/sd-netlink/netlink-message-nfnl.c +++ b/src/libsystemd/sd-netlink/netlink-message-nfnl.c @@ -242,6 +242,8 @@ int sd_nfnl_nft_message_new_basechain( _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; int r; + assert(ret); + r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWCHAIN, NLM_F_CREATE); if (r < 0) return r; @@ -287,6 +289,8 @@ int sd_nfnl_nft_message_new_table( _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; int r; + assert(ret); + r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWTABLE, NLM_F_CREATE | NLM_F_EXCL); if (r < 0) return r; @@ -309,6 +313,8 @@ int sd_nfnl_nft_message_new_rule( _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; int r; + assert(ret); + r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWRULE, NLM_F_CREATE); if (r < 0) return r; @@ -337,6 +343,8 @@ int sd_nfnl_nft_message_new_set( _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; int r; + assert(ret); + r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWSET, NLM_F_CREATE); if (r < 0) return r; @@ -372,6 +380,8 @@ int sd_nfnl_nft_message_new_setelems( _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; int r; + assert(ret); + if (add) r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWSETELEM, NLM_F_CREATE); else diff --git a/src/libsystemd/sd-netlink/netlink-message-rtnl.c b/src/libsystemd/sd-netlink/netlink-message-rtnl.c index f1871ceb822e3..af1d6fd3f5a12 100644 --- a/src/libsystemd/sd-netlink/netlink-message-rtnl.c +++ b/src/libsystemd/sd-netlink/netlink-message-rtnl.c @@ -385,6 +385,8 @@ int sd_rtnl_message_new_addr_update( int family) { int r; + assert_return(ret, -EINVAL); + r = sd_rtnl_message_new_addr(rtnl, ret, RTM_NEWADDR, ifindex, family); if (r < 0) return r; diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c index f127de7705c22..e0154b7ef7755 100644 --- a/src/libsystemd/sd-netlink/test-netlink.c +++ b/src/libsystemd/sd-netlink/test-netlink.c @@ -608,6 +608,8 @@ static void remove_dummy_interfacep(int *ifindex) { _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + POINTER_MAY_BE_NULL(ifindex); + if (!ifindex || *ifindex <= 0) return; diff --git a/src/libsystemd/sd-path/path-lookup.c b/src/libsystemd/sd-path/path-lookup.c index 32c14fb14a7d5..b0f10e4b5af23 100644 --- a/src/libsystemd/sd-path/path-lookup.c +++ b/src/libsystemd/sd-path/path-lookup.c @@ -5,6 +5,7 @@ #include "alloc-util.h" #include "fs-util.h" #include "log.h" +#include "mkdir.h" #include "path-lookup.h" #include "path-util.h" #include "stat-util.h" @@ -101,6 +102,68 @@ int runtime_directory(RuntimeScope scope, const char *fallback_suffix, char **re return 1; } +int runtime_directory_make(RuntimeScope scope, const char *subdir, char **ret_dir, char **ret_dir_destroy) { + _cleanup_free_ char *dir = NULL, *destroy = NULL; + int r; + + assert(subdir); + assert(ret_dir); + assert(ret_dir_destroy); + + /* Use runtime_directory() (not _generic()) so that when we run in a systemd service + * with RuntimeDirectory= set, we pick up $RUNTIME_DIRECTORY and place our stuff into the + * directory the service manager prepared for us. When the env var is unset, we fall back + * to the provided subdirectory under /run (or the $XDG_RUNTIME_DIR equivalent in user scope) + * and take care of creation and destruction ourselves. */ + r = runtime_directory(scope, subdir, &dir); + if (r < 0) + return r; + + if (r > 0) { + /* $RUNTIME_DIRECTORY was not set, so we got the fallback path and need to create and + * clean up the directory ourselves. */ + destroy = strdup(dir); + if (!destroy) + return -ENOMEM; + + r = mkdir_p(dir, 0755); + if (r < 0) + return r; + } + + /* When $RUNTIME_DIRECTORY is set the service manager created the directory for us and + * will destroy it (or preserve it, per RuntimeDirectoryPreserve=) when the service stops. */ + + *ret_dir = TAKE_PTR(dir); + *ret_dir_destroy = TAKE_PTR(destroy); + + return 0; +} + +int state_directory_generic(RuntimeScope scope, const char *suffix, char **ret) { + assert(ret); + + /* This does not bother with $STATE_DIRECTORY, and hence can be applied to get other service's state + * dir */ + + switch (scope) { + + case RUNTIME_SCOPE_USER: + return xdg_user_state_dir(suffix, ret); + + case RUNTIME_SCOPE_SYSTEM: { + char *d = path_join("/var/lib", suffix); + if (!d) + return -ENOMEM; + *ret = d; + return 0; + } + + default: + return -EINVAL; + } +} + static const char* const user_data_unit_paths[] = { "/usr/local/lib/systemd/user", "/usr/local/share/systemd/user", diff --git a/src/libsystemd/sd-path/path-lookup.h b/src/libsystemd/sd-path/path-lookup.h index 67a4f5d69cf0f..80e37a571c59c 100644 --- a/src/libsystemd/sd-path/path-lookup.h +++ b/src/libsystemd/sd-path/path-lookup.h @@ -60,6 +60,8 @@ void lookup_paths_done(LookupPaths *p); int config_directory_generic(RuntimeScope scope, const char *suffix, char **ret); int runtime_directory_generic(RuntimeScope scope, const char *suffix, char **ret); int runtime_directory(RuntimeScope scope, const char *fallback_suffix, char **ret); +int runtime_directory_make(RuntimeScope scope, const char *subdir, char **ret_dir, char **ret_dir_destroy); +int state_directory_generic(RuntimeScope scope, const char *suffix, char **ret); /* We don't treat /etc/xdg/systemd/ in these functions as the xdg base dir spec suggests because we assume * that is a link to /etc/systemd/ anyway. */ @@ -74,6 +76,9 @@ static inline int xdg_user_config_dir(const char *suffix, char **ret) { static inline int xdg_user_data_dir(const char *suffix, char **ret) { return sd_path_lookup(SD_PATH_USER_SHARED, suffix, ret); } +static inline int xdg_user_state_dir(const char *suffix, char **ret) { + return sd_path_lookup(SD_PATH_USER_STATE_PRIVATE, suffix, ret); +} bool path_is_user_data_dir(const char *path); bool path_is_user_config_dir(const char *path); diff --git a/src/libsystemd/sd-path/sd-path.c b/src/libsystemd/sd-path/sd-path.c index e009a71bea0fd..eebcd20b6f9c2 100644 --- a/src/libsystemd/sd-path/sd-path.c +++ b/src/libsystemd/sd-path/sd-path.c @@ -282,6 +282,9 @@ static int get_path(uint64_t type, char **buffer, const char **ret) { case SD_PATH_USER_DESKTOP: return from_xdg_user_dir("XDG_DESKTOP_DIR", buffer, ret); + case SD_PATH_USER_PROJECTS: + return from_xdg_user_dir("XDG_PROJECTS_DIR", buffer, ret); + case SD_PATH_SYSTEMD_UTIL: *ret = PREFIX_NOSLASH "/lib/systemd"; return 0; diff --git a/src/libsystemd/sd-varlink/sd-varlink-idl.c b/src/libsystemd/sd-varlink/sd-varlink-idl.c index a70cbe529ce29..be66fb34afc39 100644 --- a/src/libsystemd/sd-varlink/sd-varlink-idl.c +++ b/src/libsystemd/sd-varlink/sd-varlink-idl.c @@ -387,7 +387,10 @@ static int varlink_idl_format_symbol( /* Sooner or later we want to export this in a proper IDL language construct, see * https://github.com/varlink/varlink.github.io/issues/26 – but for now export this as a - * comment. */ + * comment. + * + * Until this is resolved upsteam, consider this comment part of the API (i.e. don't change + * only extend). It is used by tools like varlink-http-bridge. */ if ((symbol->symbol_flags & (SD_VARLINK_REQUIRES_MORE|SD_VARLINK_SUPPORTS_MORE)) != 0) { fputs(colors[COLOR_COMMENT], f); if (FLAGS_SET(symbol->symbol_flags, SD_VARLINK_REQUIRES_MORE)) @@ -398,6 +401,16 @@ static int varlink_idl_format_symbol( fputs("\n", f); } + if ((symbol->symbol_flags & (SD_VARLINK_REQUIRES_UPGRADE|SD_VARLINK_SUPPORTS_UPGRADE)) != 0) { + fputs(colors[COLOR_COMMENT], f); + if (FLAGS_SET(symbol->symbol_flags, SD_VARLINK_REQUIRES_UPGRADE)) + fputs("# [Requires 'upgrade' flag]", f); + else + fputs("# [Supports 'upgrade' flag]", f); + fputs(colors[COLOR_RESET], f); + fputs("\n", f); + } + fputs(colors[COLOR_SYMBOL_TYPE], f); fputs("method ", f); fputs(colors[COLOR_IDENTIFIER], f); @@ -827,6 +840,7 @@ static int varlink_idl_subparse_field_type( assert(p); assert(*p); assert(line); + assert(column); assert(field); r = varlink_idl_subparse_whitespace(p, line, column); @@ -1158,6 +1172,9 @@ _public_ int sd_varlink_idl_parse( _cleanup_(sd_varlink_interface_freep) sd_varlink_interface *interface = NULL; _cleanup_(varlink_symbol_freep) sd_varlink_symbol *symbol = NULL; + + POINTER_MAY_BE_NULL(ret); + enum { STATE_PRE_INTERFACE, STATE_INTERFACE, @@ -1393,7 +1410,8 @@ _public_ int sd_varlink_idl_parse( if (r < 0) return r; - *ret = TAKE_PTR(interface); + if (ret) + *ret = TAKE_PTR(interface); return 0; } @@ -1705,7 +1723,12 @@ static int varlink_idl_validate_symbol(const sd_varlink_symbol *symbol, sd_json_ static int varlink_idl_validate_field_element_type(const sd_varlink_field *field, sd_json_variant *v) { assert(field); assert(v); - assert(!sd_json_variant_is_null(v)); + + if (sd_json_variant_is_null(v)) + return varlink_idl_log( + SYNTHETIC_ERRNO(EMEDIUMTYPE), + "Field '%s' element is null, refusing.", + strna(field->name)); switch (field->field_type) { @@ -1767,7 +1790,7 @@ static int varlink_idl_validate_field_element_type(const sd_varlink_field *field case SD_VARLINK_ANY: /* The any type accepts any non-null JSON value, no validation needed. (Note that null is - * already handled by the caller.) */ + * already gracefully rejected at the start of this function.) */ break; case _SD_VARLINK_FIELD_COMMENT: @@ -1937,6 +1960,10 @@ int varlink_idl_validate_method_call(const sd_varlink_symbol *method, sd_json_va if (FLAGS_SET(method->symbol_flags, SD_VARLINK_REQUIRES_MORE) && !FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) return -EBADE; + /* Same for upgrade */ + if (FLAGS_SET(method->symbol_flags, SD_VARLINK_REQUIRES_UPGRADE) && !FLAGS_SET(flags, SD_VARLINK_METHOD_UPGRADE)) + return -EBADE; + return varlink_idl_validate_symbol(method, v, SD_VARLINK_INPUT, reterr_bad_field); } @@ -1947,7 +1974,7 @@ int varlink_idl_validate_method_reply(const sd_varlink_symbol *method, sd_json_v return -EBADMSG; /* If method replies have the "continues" flag set, but the method is not allowed to generate that, return a recognizable error */ - if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES) && (method->symbol_type & (SD_VARLINK_SUPPORTS_MORE|SD_VARLINK_REQUIRES_MORE)) == 0) + if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES) && (method->symbol_flags & (SD_VARLINK_SUPPORTS_MORE|SD_VARLINK_REQUIRES_MORE)) == 0) return -EBADE; return varlink_idl_validate_symbol(method, v, SD_VARLINK_OUTPUT, reterr_bad_field); diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 534e141554404..2a5f677ef37d0 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -18,8 +18,6 @@ #include "format-util.h" #include "glyph-util.h" #include "hashmap.h" -#include "io-util.h" -#include "iovec-util.h" #include "json-util.h" #include "list.h" #include "log.h" @@ -41,13 +39,10 @@ #include "varlink-org.varlink.service.h" #define VARLINK_DEFAULT_CONNECTIONS_MAX 4096U -#define VARLINK_DEFAULT_CONNECTIONS_PER_UID_MAX 1024U +#define VARLINK_DEFAULT_CONNECTIONS_PER_UID_MAX 128U #define VARLINK_DEFAULT_TIMEOUT_USEC (45U*USEC_PER_SEC) -#define VARLINK_BUFFER_MAX (16U*1024U*1024U) -#define VARLINK_READ_SIZE (64U*1024U) #define VARLINK_COLLECT_MAX 1024U -#define VARLINK_QUEUE_MAX (64U*1024U) static const char* const varlink_state_table[_VARLINK_STATE_MAX] = { [VARLINK_IDLE_CLIENT] = "idle-client", @@ -75,39 +70,8 @@ static const char* const varlink_state_table[_VARLINK_STATE_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(varlink_state, VarlinkState); -static int varlink_format_queue(sd_varlink *v); static void varlink_server_test_exit_on_idle(sd_varlink_server *s); -static VarlinkJsonQueueItem* varlink_json_queue_item_free(VarlinkJsonQueueItem *q) { - if (!q) - return NULL; - - sd_json_variant_unref(q->data); - close_many(q->fds, q->n_fds); - - return mfree(q); -} - -static VarlinkJsonQueueItem* varlink_json_queue_item_new(sd_json_variant *m, const int fds[], size_t n_fds) { - VarlinkJsonQueueItem *q; - - assert(m); - assert(fds || n_fds == 0); - - q = malloc(offsetof(VarlinkJsonQueueItem, fds) + sizeof(int) * n_fds); - if (!q) - return NULL; - - *q = (VarlinkJsonQueueItem) { - .data = sd_json_variant_ref(m), - .n_fds = n_fds, - }; - - memcpy_safe(q->fds, fds, n_fds * sizeof(int)); - - return TAKE_PTR(q); -} - static void varlink_set_state(sd_varlink *v, VarlinkState state) { assert(v); assert(state >= 0 && state < _VARLINK_STATE_MAX); @@ -124,8 +88,40 @@ static void varlink_set_state(sd_varlink *v, VarlinkState state) { v->state = state; } +/* Map the varlink state machine onto the generic transport-level "phase". The transport + * uses this to decide whether to ask for POLLIN, whether the connection is salvageable + * after a read/write disconnect, and whether the idle timeout deadline is in force. */ +static JsonStreamPhase varlink_phase(void *userdata) { + sd_varlink *v = ASSERT_PTR(userdata); + + /* Client side reading a reply with the per-call deadline in force. */ + if (IN_SET(v->state, + VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, + VARLINK_CALLING, VARLINK_COLLECTING) && + !v->current) + return JSON_STREAM_PHASE_AWAITING_REPLY; + + /* Server side reading the next request — no deadline applies. */ + if (v->state == VARLINK_IDLE_SERVER && !v->current) + return JSON_STREAM_PHASE_READING; + + if (v->state == VARLINK_IDLE_CLIENT) + return JSON_STREAM_PHASE_IDLE_CLIENT; + + if (IN_SET(v->state, VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE)) + return JSON_STREAM_PHASE_PENDING_OUTPUT; + + return JSON_STREAM_PHASE_OTHER; +} + +static int varlink_dispatch(void *userdata) { + sd_varlink *v = ASSERT_PTR(userdata); + return sd_varlink_process(v); +} + static int varlink_new(sd_varlink **ret) { - sd_varlink *v; + _cleanup_(sd_varlink_unrefp) sd_varlink *v = NULL; + int r; assert(ret); @@ -135,32 +131,28 @@ static int varlink_new(sd_varlink **ret) { *v = (sd_varlink) { .n_ref = 1, - .input_fd = -EBADF, - .output_fd = -EBADF, - .state = _VARLINK_STATE_INVALID, - - .ucred = UCRED_INVALID, - - .peer_pidfd = -EBADF, - - .timestamp = USEC_INFINITY, - .timeout = VARLINK_DEFAULT_TIMEOUT_USEC, - - .allow_fd_passing_input = -1, - - .af = -1, - .exec_pidref = PIDREF_NULL, }; - *ret = v; + r = json_stream_init( + &v->stream, + &(JsonStreamParams) { + .phase = varlink_phase, + .dispatch = varlink_dispatch, + .userdata = v, + }); + if (r < 0) + return r; + + json_stream_set_timeout(&v->stream, VARLINK_DEFAULT_TIMEOUT_USEC); + + *ret = TAKE_PTR(v); return 0; } _public_ int sd_varlink_connect_address(sd_varlink **ret, const char *address) { _cleanup_(sd_varlink_unrefp) sd_varlink *v = NULL; - union sockaddr_union sockaddr; int r; assert_return(ret, -EINVAL); @@ -170,39 +162,9 @@ _public_ int sd_varlink_connect_address(sd_varlink **ret, const char *address) { if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - v->input_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (v->input_fd < 0) - return log_debug_errno(errno, "Failed to create AF_UNIX socket: %m"); - - v->output_fd = v->input_fd = fd_move_above_stdio(v->input_fd); - v->af = AF_UNIX; - - r = sockaddr_un_set_path(&sockaddr.un, address); - if (r < 0) { - if (r != -ENAMETOOLONG) - return log_debug_errno(r, "Failed to set socket address '%s': %m", address); - - /* This is a file system path, and too long to fit into sockaddr_un. Let's connect via O_PATH - * to this socket. */ - - r = connect_unix_path(v->input_fd, AT_FDCWD, address); - } else - r = RET_NERRNO(connect(v->input_fd, &sockaddr.sa, r)); - - if (r < 0) { - if (!IN_SET(r, -EAGAIN, -EINPROGRESS)) - return log_debug_errno(r, "Failed to connect to %s: %m", address); - - v->connecting = true; /* We are asynchronously connecting, i.e. the connect() is being - * processed in the background. As long as that's the case the socket - * is in a special state: it's there, we can poll it for EPOLLOUT, but - * if we attempt to write() to it before we see EPOLLOUT we'll get - * ENOTCONN (and not EAGAIN, like we would for a normal connected - * socket that isn't writable at the moment). Since ENOTCONN on write() - * hence can mean two different things (i.e. connection not complete - * yet vs. already disconnected again), we store as a boolean whether - * we are still in connect(). */ - } + r = json_stream_connect_address(&v->stream, address); + if (r < 0) + return r; varlink_set_state(v, VARLINK_IDLE_CLIENT); @@ -291,8 +253,11 @@ _public_ int sd_varlink_connect_exec(sd_varlink **ret, const char *_command, cha if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - v->output_fd = v->input_fd = TAKE_FD(pair[0]); - v->af = AF_UNIX; + int conn_fd = TAKE_FD(pair[0]); + r = json_stream_attach_fds(&v->stream, conn_fd, conn_fd); + if (r < 0) + return r; + v->exec_pidref = TAKE_PIDREF(pidref); varlink_set_state(v, VARLINK_IDLE_CLIENT); @@ -375,8 +340,11 @@ static int varlink_connect_ssh_unix(sd_varlink **ret, const char *where) { if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - v->output_fd = v->input_fd = TAKE_FD(pair[0]); - v->af = AF_UNIX; + int conn_fd = TAKE_FD(pair[0]); + r = json_stream_attach_fds(&v->stream, conn_fd, conn_fd); + if (r < 0) + return r; + v->exec_pidref = TAKE_PIDREF(pidref); varlink_set_state(v, VARLINK_IDLE_CLIENT); @@ -467,9 +435,10 @@ static int varlink_connect_ssh_exec(sd_varlink **ret, const char *where) { if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - v->input_fd = TAKE_FD(output_pipe[0]); - v->output_fd = TAKE_FD(input_pipe[1]); - v->af = AF_UNSPEC; + r = json_stream_attach_fds(&v->stream, TAKE_FD(output_pipe[0]), TAKE_FD(input_pipe[1])); + if (r < 0) + return r; + v->exec_pidref = TAKE_PIDREF(pidref); varlink_set_state(v, VARLINK_IDLE_CLIENT); @@ -575,35 +544,23 @@ _public_ int sd_varlink_connect_url(sd_varlink **ret, const char *url) { } _public_ int sd_varlink_connect_fd_pair(sd_varlink **ret, int input_fd, int output_fd, const struct ucred *override_ucred) { - sd_varlink *v; + _cleanup_(sd_varlink_unrefp) sd_varlink *v = NULL; int r; assert_return(ret, -EINVAL); assert_return(input_fd >= 0, -EBADF); assert_return(output_fd >= 0, -EBADF); - r = fd_nonblock(input_fd, true); - if (r < 0) - return log_debug_errno(r, "Failed to make input fd %d nonblocking: %m", input_fd); - - if (input_fd != output_fd) { - r = fd_nonblock(output_fd, true); - if (r < 0) - return log_debug_errno(r, "Failed to make output fd %d nonblocking: %m", output_fd); - } - r = varlink_new(&v); if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - v->input_fd = input_fd; - v->output_fd = output_fd; - v->af = -1; + r = json_stream_connect_fd_pair(&v->stream, input_fd, output_fd); + if (r < 0) + return r; - if (override_ucred) { - v->ucred = *override_ucred; - v->ucred_acquired = true; - } + if (override_ucred) + json_stream_set_peer_ucred(&v->stream, override_ucred); varlink_set_state(v, VARLINK_IDLE_CLIENT); @@ -614,7 +571,7 @@ _public_ int sd_varlink_connect_fd_pair(sd_varlink **ret, int input_fd, int outp * varlink_connect_address() above, as there we do handle asynchronous connections ourselves and * avoid doing write() on it before we saw EPOLLOUT for the first time. */ - *ret = v; + *ret = TAKE_PTR(v); return 0; } @@ -622,16 +579,6 @@ _public_ int sd_varlink_connect_fd(sd_varlink **ret, int fd) { return sd_varlink_connect_fd_pair(ret, fd, fd, /* override_ucred= */ NULL); } -static void varlink_detach_event_sources(sd_varlink *v) { - assert(v); - - v->input_event_source = sd_event_source_disable_unref(v->input_event_source); - v->output_event_source = sd_event_source_disable_unref(v->output_event_source); - v->time_event_source = sd_event_source_disable_unref(v->time_event_source); - v->quit_event_source = sd_event_source_disable_unref(v->quit_event_source); - v->defer_event_source = sd_event_source_disable_unref(v->defer_event_source); -} - static void varlink_clear_current(sd_varlink *v) { assert(v); @@ -641,11 +588,12 @@ static void varlink_clear_current(sd_varlink *v) { v->current_method = NULL; v->current_reply_flags = 0; - close_many(v->input_fds, v->n_input_fds); - v->input_fds = mfree(v->input_fds); - v->n_input_fds = 0; + json_stream_close_input_fds(&v->stream); - v->previous = varlink_json_queue_item_free(v->previous); + v->previous = sd_json_variant_unref(v->previous); + close_many(v->previous_fds, v->n_previous_fds); + v->previous_fds = mfree(v->previous_fds); + v->n_previous_fds = 0; if (v->sentinel != POINTER_MAX) v->sentinel = mfree(v->sentinel); else @@ -655,39 +603,20 @@ static void varlink_clear_current(sd_varlink *v) { static void varlink_clear(sd_varlink *v) { assert(v); - varlink_detach_event_sources(v); - - if (v->input_fd != v->output_fd) { - v->input_fd = safe_close(v->input_fd); - v->output_fd = safe_close(v->output_fd); - } else - v->output_fd = v->input_fd = safe_close(v->input_fd); + /* Detach event sources first so the kernel no longer has epoll watches on the + * stream's fds, then free the stream — json_stream_done() closes the input/output + * fds, the cached peer_pidfd, the received input fds, and the queued output fds. */ + sd_varlink_detach_event(v); varlink_clear_current(v); - v->input_buffer = v->input_sensitive ? erase_and_free(v->input_buffer) : mfree(v->input_buffer); - v->output_buffer = v->output_buffer_sensitive ? erase_and_free(v->output_buffer) : mfree(v->output_buffer); - - v->input_control_buffer = mfree(v->input_control_buffer); - v->input_control_buffer_size = 0; - - close_many(v->output_fds, v->n_output_fds); - v->output_fds = mfree(v->output_fds); - v->n_output_fds = 0; + json_stream_done(&v->stream); close_many(v->pushed_fds, v->n_pushed_fds); v->pushed_fds = mfree(v->pushed_fds); v->n_pushed_fds = 0; - LIST_CLEAR(queue, v->output_queue, varlink_json_queue_item_free); - v->output_queue_tail = NULL; - v->n_output_queue = 0; - - v->event = sd_event_unref(v->event); - pidref_done_sigterm_wait(&v->exec_pidref); - - v->peer_pidfd = safe_close(v->peer_pidfd); } static sd_varlink* varlink_destroy(sd_varlink *v) { @@ -700,7 +629,6 @@ static sd_varlink* varlink_destroy(sd_varlink *v) { varlink_clear(v); - free(v->description); return mfree(v); } @@ -709,358 +637,81 @@ DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_varlink, sd_varlink, varlink_destroy); static int varlink_test_disconnect(sd_varlink *v) { assert(v); - /* Tests whether we the connection has been terminated. We are careful to not stop processing it - * prematurely, since we want to handle half-open connections as well as possible and want to flush - * out and read data before we close down if we can. */ - /* Already disconnected? */ if (!VARLINK_STATE_IS_ALIVE(v->state)) return 0; - /* Wait until connection setup is complete, i.e. until asynchronous connect() completes */ - if (v->connecting) - return 0; - - /* Still something to write and we can write? Stay around */ - if (v->output_buffer_size > 0 && !v->write_disconnected) + if (!json_stream_should_disconnect(&v->stream)) return 0; - /* Both sides gone already? Then there's no need to stick around */ - if (v->read_disconnected && v->write_disconnected) - goto disconnect; - - /* If we are waiting for incoming data but the read side is shut down, disconnect. */ - if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER) && v->read_disconnected) - goto disconnect; - - /* Similar, if are a client that hasn't written anything yet but the write side is dead, also - * disconnect. We also explicitly check for POLLHUP here since we likely won't notice the write side - * being down if we never wrote anything. */ - if (v->state == VARLINK_IDLE_CLIENT && (v->write_disconnected || v->got_pollhup)) - goto disconnect; - - /* We are on the server side and still want to send out more replies, but we saw POLLHUP already, and - * either got no buffered bytes to write anymore or already saw a write error. In that case we should - * shut down the varlink link. */ - if (IN_SET(v->state, VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE) && (v->write_disconnected || v->output_buffer_size == 0) && v->got_pollhup) - goto disconnect; - - return 0; - -disconnect: varlink_set_state(v, VARLINK_PENDING_DISCONNECT); return 1; } -static int varlink_write(sd_varlink *v) { - ssize_t n; +static int varlink_enqueue(sd_varlink *v, sd_json_variant *m) { int r; assert(v); + assert(m); - if (!VARLINK_STATE_IS_ALIVE(v->state)) - return 0; - if (v->connecting) /* Writing while we are still wait for a non-blocking connect() to complete will - * result in ENOTCONN, hence exit early here */ - return 0; - if (v->write_disconnected) - return 0; - - /* If needed let's convert some output queue json variants into text form */ - r = varlink_format_queue(v); - if (r < 0) - return r; - - if (v->output_buffer_size == 0) - return 0; - - assert(v->output_fd >= 0); - - if (v->n_output_fds > 0) { /* If we shall send fds along, we must use sendmsg() */ - struct iovec iov = { - .iov_base = v->output_buffer + v->output_buffer_index, - .iov_len = v->output_buffer_size, - }; - struct msghdr mh = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_controllen = CMSG_SPACE(sizeof(int) * v->n_output_fds), - }; - - mh.msg_control = alloca0(mh.msg_controllen); - - struct cmsghdr *control = CMSG_FIRSTHDR(&mh); - control->cmsg_len = CMSG_LEN(sizeof(int) * v->n_output_fds); - control->cmsg_level = SOL_SOCKET; - control->cmsg_type = SCM_RIGHTS; - memcpy(CMSG_DATA(control), v->output_fds, sizeof(int) * v->n_output_fds); - - n = sendmsg(v->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); - } else { - /* We generally prefer recv()/send() (mostly because of MSG_NOSIGNAL) but also want to be compatible - * with non-socket IO, hence fall back automatically. - * - * Use a local variable to help gcc figure out that we set 'n' in all cases. */ - bool prefer_write = v->prefer_write; - if (!prefer_write) { - n = send(v->output_fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size, MSG_DONTWAIT|MSG_NOSIGNAL); - if (n < 0 && errno == ENOTSOCK) - prefer_write = v->prefer_write = true; - } - if (prefer_write) - n = write(v->output_fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size); - } - if (n < 0) { - if (errno == EAGAIN) - return 0; - - if (ERRNO_IS_DISCONNECT(errno)) { - /* If we get informed about a disconnect on write, then let's remember that, but not - * act on it just yet. Let's wait for read() to report the issue first. */ - v->write_disconnected = true; - return 1; - } - - return -errno; - } - - if (v->output_buffer_sensitive) - explicit_bzero_safe(v->output_buffer + v->output_buffer_index, n); + r = json_stream_enqueue_full(&v->stream, m, v->pushed_fds, v->n_pushed_fds); + if (r >= 0) + v->n_pushed_fds = 0; /* fds belong to the queue entry now */ + /* We don't free v->pushed_fds so it can be reused for the next message. */ - v->output_buffer_size -= n; + return r; +} - if (v->output_buffer_size == 0) { - v->output_buffer_index = 0; - v->output_buffer_sensitive = false; /* We can reset the sensitive flag once the buffer is empty */ - } else - v->output_buffer_index += n; +static int varlink_write(sd_varlink *v) { + assert(v); - close_many(v->output_fds, v->n_output_fds); - v->n_output_fds = 0; + if (!VARLINK_STATE_IS_ALIVE(v->state)) + return 0; - v->timestamp = now(CLOCK_MONOTONIC); - return 1; + return json_stream_write(&v->stream); } -#define VARLINK_FDS_MAX (16U*1024U) - static int varlink_read(sd_varlink *v) { - struct iovec iov; - struct msghdr mh; - size_t rs; - ssize_t n; - void *p; - assert(v); if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER)) return 0; - if (v->connecting) /* read() on a socket while we are in connect() will fail with EINVAL, hence exit early here */ - return 0; if (v->current) return 0; - if (v->input_buffer_unscanned > 0) - return 0; - if (v->read_disconnected) - return 0; - - if (v->input_buffer_size >= VARLINK_BUFFER_MAX) - return -ENOBUFS; - - assert(v->input_fd >= 0); - - if (MALLOC_SIZEOF_SAFE(v->input_buffer) <= v->input_buffer_index + v->input_buffer_size) { - size_t add; - - add = MIN(VARLINK_BUFFER_MAX - v->input_buffer_size, VARLINK_READ_SIZE); - - if (v->input_buffer_index == 0) { - - if (!GREEDY_REALLOC(v->input_buffer, v->input_buffer_size + add)) - return -ENOMEM; - - } else { - char *b; - - b = new(char, v->input_buffer_size + add); - if (!b) - return -ENOMEM; - - memcpy(b, v->input_buffer + v->input_buffer_index, v->input_buffer_size); - - free_and_replace(v->input_buffer, b); - v->input_buffer_index = 0; - } - } - - p = v->input_buffer + v->input_buffer_index + v->input_buffer_size; - rs = MALLOC_SIZEOF_SAFE(v->input_buffer) - (v->input_buffer_index + v->input_buffer_size); - - if (v->allow_fd_passing_input > 0) { - iov = IOVEC_MAKE(p, rs); - - /* Allocate the fd buffer on the heap, since we need a lot of space potentially */ - if (!v->input_control_buffer) { - v->input_control_buffer_size = CMSG_SPACE(sizeof(int) * VARLINK_FDS_MAX); - v->input_control_buffer = malloc(v->input_control_buffer_size); - if (!v->input_control_buffer) - return -ENOMEM; - } - - mh = (struct msghdr) { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = v->input_control_buffer, - .msg_controllen = v->input_control_buffer_size, - }; - - n = recvmsg_safe(v->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); - } else { - bool prefer_read = v->prefer_read; - if (!prefer_read) { - n = recv(v->input_fd, p, rs, MSG_DONTWAIT); - if (n < 0) - n = -errno; - if (n == -ENOTSOCK) - prefer_read = v->prefer_read = true; - } - if (prefer_read) { - n = read(v->input_fd, p, rs); - if (n < 0) - n = -errno; - } - } - if (ERRNO_IS_NEG_TRANSIENT(n)) - return 0; - if (ERRNO_IS_NEG_DISCONNECT(n)) { - v->read_disconnected = true; - return 1; - } - if (n < 0) - return n; - if (n == 0) { /* EOF */ - if (v->allow_fd_passing_input > 0) - cmsg_close_all(&mh); - - v->read_disconnected = true; - return 1; - } - - if (v->allow_fd_passing_input > 0) { - struct cmsghdr *cmsg; - - cmsg = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, (socklen_t) -1); - if (cmsg) { - size_t add; - - /* We only allow file descriptors to be passed along with the first byte of a - * message. If they are passed with any other byte this is a protocol violation. */ - if (v->input_buffer_size != 0) { - cmsg_close_all(&mh); - return -EPROTO; - } - - add = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - if (add > INT_MAX - v->n_input_fds) { - cmsg_close_all(&mh); - return -EBADF; - } - - if (!GREEDY_REALLOC(v->input_fds, v->n_input_fds + add)) { - cmsg_close_all(&mh); - return -ENOMEM; - } - - memcpy_safe(v->input_fds + v->n_input_fds, CMSG_TYPED_DATA(cmsg, int), add * sizeof(int)); - v->n_input_fds += add; - } - } - - v->input_buffer_size += n; - v->input_buffer_unscanned += n; - - return 1; + return json_stream_read(&v->stream); } static int varlink_parse_message(sd_varlink *v) { - const char *e; - char *begin; - size_t sz; int r; assert(v); if (v->current) return 0; - if (v->input_buffer_unscanned <= 0) - return 0; - assert(v->input_buffer_unscanned <= v->input_buffer_size); - assert(v->input_buffer_index + v->input_buffer_size <= MALLOC_SIZEOF_SAFE(v->input_buffer)); - - begin = v->input_buffer + v->input_buffer_index; - - e = memchr(begin + v->input_buffer_size - v->input_buffer_unscanned, 0, v->input_buffer_unscanned); - if (!e) { - v->input_buffer_unscanned = 0; - return 0; - } - - sz = e - begin + 1; - - r = sd_json_parse(begin, 0, &v->current, NULL, NULL); - if (v->input_sensitive) - explicit_bzero_safe(begin, sz); - if (r < 0) { - /* If we encounter a parse failure flush all data. We cannot possibly recover from this, - * hence drop all buffered data now. */ - v->input_buffer_index = v->input_buffer_size = v->input_buffer_unscanned = 0; - return varlink_log_errno(v, r, "Failed to parse JSON: %m"); - } + r = json_stream_parse(&v->stream, &v->current); + if (r <= 0) + return r; - if (v->input_sensitive) { + if (json_stream_flags_set(&v->stream, JSON_STREAM_INPUT_SENSITIVE)) { /* Mark the parameters subfield as sensitive right-away, if that's requested */ sd_json_variant *parameters = sd_json_variant_by_key(v->current, "parameters"); if (parameters) sd_json_variant_sensitive(parameters); } - if (DEBUG_LOGGING) { - _cleanup_(erase_and_freep) char *censored_text = NULL; - - /* Suppress sensitive fields in the debug output */ - r = sd_json_variant_format(v->current, /* flags= */ SD_JSON_FORMAT_CENSOR_SENSITIVE, &censored_text); - if (r < 0) - return r; - - varlink_log(v, "Received message: %s", censored_text); - } - - v->input_buffer_size -= sz; - - if (v->input_buffer_size == 0) - v->input_buffer_index = 0; - else - v->input_buffer_index += sz; - - v->input_buffer_unscanned = v->input_buffer_size; return 1; } static int varlink_test_timeout(sd_varlink *v) { assert(v); - if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING)) - return 0; - if (v->timeout == USEC_INFINITY) - return 0; - - if (now(CLOCK_MONOTONIC) < usec_add(v->timestamp, v->timeout)) + usec_t deadline = json_stream_get_timeout(&v->stream); + if (deadline == USEC_INFINITY || now(CLOCK_MONOTONIC) < deadline) return 0; varlink_set_state(v, VARLINK_PENDING_TIMEOUT); - return 1; } @@ -1135,9 +786,8 @@ static int varlink_sanitize_incoming_parameters(sd_json_variant **v) { } static int varlink_dispatch_reply(sd_varlink *v) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *parameters = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *parameters = NULL, *error = NULL; sd_varlink_reply_flags_t flags = 0; - const char *error = NULL; sd_json_variant *e; const char *k; int r; @@ -1162,7 +812,7 @@ static int varlink_dispatch_reply(sd_varlink *v) { if (!sd_json_variant_is_string(e)) goto invalid; - error = sd_json_variant_string(e); + error = sd_json_variant_ref(e); flags |= SD_VARLINK_REPLY_ERROR; } else if (streq(k, "parameters")) { @@ -1204,7 +854,7 @@ static int varlink_dispatch_reply(sd_varlink *v) { varlink_set_state(v, VARLINK_PROCESSING_REPLY); if (v->reply_callback) { - r = v->reply_callback(v, parameters, error, flags, v->userdata); + r = v->reply_callback(v, parameters, sd_json_variant_string(error), flags, v->userdata); if (r < 0) varlink_log_errno(v, r, "Reply callback returned error, ignoring: %m"); } @@ -1309,152 +959,6 @@ static int generic_method_get_interface_description( SD_JSON_BUILD_PAIR_STRING("description", text)); } -static int varlink_format_json(sd_varlink *v, sd_json_variant *m) { - _cleanup_(erase_and_freep) char *text = NULL; - int sz, r; - - assert(v); - assert(m); - - sz = sd_json_variant_format(m, /* flags= */ 0, &text); - if (sz < 0) - return sz; - assert(text[sz] == '\0'); - - if (v->output_buffer_size + sz + 1 > VARLINK_BUFFER_MAX) - return -ENOBUFS; - - if (DEBUG_LOGGING) { - _cleanup_(erase_and_freep) char *censored_text = NULL; - - /* Suppress sensitive fields in the debug output */ - r = sd_json_variant_format(m, SD_JSON_FORMAT_CENSOR_SENSITIVE, &censored_text); - if (r < 0) - return r; - - varlink_log(v, "Sending message: %s", censored_text); - } - - if (v->output_buffer_size == 0) { - - free_and_replace(v->output_buffer, text); - - v->output_buffer_size = sz + 1; - v->output_buffer_index = 0; - - } else if (v->output_buffer_index == 0) { - - if (!GREEDY_REALLOC(v->output_buffer, v->output_buffer_size + sz + 1)) - return -ENOMEM; - - memcpy(v->output_buffer + v->output_buffer_size, text, sz + 1); - v->output_buffer_size += sz + 1; - } else { - char *n; - const size_t new_size = v->output_buffer_size + sz + 1; - - n = new(char, new_size); - if (!n) - return -ENOMEM; - - memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, sz + 1); - - free_and_replace(v->output_buffer, n); - v->output_buffer_size = new_size; - v->output_buffer_index = 0; - } - - if (sd_json_variant_is_sensitive_recursive(m)) - v->output_buffer_sensitive = true; /* Propagate sensitive flag */ - else - text = mfree(text); /* No point in the erase_and_free() destructor declared above */ - - return 0; -} - -static int varlink_format_queue(sd_varlink *v) { - int r; - - assert(v); - - /* Takes entries out of the output queue and formats them into the output buffer. But only if this - * would not corrupt our fd message boundaries */ - - while (v->output_queue) { - assert(v->n_output_queue > 0); - - if (v->n_output_fds > 0) /* unwritten fds? if we'd add more we'd corrupt the fd message boundaries, hence wait */ - return 0; - - VarlinkJsonQueueItem *q = v->output_queue; - _cleanup_free_ int *array = NULL; - - if (q->n_fds > 0) { - array = newdup(int, q->fds, q->n_fds); - if (!array) - return -ENOMEM; - } - - r = varlink_format_json(v, q->data); - if (r < 0) - return r; - - /* Take possession of the queue element's fds */ - free_and_replace(v->output_fds, array); - v->n_output_fds = q->n_fds; - q->n_fds = 0; - - LIST_REMOVE(queue, v->output_queue, q); - if (!v->output_queue) - v->output_queue_tail = NULL; - v->n_output_queue--; - - varlink_json_queue_item_free(q); - } - - return 0; -} - -static int varlink_enqueue_item(sd_varlink *v, VarlinkJsonQueueItem *q) { - assert(v); - assert(q); - - if (v->n_output_queue >= VARLINK_QUEUE_MAX) - return -ENOBUFS; - - LIST_INSERT_AFTER(queue, v->output_queue, v->output_queue_tail, q); - v->output_queue_tail = q; - v->n_output_queue++; - return 0; -} - -static int varlink_enqueue_json(sd_varlink *v, sd_json_variant *m) { - VarlinkJsonQueueItem *q; - - assert(v); - assert(m); - - /* If there are no file descriptors to be queued and no queue entries yet we can shortcut things and - * append this entry directly to the output buffer */ - if (v->n_pushed_fds == 0 && !v->output_queue) - return varlink_format_json(v, m); - - if (v->n_output_queue >= VARLINK_QUEUE_MAX) - return -ENOBUFS; - - /* Otherwise add a queue entry for this */ - q = varlink_json_queue_item_new(m, v->pushed_fds, v->n_pushed_fds); - if (!q) - return -ENOMEM; - - v->n_pushed_fds = 0; /* fds now belong to the queue entry */ - - /* We already checked the precondition ourselves so this call cannot fail. */ - assert_se(varlink_enqueue_item(v, q) >= 0); - - return 0; -} - static int varlink_dispatch_method(sd_varlink *v) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *parameters = NULL; sd_varlink_method_flags_t flags = 0; @@ -1494,7 +998,7 @@ static int varlink_dispatch_method(sd_varlink *v) { } else if (streq(k, "oneway")) { - if ((flags & (SD_VARLINK_METHOD_ONEWAY|SD_VARLINK_METHOD_MORE)) != 0) + if ((flags & (SD_VARLINK_METHOD_ONEWAY|SD_VARLINK_METHOD_MORE|SD_VARLINK_METHOD_UPGRADE)) != 0) goto invalid; if (!sd_json_variant_is_boolean(e)) @@ -1505,7 +1009,7 @@ static int varlink_dispatch_method(sd_varlink *v) { } else if (streq(k, "more")) { - if ((flags & (SD_VARLINK_METHOD_ONEWAY|SD_VARLINK_METHOD_MORE)) != 0) + if ((flags & (SD_VARLINK_METHOD_ONEWAY|SD_VARLINK_METHOD_MORE|SD_VARLINK_METHOD_UPGRADE)) != 0) goto invalid; if (!sd_json_variant_is_boolean(e)) @@ -1514,6 +1018,17 @@ static int varlink_dispatch_method(sd_varlink *v) { if (sd_json_variant_boolean(e)) flags |= SD_VARLINK_METHOD_MORE; + } else if (streq(k, "upgrade")) { + + if ((flags & (SD_VARLINK_METHOD_ONEWAY|SD_VARLINK_METHOD_MORE|SD_VARLINK_METHOD_UPGRADE)) != 0) + goto invalid; + + if (!sd_json_variant_is_boolean(e)) + goto invalid; + + if (sd_json_variant_boolean(e)) + flags |= SD_VARLINK_METHOD_UPGRADE; + } else goto invalid; } @@ -1531,6 +1046,15 @@ static int varlink_dispatch_method(sd_varlink *v) { assert(v->server); + /* Reset the per-call upgrade marker on every dispatch — a previous method's + * UPGRADE flag must not bleed into this one. The transport-level bounded reads + * stay active for SD_VARLINK_SERVER_UPGRADABLE servers regardless. */ + v->protocol_upgrade = FLAGS_SET(flags, SD_VARLINK_METHOD_UPGRADE); + json_stream_set_flags( + &v->stream, + JSON_STREAM_BOUNDED_READS, + v->protocol_upgrade || FLAGS_SET(v->server->flags, SD_VARLINK_SERVER_UPGRADABLE)); + /* First consult user supplied method implementations */ callback = hashmap_get(v->server->methods, method); if (!callback) { @@ -1551,11 +1075,15 @@ static int varlink_dispatch_method(sd_varlink *v) { r = varlink_idl_validate_method_call(v->current_method, parameters, flags, &bad_field); if (r == -EBADE) { - varlink_log_errno(v, r, "Method %s() called without 'more' flag, but flag needs to be set.", - method); + bool missing_upgrade = FLAGS_SET(v->current_method->symbol_flags, SD_VARLINK_REQUIRES_UPGRADE) && + !FLAGS_SET(flags, SD_VARLINK_METHOD_UPGRADE); + + varlink_log_errno(v, r, "Method %s() called without '%s' flag, but flag needs to be set.", + method, missing_upgrade ? "upgrade" : "more"); if (v->state == VARLINK_PROCESSING_METHOD) { - r = sd_varlink_error(v, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); + r = sd_varlink_error(v, missing_upgrade ? SD_VARLINK_ERROR_EXPECTED_UPGRADE + : SD_VARLINK_ERROR_EXPECTED_MORE, NULL); /* If we didn't manage to enqueue an error response, then fail the * connection completely. Otherwise ignore the error from * sd_varlink_error() here, as it is synthesized from the function's @@ -1590,9 +1118,11 @@ static int varlink_dispatch_method(sd_varlink *v) { r = sd_varlink_error_errno(v, r); } else if (v->sentinel) { if (v->previous) { - r = varlink_enqueue_item(v, v->previous); + r = json_stream_enqueue_full(&v->stream, v->previous, v->previous_fds, v->n_previous_fds); if (r >= 0) { - TAKE_PTR(v->previous); + v->previous = sd_json_variant_unref(v->previous); + v->previous_fds = mfree(v->previous_fds); + v->n_previous_fds = 0; varlink_set_state(v, VARLINK_PROCESSED_METHOD); } } else { @@ -1602,8 +1132,18 @@ static int varlink_dispatch_method(sd_varlink *v) { * and no replies were enqueued by the callback. */ if (sentinel == POINTER_MAX) r = sd_varlink_reply(v, NULL); - else + else { r = sd_varlink_error(v, sentinel, NULL); + /* sd_varlink_error() deliberately returns a negative + * errno mapped from the error id on success (so method + * callbacks can `return sd_varlink_error(...);` to + * enqueue a reply and propagate a matching errno in one + * go). For sentinel dispatch we don't care about that + * mapping — the reply is either enqueued or not, which + * we detect via the state transition instead. */ + if (v->state == VARLINK_PROCESSED_METHOD) + r = 0; + } if (sentinel != POINTER_MAX) free(sentinel); @@ -1820,85 +1360,13 @@ _public_ int sd_varlink_get_current_parameters(sd_varlink *v, sd_json_variant ** return 0; } -static void handle_revents(sd_varlink *v, int revents) { - assert(v); - - if (v->connecting) { - /* If we have seen POLLOUT or POLLHUP on a socket we are asynchronously waiting a connect() - * to complete on, we know we are ready. We don't read the connection error here though, - * we'll get the error on the next read() or write(). */ - if ((revents & (POLLOUT|POLLHUP)) == 0) - return; - - varlink_log(v, "Asynchronous connection completed."); - v->connecting = false; - } else { - /* Note that we don't care much about POLLIN/POLLOUT here, we'll just try reading and writing - * what we can. However, we do care about POLLHUP to detect connection termination even if we - * momentarily don't want to read nor write anything. */ - - if (!FLAGS_SET(revents, POLLHUP)) - return; - - varlink_log(v, "Got POLLHUP from socket."); - v->got_pollhup = true; - } -} - _public_ int sd_varlink_wait(sd_varlink *v, uint64_t timeout) { - int r, events; - usec_t t; - assert_return(v, -EINVAL); if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - r = sd_varlink_get_timeout(v, &t); - if (r < 0) - return r; - if (t != USEC_INFINITY) - t = usec_sub_unsigned(t, now(CLOCK_MONOTONIC)); - - t = MIN(t, timeout); - - events = sd_varlink_get_events(v); - if (events < 0) - return events; - - struct pollfd pollfd[2]; - size_t n_poll_fd = 0; - - if (v->input_fd == v->output_fd) { - pollfd[n_poll_fd++] = (struct pollfd) { - .fd = v->input_fd, - .events = events, - }; - } else { - pollfd[n_poll_fd++] = (struct pollfd) { - .fd = v->input_fd, - .events = events & POLLIN, - }; - pollfd[n_poll_fd++] = (struct pollfd) { - .fd = v->output_fd, - .events = events & POLLOUT, - }; - }; - - r = ppoll_usec(pollfd, n_poll_fd, t); - if (ERRNO_IS_NEG_TRANSIENT(r)) /* Treat EINTR as not a timeout, but also nothing happened, and - * the caller gets a chance to call back into us */ - return 1; - if (r <= 0) - return r; - - /* Merge the seen events into one */ - int revents = 0; - FOREACH_ARRAY(p, pollfd, n_poll_fd) - revents |= p->revents; - - handle_revents(v, revents); - return 1; + return json_stream_wait(&v->stream, timeout); } _public_ int sd_varlink_is_idle(sd_varlink *v) { @@ -1919,68 +1387,55 @@ _public_ int sd_varlink_is_connected(sd_varlink *v) { } _public_ int sd_varlink_get_fd(sd_varlink *v) { - assert_return(v, -EINVAL); if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - if (v->input_fd != v->output_fd) + + int input_fd = v->stream.input_fd; + int output_fd = v->stream.output_fd; + + if (input_fd != output_fd) return varlink_log_errno(v, SYNTHETIC_ERRNO(EBADF), "Separate file descriptors for input/output set."); - if (v->input_fd < 0) + if (input_fd < 0) return varlink_log_errno(v, SYNTHETIC_ERRNO(EBADF), "No valid fd."); - return v->input_fd; + return input_fd; } _public_ int sd_varlink_get_input_fd(sd_varlink *v) { - assert_return(v, -EINVAL); if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - if (v->input_fd < 0) + + int input_fd = v->stream.input_fd; + if (input_fd < 0) return varlink_log_errno(v, SYNTHETIC_ERRNO(EBADF), "No valid input fd."); - return v->input_fd; + return input_fd; } _public_ int sd_varlink_get_output_fd(sd_varlink *v) { - assert_return(v, -EINVAL); if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - if (v->output_fd < 0) + + int output_fd = v->stream.output_fd; + if (output_fd < 0) return varlink_log_errno(v, SYNTHETIC_ERRNO(EBADF), "No valid output fd."); - return v->output_fd; + return output_fd; } _public_ int sd_varlink_get_events(sd_varlink *v) { - int ret = 0; - assert_return(v, -EINVAL); if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - if (v->connecting) /* When processing an asynchronous connect(), we only wait for EPOLLOUT, which - * tells us that the connection is now complete. Before that we should neither - * write() or read() from the fd. */ - return EPOLLOUT; - - if (!v->read_disconnected && - IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER) && - !v->current && - v->input_buffer_unscanned <= 0) - ret |= EPOLLIN; - - if (!v->write_disconnected && - (v->output_queue || - v->output_buffer_size > 0)) - ret |= EPOLLOUT; - - return ret; + return json_stream_get_events(&v->stream); } _public_ int sd_varlink_get_timeout(sd_varlink *v, uint64_t *ret) { @@ -1989,51 +1444,21 @@ _public_ int sd_varlink_get_timeout(sd_varlink *v, uint64_t *ret) { if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING) && - v->timeout != USEC_INFINITY) { - if (ret) - *ret = usec_add(v->timestamp, v->timeout); - return 1; - } else { - if (ret) - *ret = USEC_INFINITY; - return 0; - } + usec_t deadline = json_stream_get_timeout(&v->stream); + + if (ret) + *ret = deadline; + + return deadline != USEC_INFINITY; } _public_ int sd_varlink_flush(sd_varlink *v) { - int ret = 0, r; - assert_return(v, -EINVAL); if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - for (;;) { - if (v->output_buffer_size == 0 && !v->output_queue) - break; - if (v->write_disconnected) - return -ECONNRESET; - - r = varlink_write(v); - if (r < 0) - return r; - if (r > 0) { - ret = 1; - continue; - } - - r = fd_wait_for_event(v->output_fd, POLLOUT, USEC_INFINITY); - if (ERRNO_IS_NEG_TRANSIENT(r)) - continue; - if (r < 0) - return varlink_log_errno(v, r, "Poll failed on fd: %m"); - assert(r > 0); - - handle_revents(v, r); - } - - return ret; + return json_stream_flush(&v->stream); } static void varlink_detach_server(sd_varlink *v) { @@ -2044,18 +1469,22 @@ static void varlink_detach_server(sd_varlink *v) { if (!v->server) return; + /* Only touch by_uid for connections we already counted in count_connection() — + * those are exactly the ones for which the ucred was acquired or injected during + * sd_varlink_server_add_connection_pair(). Don't trigger an acquire from here. */ + struct ucred ucred; if (v->server->by_uid && - v->ucred_acquired && - uid_is_valid(v->ucred.uid)) { + json_stream_get_peer_ucred(&v->stream, &ucred) >= 0 && + uid_is_valid(ucred.uid)) { unsigned c; - c = PTR_TO_UINT(hashmap_get(v->server->by_uid, UID_TO_PTR(v->ucred.uid))); + c = PTR_TO_UINT(hashmap_get(v->server->by_uid, UID_TO_PTR(ucred.uid))); assert(c > 0); if (c == 1) - (void) hashmap_remove(v->server->by_uid, UID_TO_PTR(v->ucred.uid)); + (void) hashmap_remove(v->server->by_uid, UID_TO_PTR(ucred.uid)); else - (void) hashmap_replace(v->server->by_uid, UID_TO_PTR(v->ucred.uid), UINT_TO_PTR(c - 1)); + (void) hashmap_replace(v->server->by_uid, UID_TO_PTR(ucred.uid), UINT_TO_PTR(c - 1)); } assert(v->server->n_connections > 0); @@ -2131,12 +1560,12 @@ _public_ int sd_varlink_send(sd_varlink *v, const char *method, sd_json_variant if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = varlink_enqueue_json(v, m); + r = varlink_enqueue(v, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); /* No state change here, this is one-way only after all */ - v->timestamp = now(CLOCK_MONOTONIC); + json_stream_mark_activity(&v->stream); return 0; } @@ -2178,13 +1607,13 @@ _public_ int sd_varlink_invoke(sd_varlink *v, const char *method, sd_json_varian if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = varlink_enqueue_json(v, m); + r = varlink_enqueue(v, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); varlink_set_state(v, VARLINK_AWAITING_REPLY); v->n_pending++; - v->timestamp = now(CLOCK_MONOTONIC); + json_stream_mark_activity(&v->stream); return 0; } @@ -2229,13 +1658,13 @@ _public_ int sd_varlink_observe(sd_varlink *v, const char *method, sd_json_varia if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = varlink_enqueue_json(v, m); + r = varlink_enqueue(v, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); varlink_set_state(v, VARLINK_AWAITING_REPLY_MORE); v->n_pending++; - v->timestamp = now(CLOCK_MONOTONIC); + json_stream_mark_activity(&v->stream); return 0; } @@ -2257,104 +1686,244 @@ _public_ int sd_varlink_observeb(sd_varlink *v, const char *method, ...) { return sd_varlink_observe(v, method, parameters); } -_public_ int sd_varlink_call_full( +/* On success v->state will equal VARLINK_CALLED, the caller is responsible to adjust the state further if + * needed */ +static int varlink_call_internal(sd_varlink *v, sd_json_variant *request) { + int r; + + assert(v); + assert(request); + + if (v->state == VARLINK_DISCONNECTED) + return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); + if (v->state != VARLINK_IDLE_CLIENT) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection busy."); + + assert(v->n_pending == 0); /* n_pending can't be > 0 if we are in VARLINK_IDLE_CLIENT state */ + + /* If there was still a reply pinned from a previous call, now it's the time to get rid of it, so + * that we can assign a new reply shortly. */ + varlink_clear_current(v); + + r = varlink_enqueue(v, request); + if (r < 0) + return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); + + varlink_set_state(v, VARLINK_CALLING); + v->n_pending++; + json_stream_mark_activity(&v->stream); + + while (v->state == VARLINK_CALLING) { + r = sd_varlink_process(v); + if (r < 0) + return r; + if (r > 0) + continue; + + r = sd_varlink_wait(v, USEC_INFINITY); + if (r < 0) + return r; + } + + switch (v->state) { + + case VARLINK_CALLED: + assert(v->current); + return 0; + + case VARLINK_PENDING_DISCONNECT: + case VARLINK_DISCONNECTED: + return varlink_log_errno(v, SYNTHETIC_ERRNO(ECONNRESET), "Connection was closed."); + + case VARLINK_PENDING_TIMEOUT: + return varlink_log_errno(v, SYNTHETIC_ERRNO(ETIME), "Connection timed out."); + + default: + assert_not_reached(); + } +} + +_public_ int sd_varlink_call_full( + sd_varlink *v, + const char *method, + sd_json_variant *parameters, + sd_json_variant **ret_parameters, + const char **ret_error_id, + sd_varlink_reply_flags_t *ret_flags) { + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *m = NULL; + int r; + + assert_return(v, -EINVAL); + assert_return(method, -EINVAL); + + r = sd_json_buildo( + &m, + SD_JSON_BUILD_PAIR_STRING("method", method), + JSON_BUILD_PAIR_VARIANT_NON_EMPTY("parameters", parameters)); + if (r < 0) + return varlink_log_errno(v, r, "Failed to build json message: %m"); + + r = varlink_call_internal(v, m); + if (r < 0) + return r; + + varlink_set_state(v, VARLINK_IDLE_CLIENT); + assert(v->n_pending == 1); + v->n_pending--; + + sd_json_variant *e = sd_json_variant_by_key(v->current, "error"), + *p = sd_json_variant_by_key(v->current, "parameters"); + + /* If caller doesn't ask for the error string, then let's return an error code in case of failure */ + if (!ret_error_id && e) + return sd_varlink_error_to_errno(sd_json_variant_string(e), p); + + if (ret_parameters) + *ret_parameters = p; + if (ret_error_id) + *ret_error_id = e ? sd_json_variant_string(e) : NULL; + if (ret_flags) + *ret_flags = v->current_reply_flags; + + return 1; +} + +_public_ int sd_varlink_call( + sd_varlink *v, + const char *method, + sd_json_variant *parameters, + sd_json_variant **ret_parameters, + const char **ret_error_id) { + + return sd_varlink_call_full(v, method, parameters, ret_parameters, ret_error_id, NULL); +} + +static int varlink_handle_upgrade_fds(sd_varlink *v, int *ret_input_fd, int *ret_output_fd) { + int r; + + assert(v); + assert(ret_input_fd || ret_output_fd); + + /* Ensure no post-upgrade data was consumed into our input buffer (we ensure this via MSG_PEEK or + * byte-to-byte) and refuse the upgrade rather than silently losing the data. */ + if (json_stream_has_buffered_input(&v->stream)) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EPROTO), + "Unexpected buffered data during protocol upgrade, refusing."); + + _cleanup_close_ int input_fd = TAKE_FD(v->stream.input_fd), + output_fd = TAKE_FD(v->stream.output_fd); + + /* Pass the connection fds to the caller, it owns them now. Reset to blocking mode + * since callers of the upgraded protocol will generally expect normal blocking + * semantics. For bidirectional sockets (input_fd == output_fd), dup the fd so that + * callers always get two independent fds they can close separately. */ + if (input_fd == output_fd) { + output_fd = fcntl(input_fd, F_DUPFD_CLOEXEC, 3); + if (output_fd < 0) + return varlink_log_errno(v, errno, "Failed to dup upgraded connection fd: %m"); + } else { + r = fd_nonblock(output_fd, false); + if (r < 0) + return varlink_log_errno(v, r, "Failed to set output fd to blocking mode: %m"); + } + + r = fd_nonblock(input_fd, false); + if (r < 0) + return varlink_log_errno(v, r, "Failed to set input fd to blocking mode: %m"); + + /* Hand out requested fds, shut down unwanted directions. */ + if (ret_input_fd) + *ret_input_fd = TAKE_FD(input_fd); + else + (void) shutdown(input_fd, SHUT_RD); + + if (ret_output_fd) + *ret_output_fd = TAKE_FD(output_fd); + else + (void) shutdown(output_fd, SHUT_WR); + + return 0; +} + +_public_ int sd_varlink_call_and_upgrade( sd_varlink *v, const char *method, sd_json_variant *parameters, sd_json_variant **ret_parameters, const char **ret_error_id, - sd_varlink_reply_flags_t *ret_flags) { + int *ret_input_fd, + int *ret_output_fd) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *m = NULL; int r; assert_return(v, -EINVAL); assert_return(method, -EINVAL); - - if (v->state == VARLINK_DISCONNECTED) - return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - if (v->state != VARLINK_IDLE_CLIENT) - return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection busy."); - - assert(v->n_pending == 0); /* n_pending can't be > 0 if we are in VARLINK_IDLE_CLIENT state */ - - /* If there was still a reply pinned from a previous call, now it's the time to get rid of it, so - * that we can assign a new reply shortly. */ - varlink_clear_current(v); + assert_return(ret_input_fd || ret_output_fd, -EINVAL); r = sd_json_buildo( &m, SD_JSON_BUILD_PAIR_STRING("method", method), - JSON_BUILD_PAIR_VARIANT_NON_EMPTY("parameters", parameters)); + JSON_BUILD_PAIR_VARIANT_NON_EMPTY("parameters", parameters), + SD_JSON_BUILD_PAIR_BOOLEAN("upgrade", true)); if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = varlink_enqueue_json(v, m); - if (r < 0) - return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); - - varlink_set_state(v, VARLINK_CALLING); - v->n_pending++; - v->timestamp = now(CLOCK_MONOTONIC); - - while (v->state == VARLINK_CALLING) { - r = sd_varlink_process(v); - if (r < 0) - return r; - if (r > 0) - continue; - - r = sd_varlink_wait(v, USEC_INFINITY); - if (r < 0) - return r; + v->protocol_upgrade = true; + json_stream_set_flags(&v->stream, JSON_STREAM_BOUNDED_READS, true); + r = varlink_call_internal(v, m); + if (r < 0) { + v->protocol_upgrade = false; + json_stream_set_flags(&v->stream, JSON_STREAM_BOUNDED_READS, false); + return r; } - switch (v->state) { - - case VARLINK_CALLED: { - assert(v->current); - - varlink_set_state(v, VARLINK_IDLE_CLIENT); - assert(v->n_pending == 1); - v->n_pending--; - - sd_json_variant *e = sd_json_variant_by_key(v->current, "error"), - *p = sd_json_variant_by_key(v->current, "parameters"); + /* ensure we did not consume any data from the upgraded protocol */ + assert(!json_stream_has_buffered_input(&v->stream)); - /* If caller doesn't ask for the error string, then let's return an error code in case of failure */ - if (!ret_error_id && e) - return sd_varlink_error_to_errno(sd_json_variant_string(e), p); + sd_json_variant *e = sd_json_variant_by_key(v->current, "error"), + *p = sd_json_variant_by_key(v->current, "parameters"); - if (ret_parameters) - *ret_parameters = p; - if (ret_error_id) - *ret_error_id = e ? sd_json_variant_string(e) : NULL; - if (ret_flags) - *ret_flags = v->current_reply_flags; + /* don't steal the fd on server error */ + if (e) { + if (ret_error_id) { + *ret_error_id = sd_json_variant_string(e); + if (ret_parameters) + *ret_parameters = p; + r = 0; + } else + r = sd_varlink_error_to_errno(sd_json_variant_string(e), p); - return 1; + varlink_set_state(v, VARLINK_IDLE_CLIENT); + goto finish; } - case VARLINK_PENDING_DISCONNECT: - case VARLINK_DISCONNECTED: - return varlink_log_errno(v, SYNTHETIC_ERRNO(ECONNRESET), "Connection was closed."); + /* Even if setting up the fds fails we must disconnect: the server already accepted the + * upgrade, so the other side is speaking raw protocol while we expect JSON. */ + r = varlink_handle_upgrade_fds(v, ret_input_fd, ret_output_fd); + if (r < 0) { + varlink_set_state(v, VARLINK_DISCONNECTED); + goto finish; + } - case VARLINK_PENDING_TIMEOUT: - return varlink_log_errno(v, SYNTHETIC_ERRNO(ETIME), "Connection timed out."); + varlink_set_state(v, VARLINK_DISCONNECTED); + assert(v->n_pending == 1); + v->n_pending--; - default: - assert_not_reached(); - } -} + if (ret_parameters) + *ret_parameters = p; + if (ret_error_id) + *ret_error_id = NULL; -_public_ int sd_varlink_call( - sd_varlink *v, - const char *method, - sd_json_variant *parameters, - sd_json_variant **ret_parameters, - const char **ret_error_id) { + return 1; - return sd_varlink_call_full(v, method, parameters, ret_parameters, ret_error_id, NULL); +finish: + v->protocol_upgrade = false; + json_stream_set_flags(&v->stream, JSON_STREAM_BOUNDED_READS, false); + assert(v->n_pending == 1); + v->n_pending--; + return r; } _public_ int sd_varlink_callb_ap( @@ -2444,13 +2013,13 @@ _public_ int sd_varlink_collect_full( if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = varlink_enqueue_json(v, m); + r = varlink_enqueue(v, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); varlink_set_state(v, VARLINK_COLLECTING); v->n_pending++; - v->timestamp = now(CLOCK_MONOTONIC); + json_stream_mark_activity(&v->stream); for (;;) { while (v->state == VARLINK_COLLECTING) { @@ -2612,24 +2181,28 @@ _public_ int sd_varlink_reply(sd_varlink *v, sd_json_variant *parameters) { if (more && v->sentinel) { if (v->previous) { - r = sd_json_variant_set_field_boolean(&v->previous->data, "continues", true); + r = sd_json_variant_set_field_boolean(&v->previous, "continues", true); if (r < 0) return r; - r = varlink_enqueue_item(v, v->previous); + r = json_stream_enqueue_full(&v->stream, v->previous, v->previous_fds, v->n_previous_fds); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); + + v->previous = sd_json_variant_unref(v->previous); + v->previous_fds = mfree(v->previous_fds); + v->n_previous_fds = 0; } - v->previous = varlink_json_queue_item_new(m, v->pushed_fds, v->n_pushed_fds); - if (!v->previous) - return -ENOMEM; + v->previous = sd_json_variant_ref(m); + v->previous_fds = TAKE_PTR(v->pushed_fds); + v->n_previous_fds = v->n_pushed_fds; + v->n_pushed_fds = 0; - v->n_pushed_fds = 0; /* fds now belong to the queue entry */ return 1; } - r = varlink_enqueue_json(v, m); + r = varlink_enqueue(v, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -2664,6 +2237,77 @@ _public_ int sd_varlink_replyb(sd_varlink *v, ...) { return sd_varlink_reply(v, parameters); } +_public_ int sd_varlink_reply_and_upgrade(sd_varlink *v, sd_json_variant *parameters, int *ret_input_fd, int *ret_output_fd) { + int r; + + assert_return(v, -EINVAL); + assert_return(ret_input_fd || ret_output_fd, -EINVAL); + + if (v->state == VARLINK_DISCONNECTED) + return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); + + if (!IN_SET(v->state, + VARLINK_PROCESSING_METHOD, + VARLINK_PENDING_METHOD)) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection busy."); + + /* Verify the client actually requested a protocol upgrade */ + if (!v->protocol_upgrade) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EPROTO), + "Method call did not request a protocol upgrade."); + + /* Ensure we did not buffer any data beyond the upgrade request. Check this before sending the + * reply so that we can return a normal error (the framework will send an error reply to the + * client). In normal operation this cannot happen because the client waits for our reply before + * sending raw data, and we set protocol_upgrade=true in dispatch to limit subsequent reads to + * single bytes. But a misbehaving client could pipeline data early. */ + if (json_stream_has_buffered_input(&v->stream)) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EBADMSG), + "Unexpected buffered data from client during protocol upgrade."); + + /* Validate parameters BEFORE sanitization (same validation as sd_varlink_reply(), but upgrade + * replies never carry the 'continues' flag so we always pass flags=0) */ + if (v->current_method) { + const char *bad_field = NULL; + + r = varlink_idl_validate_method_reply(v->current_method, parameters, /* flags= */ 0, &bad_field); + if (r < 0) + /* Please adjust test/units/end.sh when updating the log message. */ + varlink_log_errno(v, r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m", + v->current_method->name, strna(bad_field)); + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *m = NULL; + r = sd_json_buildo(&m, JSON_BUILD_PAIR_VARIANT_NON_EMPTY("parameters", parameters)); + if (r < 0) + return varlink_log_errno(v, r, "Failed to build json message: %m"); + + r = varlink_enqueue(v, m); + if (r < 0) + return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); + + /* Flush the reply to the socket before stealing the fds. The reply must be fully written + * before the caller starts speaking the upgraded protocol. */ + r = json_stream_flush(&v->stream); + if (r < 0) { + varlink_log_errno(v, r, "Failed to flush reply before protocol upgrade: %m"); + goto disconnect; + } + + /* Detach from the event loop before stealing the fds */ + sd_varlink_detach_event(v); + + /* Now hand the original FDs over to the caller, from this point on we have nothing to do with the + * connection anymore, it's up to the caller and we close the connection below */ + r = varlink_handle_upgrade_fds(v, ret_input_fd, ret_output_fd); + +disconnect: + /* This also sets the connection state to VARLINK_DISCONNECTED */ + sd_varlink_close(v); + + return r < 0 ? r : 1; +} + _public_ int sd_varlink_reset_fds(sd_varlink *v) { assert_return(v, -EINVAL); @@ -2692,18 +2336,20 @@ _public_ int sd_varlink_error(sd_varlink *v, const char *error_id, sd_json_varia return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection busy."); if (v->previous) { - r = sd_json_variant_set_field_boolean(&v->previous->data, "continues", true); + r = sd_json_variant_set_field_boolean(&v->previous, "continues", true); if (r < 0) return r; /* If we have a previous reply still ready make sure we queue it before the error. We only * ever set "previous" if we're in a streaming method so we pass more=true unconditionally * here as we know we're still going to queue an error afterwards. */ - r = varlink_enqueue_item(v, v->previous); + r = json_stream_enqueue_full(&v->stream, v->previous, v->previous_fds, v->n_previous_fds); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); - TAKE_PTR(v->previous); + v->previous = sd_json_variant_unref(v->previous); + v->previous_fds = mfree(v->previous_fds); + v->n_previous_fds = 0; } /* Reset the list of pushed file descriptors before sending an error reply. We do this here to @@ -2734,7 +2380,7 @@ _public_ int sd_varlink_error(sd_varlink *v, const char *error_id, sd_json_varia if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = varlink_enqueue_json(v, m); + r = varlink_enqueue(v, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -2874,7 +2520,7 @@ _public_ int sd_varlink_notify(sd_varlink *v, sd_json_variant *parameters) { if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = varlink_enqueue_json(v, m); + r = varlink_enqueue(v, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -2946,99 +2592,60 @@ _public_ void* sd_varlink_get_userdata(sd_varlink *v) { return v->userdata; } -static int varlink_acquire_ucred(sd_varlink *v) { - int r; - - assert(v); +_public_ int sd_varlink_set_sentinel(sd_varlink *v, const char *error_id) { + assert_return(v, -EINVAL); - if (v->ucred_acquired) + /* If the caller doesn't want a reply, then don't set a sentinel. */ + if (v->state == VARLINK_PROCESSING_METHOD_ONEWAY) return 0; - /* If we are connected asymmetrically, let's refuse, since it's not clear if caller wants to know - * peer on read or write fd */ - if (v->input_fd != v->output_fd) - return -EBADF; + /* This has to be called during a callback, and not after it has exited. */ + assert_return(IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE), + -EUCLEAN); - r = getpeercred(v->input_fd, &v->ucred); - if (r < 0) - return r; + char *s = NULL; + if (strdup_to(&s, error_id) < 0) + return log_oom_debug(); + + if (v->sentinel != POINTER_MAX) + free(v->sentinel); - v->ucred_acquired = true; + v->sentinel = s ?: POINTER_MAX; return 0; } _public_ int sd_varlink_get_peer_uid(sd_varlink *v, uid_t *ret) { - int r; - assert_return(v, -EINVAL); assert_return(ret, -EINVAL); - r = varlink_acquire_ucred(v); - if (r < 0) - return varlink_log_errno(v, r, "Failed to acquire credentials: %m"); - - if (!uid_is_valid(v->ucred.uid)) - return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer UID is invalid."); - - *ret = v->ucred.uid; - return 0; + return json_stream_acquire_peer_uid(&v->stream, ret); } _public_ int sd_varlink_get_peer_gid(sd_varlink *v, gid_t *ret) { - int r; - assert_return(v, -EINVAL); assert_return(ret, -EINVAL); - r = varlink_acquire_ucred(v); - if (r < 0) - return varlink_log_errno(v, r, "Failed to acquire credentials: %m"); - - if (!gid_is_valid(v->ucred.gid)) - return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer GID is invalid."); - - *ret = v->ucred.gid; - return 0; + return json_stream_acquire_peer_gid(&v->stream, ret); } _public_ int sd_varlink_get_peer_pid(sd_varlink *v, pid_t *ret) { - int r; - assert_return(v, -EINVAL); assert_return(ret, -EINVAL); - r = varlink_acquire_ucred(v); - if (r < 0) - return varlink_log_errno(v, r, "Failed to acquire credentials: %m"); - - if (!pid_is_valid(v->ucred.pid)) - return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer uid is invalid."); - - *ret = v->ucred.pid; - return 0; + return json_stream_acquire_peer_pid(&v->stream, ret); } _public_ int sd_varlink_get_peer_pidfd(sd_varlink *v) { assert_return(v, -EINVAL); - if (v->peer_pidfd >= 0) - return v->peer_pidfd; - - if (v->input_fd != v->output_fd) - return -EBADF; - - v->peer_pidfd = getpeerpidfd(v->input_fd); - if (v->peer_pidfd < 0) - return varlink_log_errno(v, v->peer_pidfd, "Failed to acquire pidfd of peer: %m"); - - return v->peer_pidfd; + return json_stream_acquire_peer_pidfd(&v->stream); } _public_ int sd_varlink_set_relative_timeout(sd_varlink *v, uint64_t timeout) { assert_return(v, -EINVAL); /* If set to 0, reset to default value */ - v->timeout = timeout == 0 ? VARLINK_DEFAULT_TIMEOUT_USEC : timeout; + json_stream_set_timeout(&v->stream, timeout == 0 ? VARLINK_DEFAULT_TIMEOUT_USEC : timeout); return 0; } @@ -3051,33 +2658,13 @@ _public_ sd_varlink_server *sd_varlink_get_server(sd_varlink *v) { _public_ int sd_varlink_set_description(sd_varlink *v, const char *description) { assert_return(v, -EINVAL); - return free_and_strdup(&v->description, description); + return json_stream_set_description(&v->stream, description); } _public_ const char* sd_varlink_get_description(sd_varlink *v) { assert_return(v, NULL); - return v->description; -} - -static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - sd_varlink *v = ASSERT_PTR(userdata); - - assert(s); - - handle_revents(v, revents); - (void) sd_varlink_process(v); - - return 1; -} - -static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) { - sd_varlink *v = ASSERT_PTR(userdata); - - assert(s); - - (void) sd_varlink_process(v); - return 1; + return json_stream_get_description(&v->stream); } static int defer_callback(sd_event_source *s, void *userdata) { @@ -3089,47 +2676,6 @@ static int defer_callback(sd_event_source *s, void *userdata) { return 1; } -static int prepare_callback(sd_event_source *s, void *userdata) { - sd_varlink *v = ASSERT_PTR(userdata); - int r, e; - usec_t until; - bool have_timeout; - - assert(s); - - e = sd_varlink_get_events(v); - if (e < 0) - return e; - - if (v->input_event_source == v->output_event_source) - /* Same fd for input + output */ - r = sd_event_source_set_io_events(v->input_event_source, e); - else { - r = sd_event_source_set_io_events(v->input_event_source, e & EPOLLIN); - if (r >= 0) - r = sd_event_source_set_io_events(v->output_event_source, e & EPOLLOUT); - } - if (r < 0) - return varlink_log_errno(v, r, "Failed to set source events: %m"); - - r = sd_varlink_get_timeout(v, &until); - if (r < 0) - return r; - have_timeout = r > 0; - - if (have_timeout) { - r = sd_event_source_set_time(v->time_event_source, until); - if (r < 0) - return varlink_log_errno(v, r, "Failed to set source time: %m"); - } - - r = sd_event_source_set_enabled(v->time_event_source, have_timeout ? SD_EVENT_ON : SD_EVENT_OFF); - if (r < 0) - return varlink_log_errno(v, r, "Failed to enable event source: %m"); - - return 1; -} - static int quit_callback(sd_event_source *event, void *userdata) { sd_varlink *v = ASSERT_PTR(userdata); @@ -3145,27 +2691,15 @@ _public_ int sd_varlink_attach_event(sd_varlink *v, sd_event *e, int64_t priorit int r; assert_return(v, -EINVAL); - assert_return(!v->event, -EBUSY); - - if (e) - v->event = sd_event_ref(e); - else { - r = sd_event_default(&v->event); - if (r < 0) - return varlink_log_errno(v, r, "Failed to create event source: %m"); - } - - r = sd_event_add_time(v->event, &v->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, v); - if (r < 0) - goto fail; + assert_return(!json_stream_get_event(&v->stream), -EBUSY); - r = sd_event_source_set_priority(v->time_event_source, priority); + r = json_stream_attach_event(&v->stream, e, priority); if (r < 0) - goto fail; + return r; - (void) sd_event_source_set_description(v->time_event_source, "varlink-time"); + sd_event *event = json_stream_get_event(&v->stream); - r = sd_event_add_exit(v->event, &v->quit_event_source, quit_callback, v); + r = sd_event_add_exit(event, &v->quit_event_source, quit_callback, v); if (r < 0) goto fail; @@ -3175,35 +2709,7 @@ _public_ int sd_varlink_attach_event(sd_varlink *v, sd_event *e, int64_t priorit (void) sd_event_source_set_description(v->quit_event_source, "varlink-quit"); - r = sd_event_add_io(v->event, &v->input_event_source, v->input_fd, 0, io_callback, v); - if (r < 0) - goto fail; - - r = sd_event_source_set_prepare(v->input_event_source, prepare_callback); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(v->input_event_source, priority); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(v->input_event_source, "varlink-input"); - - if (v->input_fd == v->output_fd) - v->output_event_source = sd_event_source_ref(v->input_event_source); - else { - r = sd_event_add_io(v->event, &v->output_event_source, v->output_fd, 0, io_callback, v); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(v->output_event_source, priority); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(v->output_event_source, "varlink-output"); - } - - r = sd_event_add_defer(v->event, &v->defer_event_source, defer_callback, v); + r = sd_event_add_defer(event, &v->defer_event_source, defer_callback, v); if (r < 0) goto fail; @@ -3225,27 +2731,25 @@ _public_ void sd_varlink_detach_event(sd_varlink *v) { if (!v) return; - varlink_detach_event_sources(v); - - v->event = sd_event_unref(v->event); + v->quit_event_source = sd_event_source_disable_unref(v->quit_event_source); + v->defer_event_source = sd_event_source_disable_unref(v->defer_event_source); + json_stream_detach_event(&v->stream); } _public_ sd_event* sd_varlink_get_event(sd_varlink *v) { assert_return(v, NULL); - return v->event; + return json_stream_get_event(&v->stream); } _public_ int sd_varlink_push_fd(sd_varlink *v, int fd) { - int i; - assert_return(v, -EINVAL); assert_return(fd >= 0, -EBADF); /* Takes an fd to send along with the *next* varlink message sent via this varlink connection. This * takes ownership of the specified fd. Use varlink_dup_fd() below to duplicate the fd first. */ - if (!v->allow_fd_passing_output) + if (!json_stream_flags_set(&v->stream, JSON_STREAM_ALLOW_FD_PASSING_OUTPUT)) return -EPERM; if (v->n_pushed_fds >= SCM_MAX_FD) /* Kernel doesn't support more than 253 fds per message, refuse early hence */ @@ -3254,7 +2758,7 @@ _public_ int sd_varlink_push_fd(sd_varlink *v, int fd) { if (!GREEDY_REALLOC(v->pushed_fds, v->n_pushed_fds + 1)) return -ENOMEM; - i = (int) v->n_pushed_fds; + int i = (int) v->n_pushed_fds; v->pushed_fds[v->n_pushed_fds++] = fd; return i; } @@ -3286,13 +2790,10 @@ _public_ int sd_varlink_peek_fd(sd_varlink *v, size_t i) { /* Returns one of the file descriptors that were received along with the current message. This does * not duplicate the fd nor invalidate it, it hence remains in our possession. */ - if (v->allow_fd_passing_input <= 0) + if (!json_stream_flags_set(&v->stream, JSON_STREAM_ALLOW_FD_PASSING_INPUT)) return -EPERM; - if (i >= v->n_input_fds) - return -ENXIO; - - return v->input_fds[i]; + return json_stream_peek_input_fd(&v->stream, i); } _public_ int sd_varlink_peek_dup_fd(sd_varlink *v, size_t i) { @@ -3312,113 +2813,42 @@ _public_ int sd_varlink_take_fd(sd_varlink *v, size_t i) { * we'll invalidate the reference to it under our possession. If called twice in a row will return * -EBADF */ - if (v->allow_fd_passing_input <= 0) + if (!json_stream_flags_set(&v->stream, JSON_STREAM_ALLOW_FD_PASSING_INPUT)) return -EPERM; - if (i >= v->n_input_fds) - return -ENXIO; - - return TAKE_FD(v->input_fds[i]); + return json_stream_take_input_fd(&v->stream, i); } _public_ int sd_varlink_get_n_fds(sd_varlink *v) { assert_return(v, -EINVAL); - if (v->allow_fd_passing_input <= 0) + if (!json_stream_flags_set(&v->stream, JSON_STREAM_ALLOW_FD_PASSING_INPUT)) return -EPERM; - return (int) v->n_input_fds; -} - -static int verify_unix_socket(sd_varlink *v) { - assert(v); - - /* Returns: - * • 0 if this is an AF_UNIX socket - * • -ENOTSOCK if this is not a socket at all - * • -ENOMEDIUM if this is a socket, but not an AF_UNIX socket - * - * Reminder: - * • v->af is < 0 if we haven't checked what kind of address family the thing is yet. - * • v->af == AF_UNSPEC if we checked but it's not a socket - * • otherwise: v->af contains the address family we determined */ - - if (v->af < 0) { - /* If we have distinct input + output fds, we don't consider ourselves to be connected via a regular - * AF_UNIX socket. */ - if (v->input_fd != v->output_fd) { - v->af = AF_UNSPEC; - return -ENOTSOCK; - } - - struct stat st; - - if (fstat(v->input_fd, &st) < 0) - return -errno; - if (!S_ISSOCK(st.st_mode)) { - v->af = AF_UNSPEC; - return -ENOTSOCK; - } - - v->af = socket_get_family(v->input_fd); - if (v->af < 0) - return v->af; - } - - return v->af == AF_UNIX ? 0 : - v->af == AF_UNSPEC ? -ENOTSOCK : -ENOMEDIUM; + return (int) json_stream_get_n_input_fds(&v->stream); } _public_ int sd_varlink_set_allow_fd_passing_input(sd_varlink *v, int b) { - int r; - assert_return(v, -EINVAL); - if (v->allow_fd_passing_input >= 0 && (v->allow_fd_passing_input > 0) == !!b) - return 0; - - r = verify_unix_socket(v); - if (r < 0) { - assert(v->allow_fd_passing_input <= 0); - - if (!b) { - v->allow_fd_passing_input = false; - return 0; - } - - return r; - } - - if (!v->server || FLAGS_SET(v->server->flags, SD_VARLINK_SERVER_FD_PASSING_INPUT_STRICT)) { - r = setsockopt_int(v->input_fd, SOL_SOCKET, SO_PASSRIGHTS, !!b); - if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) - log_debug_errno(r, "Failed to set SO_PASSRIGHTS socket option: %m"); - } + /* Server connections that haven't opted into FD_PASSING_INPUT_STRICT skip the + * per-connection SO_PASSRIGHTS setsockopt — the listening server already configured + * the socket option once at listen time. */ + bool with_sockopt = !v->server || FLAGS_SET(v->server->flags, SD_VARLINK_SERVER_FD_PASSING_INPUT_STRICT); - v->allow_fd_passing_input = !!b; - return 1; + return json_stream_set_allow_fd_passing_input(&v->stream, !!b, with_sockopt); } _public_ int sd_varlink_set_allow_fd_passing_output(sd_varlink *v, int b) { - int r; - assert_return(v, -EINVAL); - if (v->allow_fd_passing_output == !!b) - return 0; - - r = verify_unix_socket(v); - if (r < 0) - return r; - - v->allow_fd_passing_output = !!b; - return 1; + return json_stream_set_allow_fd_passing_output(&v->stream, !!b); } _public_ int sd_varlink_set_input_sensitive(sd_varlink *v) { assert_return(v, -EINVAL); - v->input_sensitive = true; + json_stream_set_flags(&v->stream, JSON_STREAM_INPUT_SENSITIVE, true); return 0; } @@ -3436,7 +2866,8 @@ _public_ int sd_varlink_server_new(sd_varlink_server **ret, sd_varlink_server_fl SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT| SD_VARLINK_SERVER_FD_PASSING_INPUT_STRICT| SD_VARLINK_SERVER_HANDLE_SIGINT| - SD_VARLINK_SERVER_HANDLE_SIGTERM)) == 0, -EINVAL); + SD_VARLINK_SERVER_HANDLE_SIGTERM| + SD_VARLINK_SERVER_UPGRADABLE)) == 0, -EINVAL); s = new(sd_varlink_server, 1); if (!s) @@ -3563,8 +2994,6 @@ static int count_connection(sd_varlink_server *server, const struct ucred *ucred assert(server); assert(ucred); - server->n_connections++; - if (FLAGS_SET(server->flags, SD_VARLINK_SERVER_ACCOUNT_UID)) { assert(uid_is_valid(ucred->uid)); @@ -3582,6 +3011,8 @@ static int count_connection(sd_varlink_server *server, const struct ucred *ucred return varlink_server_log_errno(server, r, "Failed to increment counter in UID hash table: %m"); } + server->n_connections++; + return 0; } @@ -3638,19 +3069,24 @@ _public_ int sd_varlink_server_add_connection_pair( v->server = sd_varlink_server_ref(server); sd_varlink_ref(v); - v->input_fd = input_fd; - v->output_fd = output_fd; + r = json_stream_attach_fds(&v->stream, input_fd, output_fd); + if (r < 0) + return r; + if (server->flags & SD_VARLINK_SERVER_INHERIT_USERDATA) v->userdata = server->userdata; - if (ucred_acquired) { - v->ucred = ucred; - v->ucred_acquired = true; - } + /* If the server might receive a protocol upgrade method, switch the input path to + * byte-bounded reads so we don't accidentally consume post-upgrade bytes. */ + if (FLAGS_SET(server->flags, SD_VARLINK_SERVER_UPGRADABLE)) + json_stream_set_flags(&v->stream, JSON_STREAM_BOUNDED_READS, true); + + if (ucred_acquired) + json_stream_set_peer_ucred(&v->stream, &ucred); _cleanup_free_ char *desc = NULL; if (asprintf(&desc, "%s-%i-%i", varlink_server_description(server), input_fd, output_fd) >= 0) - v->description = TAKE_PTR(desc); + json_stream_set_description(&v->stream, desc); (void) sd_varlink_set_allow_fd_passing_input(v, FLAGS_SET(server->flags, SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT)); (void) sd_varlink_set_allow_fd_passing_output(v, FLAGS_SET(server->flags, SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT)); @@ -3661,8 +3097,12 @@ _public_ int sd_varlink_server_add_connection_pair( r = sd_varlink_attach_event(v, server->event, server->event_priority); if (r < 0) { varlink_log_errno(v, r, "Failed to attach new connection: %m"); - TAKE_FD(v->input_fd); /* take the fd out of the connection again */ - TAKE_FD(v->output_fd); + /* Detach the fds from the connection so the caller (the connect callback) + * can decide what to do with them. The original fd value(s) the caller + * passed in are still owned by the caller; we just stop the connection + * from closing them on shutdown. */ + TAKE_FD(v->stream.input_fd); + TAKE_FD(v->stream.output_fd); sd_varlink_close(v); return r; } @@ -4393,6 +3833,7 @@ _public_ int sd_varlink_error_to_errno(const char *error, sd_json_variant *param { SD_VARLINK_ERROR_INVALID_PARAMETER, -EINVAL }, { SD_VARLINK_ERROR_PERMISSION_DENIED, -EACCES }, { SD_VARLINK_ERROR_EXPECTED_MORE, -EBADE }, + { SD_VARLINK_ERROR_EXPECTED_UPGRADE, -EPROTOTYPE }, }; int r; diff --git a/src/libsystemd/sd-varlink/varlink-internal.h b/src/libsystemd/sd-varlink/varlink-internal.h index 39b15e12a1fee..8087c2c432464 100644 --- a/src/libsystemd/sd-varlink/varlink-internal.h +++ b/src/libsystemd/sd-varlink/varlink-internal.h @@ -5,6 +5,7 @@ #include "sd-varlink.h" +#include "json-stream.h" #include "list.h" #include "pidref.h" #include "sd-forward.h" @@ -70,85 +71,21 @@ typedef enum VarlinkState { VARLINK_PROCESSING_METHOD, \ VARLINK_PROCESSING_METHOD_MORE) -typedef struct VarlinkJsonQueueItem VarlinkJsonQueueItem; - -/* A queued message we shall write into the socket, along with the file descriptors to send at the same - * time. This queue item binds them together so that message/fd boundaries are maintained throughout the - * whole pipeline. */ -struct VarlinkJsonQueueItem { - LIST_FIELDS(VarlinkJsonQueueItem, queue); - sd_json_variant *data; - size_t n_fds; - int fds[]; -}; - typedef struct sd_varlink { unsigned n_ref; sd_varlink_server *server; VarlinkState state; - bool connecting; /* This boolean indicates whether the socket fd we are operating on is currently - * processing an asynchronous connect(). In that state we watch the socket for - * EPOLLOUT, but we refrain from calling read() or write() on the socket as that - * will trigger ENOTCONN. Note that this boolean is kept separate from the - * VarlinkState above on purpose: while the connect() is still not complete we - * already want to allow queuing of messages and similar. Thus it's nice to keep - * these two state concepts separate: the VarlinkState encodes what our own view of - * the connection is, i.e. whether we think it's a server, a client, and has - * something queued already, while 'connecting' tells us a detail about the - * transport used below, that should have no effect on how we otherwise accept and - * process operations from the user. - * - * Or to say this differently: VARLINK_STATE_IS_ALIVE(state) tells you whether the - * connection is good to use, even if it might not be fully connected - * yet. connecting=true then informs you that actually we are still connecting, and - * the connection is actually not established yet and thus any requests you enqueue - * now will still work fine but will be queued only, not sent yet, but that - * shouldn't stop you from using the connection, since eventually whatever you queue - * *will* be sent. - * - * Or to say this even differently: 'state' is a high-level ("application layer" - * high, if you so will) state, while 'conecting' is a low-level ("transport layer" - * low, if you so will) state, and while they are not entirely unrelated and - * sometimes propagate effects to each other they are only asynchronously connected - * at most. */ - unsigned n_pending; - - int input_fd; - int output_fd; - - char *input_buffer; /* valid data starts at input_buffer_index, ends at input_buffer_index+input_buffer_size */ - size_t input_buffer_index; - size_t input_buffer_size; - size_t input_buffer_unscanned; - void *input_control_buffer; - size_t input_control_buffer_size; + /* Transport layer: input/output buffers, fd passing, output queue, read/write/parse + * step functions, sd-event integration (input/output/time event sources, idle + * timeout, description, peer credentials). The varlink-level state machine and + * dispatch logic live in sd-varlink.c; everything else about moving bytes is + * delegated. */ + JsonStream stream; - char *output_buffer; /* valid data starts at output_buffer_index, ends at output_buffer_index+output_buffer_size */ - size_t output_buffer_index; - size_t output_buffer_size; - - int *input_fds; /* file descriptors associated with the data in input_buffer (for fd passing) */ - size_t n_input_fds; - - int *output_fds; /* file descriptors associated with the data in output_buffer (for fd passing) */ - size_t n_output_fds; - - /* Further messages to output not yet formatted into text, and thus not included in output_buffer - * yet. We keep them separate from output_buffer, to not violate fd message boundaries: we want that - * each fd that is sent is associated with its fds, and that fds cannot be accidentally associated - * with preceding or following messages. */ - LIST_HEAD(VarlinkJsonQueueItem, output_queue); - VarlinkJsonQueueItem *output_queue_tail; - size_t n_output_queue; - - /* The fds to associate with the next message that is about to be enqueued. The user first pushes the - * fds it intends to send via varlink_push_fd() into this queue, and then once the message data is - * submitted we'll combine the fds and the message data into one. */ - int *pushed_fds; - size_t n_pushed_fds; + unsigned n_pending; sd_varlink_reply_t reply_callback; @@ -157,37 +94,23 @@ typedef struct sd_varlink { sd_varlink_reply_flags_t current_reply_flags; sd_varlink_symbol *current_method; - VarlinkJsonQueueItem *previous; - char *sentinel; - - int peer_pidfd; - struct ucred ucred; - bool ucred_acquired:1; - - bool write_disconnected:1; - bool read_disconnected:1; - bool prefer_read:1; - bool prefer_write:1; - bool got_pollhup:1; - - bool output_buffer_sensitive:1; /* whether to erase the output buffer after writing it to the socket */ - bool input_sensitive:1; /* Whether incoming messages might be sensitive */ - - bool allow_fd_passing_output; - int allow_fd_passing_input; + int *pushed_fds; + size_t n_pushed_fds; - int af; /* address family if socket; AF_UNSPEC if not socket; negative if not known */ + sd_json_variant *previous; + int *previous_fds; + size_t n_previous_fds; + char *sentinel; - usec_t timestamp; - usec_t timeout; + /* Per-call protocol-upgrade marker: set when the *current* method call carries the + * SD_VARLINK_METHOD_UPGRADE flag. Validated by sd_varlink_reply_and_upgrade() to + * ensure the caller's contract is honored. The transport-layer "stop reading at the + * next message boundary" behavior is governed independently by the JsonStream's + * bounded_reads flag. */ + bool protocol_upgrade:1; void *userdata; - char *description; - sd_event *event; - sd_event_source *input_event_source; - sd_event_source *output_event_source; - sd_event_source *time_event_source; sd_event_source *quit_event_source; sd_event_source *defer_event_source; @@ -252,7 +175,7 @@ typedef struct sd_varlink_server { log_debug("%s: " fmt, varlink_server_description(s), ##__VA_ARGS__) static inline const char* varlink_description(sd_varlink *v) { - return (v ? v->description : NULL) ?: "varlink"; + return (v ? json_stream_get_description(&v->stream) : NULL) ?: "varlink"; } static inline const char* varlink_server_description(sd_varlink_server *s) { diff --git a/src/libsystemd/sd-varlink/varlink-util.c b/src/libsystemd/sd-varlink/varlink-util.c index 916ac2ba996fe..7b8797c92c85b 100644 --- a/src/libsystemd/sd-varlink/varlink-util.c +++ b/src/libsystemd/sd-varlink/varlink-util.c @@ -1,10 +1,17 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-event.h" +#include "sd-varlink.h" + #include "alloc-util.h" #include "errno-util.h" +#include "fd-util.h" #include "log.h" +#include "path-util.h" #include "pidref.h" +#include "recurse-dir.h" #include "set.h" +#include "socket-util.h" #include "string-util.h" #include "varlink-internal.h" #include "varlink-util.h" @@ -175,6 +182,8 @@ int varlink_server_new( _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; int r; + assert(ret); + r = sd_varlink_server_new(&s, flags|SD_VARLINK_SERVER_FD_PASSING_INPUT_STRICT); if (r < 0) return log_debug_errno(r, "Failed to allocate varlink server object: %m"); @@ -205,35 +214,171 @@ int varlink_check_privileged_peer(sd_varlink *vl) { return 0; } -int varlink_set_sentinel(sd_varlink *v, const char *error_id) { - _cleanup_free_ char *s = NULL; +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + varlink_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + sd_varlink, + sd_varlink_unref); - assert(v); +static int varlink_finish_idle(Set *s) { + int r; - /* If the caller doesn't want a reply, then don't set a sentinel. */ - if (v->state == VARLINK_PROCESSING_METHOD_ONEWAY) - return 0; + sd_varlink *vl; + bool fully_idle = true; + SET_FOREACH(vl, s) { + r = sd_varlink_is_idle(vl); + if (r < 0) + return r; + if (r == 0) + fully_idle = false; + else { + /* Idle? Then we can close the connection, and release some resources. */ + assert_se(set_remove(s, vl) == vl); + vl = sd_varlink_close_unref(vl); + } + } - /* This has to be called during a callback, and not after it has exited. */ - assert(IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE)); + return fully_idle; +} - if (error_id) { - s = strdup(error_id); - if (!s) - return -ENOMEM; +#define VARLINK_EXECUTE_SOCKETS_MAX 255 + +ssize_t varlink_execute_directory( + const char *path, + const char *method, + sd_json_variant *parameters, + bool more, + usec_t timeout_usec, + sd_varlink_reply_t reply, + void *userdata) { + + int r; + + assert(path); + assert(method); + + /* Invokes the specified method on all Varlink sockets in the specified directory. Any reply + * will be dispatched to the reply callback. Blocks until the last reply has come in. + * + * Returns how many sockets were contacted. + * + * Usecase for all of this: hook directories, where components can link their sockets into to get + * notified about certain system events. */ + + _cleanup_close_ int fd = open(path, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return log_debug_errno(errno, "Failed to open '%s': %m", path); + + _cleanup_free_ DirectoryEntries *dentries = NULL; + r = readdir_all(fd, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &dentries); + if (r < 0) + return log_debug_errno(r, "Failed to enumerate '%s': %m", path); + + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(set_freep) Set *links = NULL; + size_t t = 0; + FOREACH_ARRAY(dp, dentries->entries, dentries->n_entries) { + struct dirent *de = *dp; + + if (de->d_type != DT_SOCK) + continue; + + t++; + + _cleanup_free_ char *j = path_join(path, de->d_name); + if (!j) + return log_oom_debug(); + + if (set_size(links) >= VARLINK_EXECUTE_SOCKETS_MAX) { + log_debug("Too many sockets (%zu) in directory, skipping '%s'.", t, j); + continue; + } + + _cleanup_close_ int socket_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (socket_fd < 0) + return log_debug_errno(errno, "Failed to allocate AF_UNIX/SOCK_STREAM socket: %m"); + + r = connect_unix_path(socket_fd, fd, de->d_name); + if (r < 0) { + log_debug_errno(r, "Failed to connect to '%s', ignoring: %m", j); + continue; + } + + if (!event) { + r = sd_event_new(&event); + if (r < 0) + return log_debug_errno(r, "Failed to allocate event loop: %m"); + } + + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + r = sd_varlink_connect_fd(&link, socket_fd); + if (r < 0) + return log_debug_errno(r, "Failed to allocate Varlink connection: %m"); + + TAKE_FD(socket_fd); + + r = sd_varlink_attach_event(link, event, /* priority= */ 0); + if (r < 0) + return log_debug_errno(r, "Failed to attach varlink connection to event loop: %m"); + + sd_varlink_set_userdata(link, userdata); + + r = sd_varlink_bind_reply(link, reply); + if (r < 0) + return log_debug_errno(r, "Failed to bind reply callback: %m"); + + r = sd_varlink_set_description(link, j); + if (r < 0) + return log_debug_errno(r, "Failed to set description: %m"); + + r = sd_varlink_set_relative_timeout(link, timeout_usec); + if (r < 0) + return log_debug_errno(r, "Failed to set relative timeout: %m"); + + if (more) + r = sd_varlink_observe(link, method, parameters); + else + r = sd_varlink_invoke(link, method, parameters); + if (r < 0) + return log_debug_errno(r, "Failed to enqueue message on Varlink connection: %m"); + + if (set_ensure_consume(&links, &varlink_hash_ops, TAKE_PTR(link)) < 0) + return log_oom_debug(); } - if (v->sentinel != POINTER_MAX) - free(v->sentinel); + size_t c = set_size(links); + + for (;;) { + if (event) { + int state = sd_event_get_state(event); + if (state < 0) + return state; + if (state == SD_EVENT_FINISHED) { + int x; + r = sd_event_get_exit_code(event, &x); + if (r < 0) + return r; + if (x != 0) + return x; + + break; + } + } + + r = varlink_finish_idle(links); + if (r < 0) + return r; + if (r > 0) + break; /* idle, we are done */ + + assert(event); - v->sentinel = s ? TAKE_PTR(s) : POINTER_MAX; - return 0; -} + r = sd_event_run(event, /* timeout= */ UINT64_MAX); + if (r < 0) + return r; + } -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( - varlink_hash_ops, - void, - trivial_hash_func, - trivial_compare_func, - sd_varlink, - sd_varlink_unref); + return (ssize_t) c; +} diff --git a/src/libsystemd/sd-varlink/varlink-util.h b/src/libsystemd/sd-varlink/varlink-util.h index dee79555ce921..d6ecb03c54533 100644 --- a/src/libsystemd/sd-varlink/varlink-util.h +++ b/src/libsystemd/sd-varlink/varlink-util.h @@ -28,6 +28,6 @@ int varlink_server_new( int varlink_check_privileged_peer(sd_varlink *vl); -int varlink_set_sentinel(sd_varlink *v, const char *error_id); - extern const struct hash_ops varlink_hash_ops; + +ssize_t varlink_execute_directory(const char *path, const char *method, sd_json_variant *parameters, bool more, usec_t timeout_usec, sd_varlink_reply_t reply, void *userdata); diff --git a/src/libudev/test-libudev.c b/src/libudev/test-libudev.c index 63c24031240e6..48d5ac4d9d960 100644 --- a/src/libudev/test-libudev.c +++ b/src/libudev/test-libudev.c @@ -1,15 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include "devnum-util.h" #include "fd-util.h" +#include "format-table.h" #include "libudev-list-internal.h" #include "log.h" #include "main-func.h" #include "libudev-util.h" +#include "options.h" #include "string-util.h" #include "tests.h" #include "version.h" @@ -404,49 +405,57 @@ static void test_list(void) { assert_se(!udev_list_entry_get_by_name(e, "ccc")); } +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n\n", program_invocation_short_name); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + return 0; +} + static int parse_args(int argc, char *argv[], const char **syspath, const char **subsystem) { - static const struct option options[] = { - { "syspath", required_argument, NULL, 'p' }, - { "subsystem", required_argument, NULL, 's' }, - { "debug", no_argument, NULL, 'd' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "monitor", no_argument, NULL, 'm' }, - {} - }; - int c; + assert(argc >= 0); + assert(argv); + assert(syspath); + assert(subsystem); + + OptionParser state = { argc, argv }; + const char *arg; - while ((c = getopt_long(argc, argv, "p:s:dhVm", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'p': - *syspath = optarg; + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + printf("%s\n", GIT_VERSION); + return 0; + + OPTION('p', "syspath", "PATH", "Syspath to test"): + *syspath = arg; break; - case 's': - *subsystem = optarg; + OPTION('s', "subsystem", "SUBSYSTEM", "Subsystem to enumerate"): + *subsystem = arg; break; - case 'd': + OPTION('d', "debug", NULL, "Enable debug logging"): log_set_max_level(LOG_DEBUG); break; - case 'h': - printf("--debug --syspath= --subsystem= --help\n"); - return 0; - - case 'V': - printf("%s\n", GIT_VERSION); - return 0; - - case 'm': + OPTION('m', "monitor", NULL, "Run monitor test"): arg_monitor = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } return 1; diff --git a/src/locale/localectl.c b/src/locale/localectl.c index 63703007ad527..e80cd96c86ee8 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -144,14 +144,10 @@ static int print_status_info(StatusInfo *i) { return table_log_add_error(r); } - r = table_print(table, NULL); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } -static int show_status(int argc, char **argv, void *userdata) { +static int verb_show_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(status_info_clear) StatusInfo info = {}; static const struct bus_properties_map map[] = { { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) }, @@ -183,7 +179,7 @@ static int show_status(int argc, char **argv, void *userdata) { return print_status_info(&info); } -static int set_locale(int argc, char **argv, void *userdata) { +static int verb_set_locale(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); @@ -211,7 +207,7 @@ static int set_locale(int argc, char **argv, void *userdata) { return 0; } -static int list_locales(int argc, char **argv, void *userdata) { +static int verb_list_locales(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **l = NULL; int r; @@ -225,7 +221,7 @@ static int list_locales(int argc, char **argv, void *userdata) { return 0; } -static int set_vconsole_keymap(int argc, char **argv, void *userdata) { +static int verb_set_vconsole_keymap(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; const char *map, *toggle_map; sd_bus *bus = ASSERT_PTR(userdata); @@ -249,7 +245,7 @@ static int set_vconsole_keymap(int argc, char **argv, void *userdata) { return 0; } -static int list_vconsole_keymaps(int argc, char **argv, void *userdata) { +static int verb_list_vconsole_keymaps(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **l = NULL; int r; @@ -264,7 +260,7 @@ static int list_vconsole_keymaps(int argc, char **argv, void *userdata) { return 0; } -static int set_x11_keymap(int argc, char **argv, void *userdata) { +static int verb_set_x11_keymap(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; const char *layout, *model, *variant, *options; sd_bus *bus = userdata; @@ -299,7 +295,7 @@ static const char* xkb_directory(void) { return cached; } -static int list_x11_keymaps(int argc, char **argv, void *userdata) { +static int verb_list_x11_keymaps(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_fclose_ FILE *f = NULL; _cleanup_strv_free_ char **list = NULL; enum { @@ -446,7 +442,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } @@ -526,17 +522,17 @@ static int parse_argv(int argc, char *argv[]) { static int localectl_main(sd_bus *bus, int argc, char *argv[]) { static const Verb verbs[] = { - { "status", VERB_ANY, 1, VERB_DEFAULT, show_status }, - { "set-locale", 2, VERB_ANY, 0, set_locale }, - { "list-locales", VERB_ANY, 1, 0, list_locales }, - { "set-keymap", 2, 3, 0, set_vconsole_keymap }, - { "list-keymaps", VERB_ANY, 1, 0, list_vconsole_keymaps }, - { "set-x11-keymap", 2, 5, 0, set_x11_keymap }, - { "list-x11-keymap-models", VERB_ANY, 1, 0, list_x11_keymaps }, - { "list-x11-keymap-layouts", VERB_ANY, 1, 0, list_x11_keymaps }, - { "list-x11-keymap-variants", VERB_ANY, 2, 0, list_x11_keymaps }, - { "list-x11-keymap-options", VERB_ANY, 1, 0, list_x11_keymaps }, - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it is created. */ + { "status", VERB_ANY, 1, VERB_DEFAULT, verb_show_status }, + { "set-locale", 2, VERB_ANY, 0, verb_set_locale }, + { "list-locales", VERB_ANY, 1, 0, verb_list_locales }, + { "set-keymap", 2, 3, 0, verb_set_vconsole_keymap }, + { "list-keymaps", VERB_ANY, 1, 0, verb_list_vconsole_keymaps }, + { "set-x11-keymap", 2, 5, 0, verb_set_x11_keymap }, + { "list-x11-keymap-models", VERB_ANY, 1, 0, verb_list_x11_keymaps }, + { "list-x11-keymap-layouts", VERB_ANY, 1, 0, verb_list_x11_keymaps }, + { "list-x11-keymap-variants", VERB_ANY, 2, 0, verb_list_x11_keymaps }, + { "list-x11-keymap-options", VERB_ANY, 1, 0, verb_list_x11_keymaps }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it has been created. */ {} }; diff --git a/src/locale/localed-util.c b/src/locale/localed-util.c index 3fe039a053061..4cddfc32d9103 100644 --- a/src/locale/localed-util.c +++ b/src/locale/localed-util.c @@ -319,7 +319,7 @@ int vconsole_write_data(Context *c) { return 0; } - r = write_vconsole_conf(AT_FDCWD, "/etc/vconsole.conf", l); + r = write_vconsole_conf(AT_FDCWD, etc_vconsole_conf(), l); if (r < 0) return r; diff --git a/src/locale/xkbcommon-util.c b/src/locale/xkbcommon-util.c index 2334587e88ccd..a55316c73a940 100644 --- a/src/locale/xkbcommon-util.c +++ b/src/locale/xkbcommon-util.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dlopen.h" + #include "dlfcn-util.h" #include "log.h" #include "string-util.h" @@ -14,13 +16,14 @@ DLSYM_PROTOTYPE(xkb_context_set_log_fn) = NULL; DLSYM_PROTOTYPE(xkb_keymap_new_from_names) = NULL; DLSYM_PROTOTYPE(xkb_keymap_unref) = NULL; -static int dlopen_xkbcommon(void) { - ELF_NOTE_DLOPEN("xkbcommon", +static int dlopen_xkbcommon(int log_level) { + SD_ELF_NOTE_DLOPEN( + "xkbcommon", "Support for keyboard locale descriptions", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libxkbcommon.so.0"); + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libxkbcommon.so.0"); return dlopen_many_sym_or_warn( - &xkbcommon_dl, "libxkbcommon.so.0", LOG_DEBUG, + &xkbcommon_dl, "libxkbcommon.so.0", log_level, DLSYM_ARG(xkb_context_new), DLSYM_ARG(xkb_context_unref), DLSYM_ARG(xkb_context_set_log_fn), @@ -54,7 +57,7 @@ int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, /* Compile keymap from RMLVO information to check out its validity */ - r = dlopen_xkbcommon(); + r = dlopen_xkbcommon(LOG_DEBUG); if (r < 0) return r; diff --git a/src/login/inhibit.c b/src/login/inhibit.c index 493f06f24e4e4..4002d3fe4ae76 100644 --- a/src/login/inhibit.c +++ b/src/login/inhibit.c @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -18,6 +17,7 @@ #include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "pidref.h" @@ -170,139 +170,100 @@ static int print_inhibitors(sd_bus *bus) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-inhibit", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] COMMAND ...\n" - "\n%sExecute a process while inhibiting shutdown/sleep/idle.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-ask-password Do not attempt interactive authorization\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " --what=WHAT Operations to inhibit, colon separated list of:\n" - " shutdown, sleep, idle, handle-power-key,\n" - " handle-suspend-key, handle-hibernate-key,\n" - " handle-lid-switch\n" - " --who=STRING A descriptive string who is inhibiting\n" - " --why=STRING A descriptive string why is being inhibited\n" - " --mode=MODE One of block, block-weak, or delay\n" - " --list List active inhibitors\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sExecute a process while inhibiting shutdown/sleep/idle.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_WHAT, - ARG_WHO, - ARG_WHY, - ARG_MODE, - ARG_LIST, - ARG_NO_ASK_PASSWORD, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_JSON, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "what", required_argument, NULL, ARG_WHAT }, - { "who", required_argument, NULL, ARG_WHO }, - { "why", required_argument, NULL, ARG_WHY }, - { "mode", required_argument, NULL, ARG_MODE }, - { "list", no_argument, NULL, ARG_LIST }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "json", required_argument, NULL, ARG_JSON }, - {} - }; - - int c, r; - +static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); + assert(remaining_args); - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; - while ((c = getopt_long(argc, argv, "+h", options, NULL)) >= 0) + OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_WHAT: - arg_what = optarg; + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; break; - case ARG_WHO: - arg_who = optarg; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case ARG_WHY: - arg_why = optarg; + OPTION_COMMON_NO_LEGEND: + arg_legend = false; break; - case ARG_MODE: - arg_mode = optarg; + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); + if (r <= 0) + return r; break; - case ARG_LIST: - arg_action = ACTION_LIST; + OPTION_LONG("what", "WHAT", + "Operations to inhibit, colon separated list " + "(shutdown, sleep, idle, handle-power-key, " + "handle-suspend-key, handle-hibernate-key, " + "handle-lid-switch)"): + arg_what = arg; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION_LONG("who", "STRING", + "A descriptive string who is inhibiting"): + arg_who = arg; break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION_LONG("why", "STRING", + "A descriptive string why is being inhibited"): + arg_why = arg; break; - case ARG_NO_LEGEND: - arg_legend = false; + OPTION_LONG("mode", "MODE", "One of block, block-weak, or delay"): + arg_mode = arg; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; - + OPTION_LONG("list", NULL, "List active inhibitors"): + arg_action = ACTION_LIST; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (arg_action == ACTION_INHIBIT && optind == argc) - arg_action = ACTION_LIST; + char **args = option_parser_get_args(&state); - else if (arg_action == ACTION_INHIBIT && optind >= argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Missing command line to execute."); + if (arg_action == ACTION_INHIBIT && strv_isempty(args)) + arg_action = ACTION_LIST; + *remaining_args = args; return 1; } @@ -312,7 +273,8 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -337,7 +299,7 @@ static int run(int argc, char *argv[]) { arg_what = "idle:sleep:shutdown"; if (!arg_who) { - w = strv_join(argv + optind, " "); + w = strv_join(args, " "); if (!w) return log_oom(); @@ -354,7 +316,7 @@ static int run(int argc, char *argv[]) { if (fd < 0) return log_error_errno(fd, "Failed to inhibit: %s", bus_error_message(&error, fd)); - arguments = strv_copy(argv + optind); + arguments = strv_copy(args); if (!arguments) return log_oom(); @@ -370,7 +332,7 @@ static int run(int argc, char *argv[]) { _exit(EXIT_FAILURE); } - return pidref_wait_for_terminate_and_check(argv[optind], &pidref, WAIT_LOG_ABNORMAL); + return pidref_wait_for_terminate_and_check(args[0], &pidref, WAIT_LOG_ABNORMAL); } } diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 1ff650c2adbfd..cdffd79d8ca12 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -266,7 +266,7 @@ static int list_sessions_table_add_fallback(Table *table, sd_bus_message *reply, return 0; } -static int list_sessions(int argc, char *argv[], void *userdata) { +static int verb_list_sessions(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; @@ -308,7 +308,7 @@ static int list_sessions(int argc, char *argv[], void *userdata) { return list_table_print(table, "sessions"); } -static int list_users(int argc, char *argv[], void *userdata) { +static int verb_list_users(int argc, char *argv[], uintptr_t _data, void *userdata) { static const struct bus_properties_map property_map[] = { { "Linger", "b", NULL, offsetof(UserStatusInfo, linger) }, @@ -384,7 +384,7 @@ static int list_users(int argc, char *argv[], void *userdata) { return list_table_print(table, "users"); } -static int list_seats(int argc, char *argv[], void *userdata) { +static int verb_list_seats(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(table_unrefp) Table *table = NULL; @@ -716,9 +716,9 @@ static int print_session_status_info(sd_bus *bus, const char *path) { /* We don't use the table to show the header, in order to make the width of the column stable. */ printf("%s%s - %s (" UID_FMT ")%s\n", ansi_highlight(), i.id, i.name, i.uid, ansi_normal()); - r = table_print(table, NULL); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; if (i.scope) { show_unit_cgroup(bus, i.scope, i.leader, /* prefix= */ strrepa(" ", STRLEN("Display: "))); @@ -821,9 +821,9 @@ static int print_user_status_info(sd_bus *bus, const char *path) { printf("%s%s (" UID_FMT ")%s\n", ansi_highlight(), i.name, i.uid, ansi_normal()); - r = table_print(table, NULL); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; if (i.slice) { show_unit_cgroup(bus, i.slice, /* leader= */ 0, /* prefix= */ strrepa(" ", STRLEN("Sessions: "))); @@ -896,9 +896,9 @@ static int print_seat_status_info(sd_bus *bus, const char *path) { printf("%s%s%s\n", ansi_highlight(), i.id, ansi_normal()); - r = table_print(table, NULL); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; if (arg_transport == BUS_TRANSPORT_LOCAL) { unsigned c = MAX(LESS_BY(columns(), 21U), 10U); @@ -1038,7 +1038,7 @@ static int get_bus_path_by_id( return strdup_to(ret, path); } -static int show_session(int argc, char *argv[], void *userdata) { +static int verb_show_session(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); bool properties; int r; @@ -1084,7 +1084,7 @@ static int show_session(int argc, char *argv[], void *userdata) { return 0; } -static int show_user(int argc, char *argv[], void *userdata) { +static int verb_show_user(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); bool properties; int r; @@ -1135,7 +1135,7 @@ static int show_user(int argc, char *argv[], void *userdata) { return 0; } -static int show_seat(int argc, char *argv[], void *userdata) { +static int verb_show_seat(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); bool properties; int r; @@ -1181,7 +1181,7 @@ static int show_seat(int argc, char *argv[], void *userdata) { return 0; } -static int activate(int argc, char *argv[], void *userdata) { +static int verb_activate(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1224,7 +1224,7 @@ static int activate(int argc, char *argv[], void *userdata) { return 0; } -static int kill_session(int argc, char *argv[], void *userdata) { +static int verb_kill_session(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1250,7 +1250,7 @@ static int kill_session(int argc, char *argv[], void *userdata) { return 0; } -static int enable_linger(int argc, char *argv[], void *userdata) { +static int verb_enable_linger(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); char* short_argv[3]; @@ -1298,7 +1298,7 @@ static int enable_linger(int argc, char *argv[], void *userdata) { return 0; } -static int terminate_user(int argc, char *argv[], void *userdata) { +static int verb_terminate_user(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1328,7 +1328,7 @@ static int terminate_user(int argc, char *argv[], void *userdata) { return 0; } -static int kill_user(int argc, char *argv[], void *userdata) { +static int verb_kill_user(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1366,7 +1366,7 @@ static int kill_user(int argc, char *argv[], void *userdata) { return 0; } -static int attach(int argc, char *argv[], void *userdata) { +static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1390,7 +1390,7 @@ static int attach(int argc, char *argv[], void *userdata) { return 0; } -static int flush_devices(int argc, char *argv[], void *userdata) { +static int verb_flush_devices(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1406,7 +1406,7 @@ static int flush_devices(int argc, char *argv[], void *userdata) { return 0; } -static int lock_sessions(int argc, char *argv[], void *userdata) { +static int verb_lock_sessions(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1427,7 +1427,7 @@ static int lock_sessions(int argc, char *argv[], void *userdata) { return 0; } -static int terminate_seat(int argc, char *argv[], void *userdata) { +static int verb_terminate_seat(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1446,7 +1446,7 @@ static int terminate_seat(int argc, char *argv[], void *userdata) { return 0; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -1519,6 +1519,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, @@ -1560,7 +1564,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -1669,30 +1673,30 @@ static int parse_argv(int argc, char *argv[]) { static int loginctl_main(int argc, char *argv[], sd_bus *bus) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "list-sessions", VERB_ANY, 1, VERB_DEFAULT, list_sessions }, - { "session-status", VERB_ANY, VERB_ANY, 0, show_session }, - { "show-session", VERB_ANY, VERB_ANY, 0, show_session }, - { "activate", VERB_ANY, 2, 0, activate }, - { "lock-session", VERB_ANY, VERB_ANY, 0, activate }, - { "unlock-session", VERB_ANY, VERB_ANY, 0, activate }, - { "lock-sessions", VERB_ANY, 1, 0, lock_sessions }, - { "unlock-sessions", VERB_ANY, 1, 0, lock_sessions }, - { "terminate-session", 2, VERB_ANY, 0, activate }, - { "kill-session", 2, VERB_ANY, 0, kill_session }, - { "list-users", VERB_ANY, 1, 0, list_users }, - { "user-status", VERB_ANY, VERB_ANY, 0, show_user }, - { "show-user", VERB_ANY, VERB_ANY, 0, show_user }, - { "enable-linger", VERB_ANY, VERB_ANY, 0, enable_linger }, - { "disable-linger", VERB_ANY, VERB_ANY, 0, enable_linger }, - { "terminate-user", 2, VERB_ANY, 0, terminate_user }, - { "kill-user", 2, VERB_ANY, 0, kill_user }, - { "list-seats", VERB_ANY, 1, 0, list_seats }, - { "seat-status", VERB_ANY, VERB_ANY, 0, show_seat }, - { "show-seat", VERB_ANY, VERB_ANY, 0, show_seat }, - { "attach", 3, VERB_ANY, 0, attach }, - { "flush-devices", VERB_ANY, 1, 0, flush_devices }, - { "terminate-seat", 2, VERB_ANY, 0, terminate_seat }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "list-sessions", VERB_ANY, 1, VERB_DEFAULT, verb_list_sessions }, + { "session-status", VERB_ANY, VERB_ANY, 0, verb_show_session }, + { "show-session", VERB_ANY, VERB_ANY, 0, verb_show_session }, + { "activate", VERB_ANY, 2, 0, verb_activate }, + { "lock-session", VERB_ANY, VERB_ANY, 0, verb_activate }, + { "unlock-session", VERB_ANY, VERB_ANY, 0, verb_activate }, + { "lock-sessions", VERB_ANY, 1, 0, verb_lock_sessions }, + { "unlock-sessions", VERB_ANY, 1, 0, verb_lock_sessions }, + { "terminate-session", 2, VERB_ANY, 0, verb_activate }, + { "kill-session", 2, VERB_ANY, 0, verb_kill_session }, + { "list-users", VERB_ANY, 1, 0, verb_list_users }, + { "user-status", VERB_ANY, VERB_ANY, 0, verb_show_user }, + { "show-user", VERB_ANY, VERB_ANY, 0, verb_show_user }, + { "enable-linger", VERB_ANY, VERB_ANY, 0, verb_enable_linger }, + { "disable-linger", VERB_ANY, VERB_ANY, 0, verb_enable_linger }, + { "terminate-user", 2, VERB_ANY, 0, verb_terminate_user }, + { "kill-user", 2, VERB_ANY, 0, verb_kill_user }, + { "list-seats", VERB_ANY, 1, 0, verb_list_seats }, + { "seat-status", VERB_ANY, VERB_ANY, 0, verb_show_seat }, + { "show-seat", VERB_ANY, VERB_ANY, 0, verb_show_seat }, + { "attach", 3, VERB_ANY, 0, verb_attach }, + { "flush-devices", VERB_ANY, 1, 0, verb_flush_devices }, + { "terminate-seat", 2, VERB_ANY, 0, verb_terminate_seat }, {} }; diff --git a/src/login/logind-action.c b/src/login/logind-action.c index 843bb1a5a085c..48f2031fc47c7 100644 --- a/src/login/logind-action.c +++ b/src/login/logind-action.c @@ -222,10 +222,6 @@ static int handle_action_execute( assert(m); assert(!IN_SET(handle, HANDLE_IGNORE, HANDLE_LOCK, HANDLE_SLEEP)); - if (handle == HANDLE_KEXEC && access(KEXEC, X_OK) < 0) - return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Requested %s operation not supported, ignoring.", handle_action_to_string(handle)); - if (m->delayed_action) return log_debug_errno(SYNTHETIC_ERRNO(EALREADY), "Action %s already in progress, ignoring requested %s operation.", diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index b50e69809fea0..14208bc1496e9 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -45,6 +45,7 @@ #include "logind-seat.h" #include "logind-seat-dbus.h" #include "logind-session-dbus.h" +#include "logind-shutdown.h" #include "logind-user.h" #include "logind-user-dbus.h" #include "logind-utmp.h" @@ -58,6 +59,7 @@ #include "signal-util.h" #include "sleep-config.h" #include "stdio-util.h" +#include "string-util.h" #include "strv.h" #include "terminal-util.h" #include "tmpfile-util.h" @@ -76,10 +78,6 @@ */ #define WALL_MESSAGE_MAX 4096U -#define SHUTDOWN_SCHEDULE_FILE "/run/systemd/shutdown/scheduled" - -static void reset_scheduled_shutdown(Manager *m); - static int get_sender_session( Manager *m, sd_bus_message *message, @@ -93,6 +91,7 @@ static int get_sender_session( int r; assert(m); + assert(ret); /* Acquire the sender's session. This first checks if the sending process is inside a session itself, * and returns that. If not and 'consult_display' is true, this returns the display session of the @@ -168,6 +167,8 @@ static int get_sender_user(Manager *m, sd_bus_message *message, sd_bus_error *er User *user; int r; + assert(ret); + /* Note that we get the owner UID of the session, not the actual client UID here! */ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_OWNER_UID|SD_BUS_CREDS_AUGMENT, &creds); if (r < 0) @@ -1154,7 +1155,7 @@ static int manager_create_session_by_bus( if (isempty(desktop)) desktop = NULL; else { - if (!string_is_safe(desktop)) + if (!string_is_safe(desktop, STRING_ALLOW_GLOBS)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid desktop string %s", desktop); } @@ -1866,24 +1867,6 @@ static int method_flush_devices(sd_bus_message *message, void *userdata, sd_bus_ return sd_bus_reply_method_return(message, NULL); } -static int have_multiple_sessions( - Manager *m, - uid_t uid) { - - Session *session; - - assert(m); - - /* Check for other users' sessions. Greeter sessions do not - * count, and non-login sessions do not count either. */ - HASHMAP_FOREACH(session, m->sessions) - if (SESSION_CLASS_IS_INHIBITOR_LIKE(session->class) && - session->user->user_record->uid != uid) - return true; - - return false; -} - static int bus_manager_log_shutdown( Manager *m, const HandleActionData *a) { @@ -2189,121 +2172,6 @@ int bus_manager_shutdown_or_sleep_now_or_later( return r; } -static int verify_shutdown_creds( - Manager *m, - sd_bus_message *message, - const HandleActionData *a, - uint64_t flags, - sd_bus_error *error) { - - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - bool multiple_sessions, blocked, interactive; - _unused_ bool error_or_denial = false; - Inhibitor *offending = NULL; - uid_t uid; - int r; - - assert(m); - assert(a); - assert(message); - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_euid(creds, &uid); - if (r < 0) - return r; - - r = have_multiple_sessions(m, uid); - if (r < 0) - return r; - - multiple_sessions = r > 0; - blocked = manager_is_inhibited(m, a->inhibit_what, NULL, /* flags= */ 0, uid, &offending); - interactive = flags & SD_LOGIND_INTERACTIVE; - - if (multiple_sessions) { - r = bus_verify_polkit_async_full( - message, - a->polkit_action_multiple_sessions, - /* details= */ NULL, - /* good_user= */ UID_INVALID, - interactive ? POLKIT_ALLOW_INTERACTIVE : 0, - &m->polkit_registry, - error); - if (r < 0) { - /* If we get -EBUSY, it means a polkit decision was made, but not for - * this action in particular. Assuming we are blocked on inhibitors, - * ignore that error and allow the decision to be revealed below. */ - if (blocked && r == -EBUSY) - error_or_denial = true; - else - return r; - } - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - } - - if (blocked) { - PolkitFlags polkit_flags = 0; - - /* With a strong inhibitor, if the skip flag is not set, reject outright. - * With a weak inhibitor, if root is asking and the root flag is set, reject outright. - * All else, check polkit first. */ - if (!FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS) && - (offending->mode != INHIBIT_BLOCK_WEAK || - (uid == 0 && FLAGS_SET(flags, SD_LOGIND_ROOT_CHECK_INHIBITORS)))) - return sd_bus_error_set(error, BUS_ERROR_BLOCKED_BY_INHIBITOR_LOCK, - "Operation denied due to active block inhibitor"); - - /* We want to always ask here, even for root, to only allow bypassing if explicitly allowed - * by polkit, unless a weak blocker is used, in which case it will be authorized. */ - if (offending->mode != INHIBIT_BLOCK_WEAK) - polkit_flags |= POLKIT_ALWAYS_QUERY; - - if (interactive) - polkit_flags |= POLKIT_ALLOW_INTERACTIVE; - - r = bus_verify_polkit_async_full( - message, - a->polkit_action_ignore_inhibit, - /* details= */ NULL, - /* good_user= */ UID_INVALID, - polkit_flags, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - } - - if (!multiple_sessions && !blocked) { - r = bus_verify_polkit_async_full( - message, - a->polkit_action, - /* details= */ NULL, - /* good_user= */ UID_INVALID, - interactive ? POLKIT_ALLOW_INTERACTIVE : 0, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - } - - /* If error_or_denial was set above, it means that a polkit denial or - * error was deferred for a future call to bus_verify_polkit_async_full() - * to catch. In any case, it also means that the payload guarded by - * these polkit calls should never be executed, and hence we should - * never reach this point. */ - assert(!error_or_denial); - - return 0; -} - static int setup_wall_message_timer(Manager *m, sd_bus_message* message) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; int r; @@ -2442,10 +2310,17 @@ static int method_do_shutdown_or_sleep( } else if (!a) assert_se(a = handle_action_lookup(action)); - r = verify_shutdown_creds(m, message, a, flags, error); + r = manager_verify_shutdown_creds(m, message, /* link= */ NULL, a, flags, error); if (r != 0) return r; + { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + + (void) bus_query_sender_pidref(message, &pidref); + log_shutdown_caller(&pidref, handle_action_to_string(a->handle)); + } + if (m->delayed_action) return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS, "Action %s already in progress, refusing requested %s operation.", @@ -2454,7 +2329,7 @@ static int method_do_shutdown_or_sleep( /* reset case we're shorting a scheduled shutdown */ m->unlink_nologin = false; - reset_scheduled_shutdown(m); + manager_reset_scheduled_shutdown(m); m->scheduled_shutdown_timeout = 0; m->scheduled_shutdown_action = action; @@ -2568,29 +2443,6 @@ static usec_t nologin_timeout_usec(usec_t elapse) { return LESS_BY(elapse, 5 * USEC_PER_MINUTE); } -static void reset_scheduled_shutdown(Manager *m) { - assert(m); - - m->scheduled_shutdown_timeout_source = sd_event_source_disable_unref(m->scheduled_shutdown_timeout_source); - m->wall_message_timeout_source = sd_event_source_disable_unref(m->wall_message_timeout_source); - m->nologin_timeout_source = sd_event_source_disable_unref(m->nologin_timeout_source); - - m->scheduled_shutdown_action = _HANDLE_ACTION_INVALID; - m->scheduled_shutdown_timeout = USEC_INFINITY; - m->scheduled_shutdown_uid = UID_INVALID; - m->scheduled_shutdown_tty = mfree(m->scheduled_shutdown_tty); - m->shutdown_dry_run = false; - - if (m->unlink_nologin) { - (void) unlink_or_warn("/run/nologin"); - m->unlink_nologin = false; - } - - (void) unlink(SHUTDOWN_SCHEDULE_FILE); - - manager_send_changed(m, "ScheduledShutdown"); -} - static int update_schedule_file(Manager *m) { _cleanup_(unlink_and_freep) char *temp_path = NULL; _cleanup_fclose_ FILE *f = NULL; @@ -2669,7 +2521,7 @@ static int manager_scheduled_shutdown_handler( bus_manager_log_shutdown(m, a); log_info("Running in dry run, suppressing action."); - reset_scheduled_shutdown(m); + manager_reset_scheduled_shutdown(m); return 0; } @@ -2683,7 +2535,7 @@ static int manager_scheduled_shutdown_handler( return 0; error: - reset_scheduled_shutdown(m); + manager_reset_scheduled_shutdown(m); return r; } @@ -2738,7 +2590,7 @@ void manager_load_scheduled_shutdown(Manager *m) { "TTY", &tty); /* reset will delete the file */ - reset_scheduled_shutdown(m); + manager_reset_scheduled_shutdown(m); if (r == -ENOENT) return; @@ -2784,7 +2636,7 @@ void manager_load_scheduled_shutdown(Manager *m) { r = manager_setup_shutdown_timers(m); if (r < 0) - return reset_scheduled_shutdown(m); + return manager_reset_scheduled_shutdown(m); (void) manager_setup_wall_message_timer(m); (void) update_schedule_file(m); @@ -2819,7 +2671,7 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ assert_se(a = handle_action_lookup(handle)); assert(a->polkit_action); - r = verify_shutdown_creds(m, message, a, 0, error); + r = manager_verify_shutdown_creds(m, message, /* link= */ NULL, a, 0, error); if (r != 0) return r; @@ -2853,7 +2705,7 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ r = update_schedule_file(m); if (r < 0) { - reset_scheduled_shutdown(m); + manager_reset_scheduled_shutdown(m); return r; } @@ -2913,7 +2765,7 @@ static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userd } cancel_delayed_action(m); - reset_scheduled_shutdown(m); + manager_reset_scheduled_shutdown(m); return sd_bus_reply_method_return(message, "b", true); } @@ -2969,7 +2821,7 @@ static int method_can_shutdown_or_sleep( if (r < 0) return r; - r = have_multiple_sessions(m, uid); + r = manager_have_multiple_sessions(m, uid); if (r < 0) return r; @@ -3731,6 +3583,46 @@ static int property_get_boot_loader_entries( return sd_bus_message_close_container(reply); } +static int wall_message_validate(const char *wall_message, sd_bus_error *error) { + if (strlen(wall_message) > WALL_MESSAGE_MAX) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Wall message too long, maximum permitted length is %u characters.", + WALL_MESSAGE_MAX); + + if (string_has_cc(wall_message, /* ok= */ "\n\t")) + return sd_bus_error_set(error, + SD_BUS_ERROR_INVALID_ARGS, + "Wall message contains control characters, refusing."); + + return 0; +} + +static int property_set_wall_message( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *value, + void *userdata, + sd_bus_error *error) { + + char **p = ASSERT_PTR(userdata); + const char *s; + int r; + + assert(value); + + r = sd_bus_message_read(value, "s", &s); + if (r < 0) + return r; + + r = wall_message_validate(s, error); + if (r < 0) + return r; + + return free_and_strdup_warn(p, empty_to_null(s)); +} + static int method_set_wall_message( sd_bus_message *message, void *userdata, @@ -3747,10 +3639,9 @@ static int method_set_wall_message( if (r < 0) return r; - if (strlen(wall_message) > WALL_MESSAGE_MAX) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Wall message too long, maximum permitted length is %u characters.", - WALL_MESSAGE_MAX); + r = wall_message_validate(wall_message, error); + if (r < 0) + return r; /* Short-circuit the operation if the desired state is already in place, to * avoid an unnecessary polkit permission check. */ @@ -3880,7 +3771,7 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error if (asprintf(&id, "%" PRIu64, ++m->inhibit_counter) < 0) return -ENOMEM; - } while (hashmap_get(m->inhibitors, id)); + } while (hashmap_contains(m->inhibitors, id)); _cleanup_(inhibitor_freep) Inhibitor *i = NULL; r = manager_add_inhibitor(m, id, &i); @@ -3913,7 +3804,7 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_WRITABLE_PROPERTY("EnableWallMessages", "b", bus_property_get_bool, bus_property_set_bool, offsetof(Manager, wall_messages), 0), - SD_BUS_WRITABLE_PROPERTY("WallMessage", "s", NULL, NULL, offsetof(Manager, wall_message), 0), + SD_BUS_WRITABLE_PROPERTY("WallMessage", "s", NULL, property_set_wall_message, offsetof(Manager, wall_message), 0), SD_BUS_PROPERTY("NAutoVTs", "u", NULL, offsetof(Manager, n_autovts), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("KillOnlyUsers", "as", NULL, offsetof(Manager, kill_only_users), SD_BUS_VTABLE_PROPERTY_CONST), diff --git a/src/login/logind-session-device.c b/src/login/logind-session-device.c index 7129f823e8aad..137db8e93e1d8 100644 --- a/src/login/logind-session-device.c +++ b/src/login/logind-session-device.c @@ -105,9 +105,8 @@ static void sd_eviocrevoke(int fd) { if (errno == EINVAL) { log_warning_errno(errno, "Kernel does not support evdev-revocation, continuing without revoking device access: %m"); warned = true; - } else if (errno != ENODEV) { + } else if (errno != ENODEV) log_warning_errno(errno, "Failed to revoke evdev device, continuing without revoking device access: %m"); - } } } diff --git a/src/login/logind-shutdown.c b/src/login/logind-shutdown.c new file mode 100644 index 0000000000000..064ebf8e2ff8f --- /dev/null +++ b/src/login/logind-shutdown.c @@ -0,0 +1,250 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-bus.h" +#include "sd-event.h" +#include "sd-varlink.h" + +#include "bus-common-errors.h" +#include "bus-polkit.h" +#include "cgroup-util.h" +#include "format-util.h" +#include "fs-util.h" +#include "hashmap.h" +#include "log.h" +#include "login-util.h" +#include "logind.h" +#include "logind-dbus.h" +#include "logind-inhibit.h" +#include "logind-session.h" +#include "logind-shutdown.h" +#include "logind-user.h" +#include "pidref.h" +#include "process-util.h" +#include "user-record.h" + +int manager_have_multiple_sessions( + Manager *m, + uid_t uid) { + + Session *session; + + assert(m); + + /* Check for other users' sessions. Greeter sessions do not + * count, and non-login sessions do not count either. */ + HASHMAP_FOREACH(session, m->sessions) + if (SESSION_CLASS_IS_INHIBITOR_LIKE(session->class) && + session->user->user_record->uid != uid) + return true; + + return false; +} + +void log_shutdown_caller(const PidRef *caller, const char *method) { + _cleanup_free_ char *comm = NULL, *unit = NULL; + + assert(method); + + if (!pidref_is_set(caller)) { + return log_notice("%s requested from unknown client PID...", method); + } + + (void) pidref_get_comm(caller, &comm); + (void) cg_pidref_get_unit(caller, &unit); + + log_notice("%s requested from client PID " PID_FMT "%s%s%s%s%s%s...", + method, caller->pid, + comm ? " ('" : "", strempty(comm), comm ? "')" : "", + unit ? " (unit " : "", strempty(unit), unit ? ")" : ""); +} + +int manager_verify_shutdown_creds( + Manager *m, + sd_bus_message *message, + sd_varlink *link, + const HandleActionData *a, + uint64_t flags, + sd_bus_error *error) { + + bool multiple_sessions, blocked, interactive; + _unused_ bool error_or_denial = false; + Inhibitor *offending = NULL; + uid_t uid; + int r; + + assert(m); + assert(a); + assert(!!message != !!link); /* exactly one transport */ + assert(!link || !error); /* varlink doesn't use sd_bus_error */ + + if (message) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_euid(creds, &uid); + if (r < 0) + return r; + } else { + r = sd_varlink_get_peer_uid(link, &uid); + if (r < 0) + return r; + } + + r = manager_have_multiple_sessions(m, uid); + if (r < 0) + return r; + + multiple_sessions = r > 0; + blocked = manager_is_inhibited(m, a->inhibit_what, NULL, /* flags= */ 0, uid, &offending); + interactive = flags & SD_LOGIND_INTERACTIVE; + + if (multiple_sessions) { + if (message) + r = bus_verify_polkit_async_full( + message, + a->polkit_action_multiple_sessions, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, + &m->polkit_registry, + error); + else + r = varlink_verify_polkit_async_full( + link, + m->bus, + a->polkit_action_multiple_sessions, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, + &m->polkit_registry); + + if (r < 0) { + /* If we get -EBUSY, it means a polkit decision was made, but not for + * this action in particular. Assuming we are blocked on inhibitors, + * ignore that error and allow the decision to be revealed below. */ + if (blocked && r == -EBUSY) + error_or_denial = true; + else + return r; + } + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + } + + if (blocked) { + PolkitFlags polkit_flags = 0; + + /* With a strong inhibitor, if the skip flag is not set, reject outright. + * With a weak inhibitor, if root is asking and the root flag is set, reject outright. + * All else, check polkit first. */ + if (!FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS) && + (offending->mode != INHIBIT_BLOCK_WEAK || + (uid == 0 && FLAGS_SET(flags, SD_LOGIND_ROOT_CHECK_INHIBITORS)))) { + if (link) + return sd_varlink_errorbo( + link, + "io.systemd.Shutdown.BlockedByInhibitor", + SD_JSON_BUILD_PAIR_STRING("who", offending->who), + SD_JSON_BUILD_PAIR_STRING("why", offending->why)); + if (error) + return sd_bus_error_set(error, BUS_ERROR_BLOCKED_BY_INHIBITOR_LOCK, + "Operation denied due to active block inhibitor"); + return -EACCES; + } + + /* We want to always ask here, even for root, to only allow bypassing if explicitly allowed + * by polkit, unless a weak blocker is used, in which case it will be authorized. */ + if (offending->mode != INHIBIT_BLOCK_WEAK) + polkit_flags |= POLKIT_ALWAYS_QUERY; + + if (interactive) + polkit_flags |= POLKIT_ALLOW_INTERACTIVE; + + if (message) + r = bus_verify_polkit_async_full( + message, + a->polkit_action_ignore_inhibit, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + polkit_flags, + &m->polkit_registry, + error); + else + r = varlink_verify_polkit_async_full( + link, + m->bus, + a->polkit_action_ignore_inhibit, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + polkit_flags, + &m->polkit_registry); + + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + } + + if (!multiple_sessions && !blocked) { + if (message) + r = bus_verify_polkit_async_full( + message, + a->polkit_action, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, + &m->polkit_registry, + error); + else + r = varlink_verify_polkit_async_full( + link, + m->bus, + a->polkit_action, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, + &m->polkit_registry); + + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + } + + /* If error_or_denial was set above, it means that a polkit denial or + * error was deferred for a future call to bus_verify_polkit_async_full() + * to catch. In any case, it also means that the payload guarded by + * these polkit calls should never be executed, and hence we should + * never reach this point. */ + assert(!error_or_denial); + + return 0; +} + +void manager_reset_scheduled_shutdown(Manager *m) { + assert(m); + + m->scheduled_shutdown_timeout_source = sd_event_source_disable_unref(m->scheduled_shutdown_timeout_source); + m->wall_message_timeout_source = sd_event_source_disable_unref(m->wall_message_timeout_source); + m->nologin_timeout_source = sd_event_source_disable_unref(m->nologin_timeout_source); + + m->scheduled_shutdown_action = _HANDLE_ACTION_INVALID; + m->scheduled_shutdown_timeout = USEC_INFINITY; + m->scheduled_shutdown_uid = UID_INVALID; + m->scheduled_shutdown_tty = mfree(m->scheduled_shutdown_tty); + m->shutdown_dry_run = false; + + if (m->unlink_nologin) { + (void) unlink_or_warn("/run/nologin"); + m->unlink_nologin = false; + } + + (void) unlink(SHUTDOWN_SCHEDULE_FILE); + + manager_send_changed(m, "ScheduledShutdown"); +} diff --git a/src/login/logind-shutdown.h b/src/login/logind-shutdown.h new file mode 100644 index 0000000000000..e6bcc8c4f5d4e --- /dev/null +++ b/src/login/logind-shutdown.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "logind-forward.h" + +#define SHUTDOWN_SCHEDULE_FILE "/run/systemd/shutdown/scheduled" + +int manager_have_multiple_sessions(Manager *m, uid_t uid); + +void log_shutdown_caller(const PidRef *caller, const char *method); + +/* manager_verify_shutdown_creds() takes *either* a "message" or "link" depending on if it is used + * to validate a D-Bus or Varlink shutdown request. When varlink is used the sd_bus_error *error + * must be NULL */ +int manager_verify_shutdown_creds(Manager *m, sd_bus_message *message, sd_varlink *link, const HandleActionData *a, uint64_t flags, sd_bus_error *error); + +void manager_reset_scheduled_shutdown(Manager *m); diff --git a/src/login/logind-varlink.c b/src/login/logind-varlink.c index a1fdac01c907b..56b02b4eb2eee 100644 --- a/src/login/logind-varlink.c +++ b/src/login/logind-varlink.c @@ -4,15 +4,19 @@ #include "sd-event.h" #include "alloc-util.h" +#include "bus-error.h" +#include "bus-polkit.h" #include "cgroup-util.h" #include "fd-util.h" #include "format-util.h" #include "hashmap.h" #include "json-util.h" -#include "logind-session.h" +#include "login-util.h" #include "logind.h" #include "logind-dbus.h" #include "logind-seat.h" +#include "logind-session.h" +#include "logind-shutdown.h" #include "logind-user.h" #include "logind-varlink.h" #include "strv.h" @@ -20,6 +24,7 @@ #include "user-record.h" #include "user-util.h" #include "varlink-io.systemd.Login.h" +#include "varlink-io.systemd.Shutdown.h" #include "varlink-io.systemd.service.h" #include "varlink-util.h" @@ -125,8 +130,8 @@ int session_send_create_reply_varlink(Session *s, const sd_bus_error *error) { SD_JSON_BUILD_PAIR_UNSIGNED("UID", s->user->user_record->uid), SD_JSON_BUILD_PAIR_CONDITION(!!s->seat, "Seat", SD_JSON_BUILD_STRING(s->seat ? s->seat->id : NULL)), SD_JSON_BUILD_PAIR_CONDITION(s->vtnr > 0, "VTNr", SD_JSON_BUILD_UNSIGNED(s->vtnr)), - SD_JSON_BUILD_PAIR("Class", JSON_BUILD_STRING_UNDERSCORIFY(session_class_to_string(s->class))), - SD_JSON_BUILD_PAIR("Type", JSON_BUILD_STRING_UNDERSCORIFY(session_type_to_string(s->type)))); + JSON_BUILD_PAIR_ENUM("Class", session_class_to_string(s->class)), + JSON_BUILD_PAIR_ENUM("Type", session_type_to_string(s->type))); } static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_session_class, SessionClass, session_class_from_string); @@ -308,7 +313,7 @@ static int vl_method_release_session(sd_varlink *link, sd_json_variant *paramete } p; static const sd_json_dispatch_field dispatch_table[] = { - { "Id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, id), SD_JSON_MANDATORY }, + { "Id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, id), 0 }, {} }; @@ -336,6 +341,101 @@ static int vl_method_release_session(sd_varlink *link, sd_json_variant *paramete return sd_varlink_reply(link, NULL); } +static int setup_wall_message_timer(Manager *m, sd_varlink *link) { + uid_t uid = UID_INVALID; + int r; + + (void) sd_varlink_get_peer_uid(link, &uid); + m->scheduled_shutdown_uid = uid; + + _cleanup_free_ char *tty = NULL; + pid_t pid = 0; + r = sd_varlink_get_peer_pid(link, &pid); + if (r >= 0) + (void) get_ctty(pid, /* ret_devnr= */ NULL, &tty); + + r = free_and_strdup_warn(&m->scheduled_shutdown_tty, tty); + if (r < 0) + return log_oom(); + + return manager_setup_wall_message_timer(m); +} + +static int manager_do_shutdown_action(sd_varlink *link, sd_json_variant *parameters, HandleAction action) { + Manager *m = ASSERT_PTR(sd_varlink_get_userdata(link)); + int skip_inhibitors = -1; + int r; + + static const sd_json_dispatch_field dispatch_table[] = { + { "skipInhibitors", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, 0, 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &skip_inhibitors); + if (r != 0) + return r; + + uint64_t flags = skip_inhibitors > 0 ? SD_LOGIND_SKIP_INHIBITORS : 0; + + const HandleActionData *a = handle_action_lookup(action); + assert(a); + + r = manager_verify_shutdown_creds(m, /* message= */ NULL, link, a, flags, /* error= */ NULL); + if (r != 0) + return r; + + { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + + (void) varlink_get_peer_pidref(link, &pidref); + log_shutdown_caller(&pidref, handle_action_to_string(action)); + } + + if (m->delayed_action) + return sd_varlink_error(link, "io.systemd.Shutdown.AlreadyInProgress", /* parameters= */ NULL); + + /* Reset in case we're short-circuiting a scheduled shutdown */ + m->unlink_nologin = false; + manager_reset_scheduled_shutdown(m); + + m->scheduled_shutdown_timeout = 0; + m->scheduled_shutdown_action = action; + + (void) setup_wall_message_timer(m, link); + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = bus_manager_shutdown_or_sleep_now_or_later(m, a, &error); + if (r < 0) { + log_warning_errno(r, "Failed to execute %s: %s", + handle_action_to_string(action), + bus_error_message(&error, r)); + return sd_varlink_error_errno(link, r); + } + + return sd_varlink_reply(link, NULL); +} + +static int vl_method_power_off(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_shutdown_action(link, parameters, HANDLE_POWEROFF); +} + +static int vl_method_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_shutdown_action(link, parameters, HANDLE_REBOOT); +} + +static int vl_method_halt(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_shutdown_action(link, parameters, HANDLE_HALT); +} + +static int vl_method_kexec(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_shutdown_action(link, parameters, HANDLE_KEXEC); +} + +static int vl_method_soft_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_shutdown_action(link, parameters, HANDLE_SOFT_REBOOT); +} + int manager_varlink_init(Manager *m, int fd) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; _unused_ _cleanup_close_ int fd_close = fd; @@ -358,14 +458,20 @@ int manager_varlink_init(Manager *m, int fd) { r = sd_varlink_server_add_interface_many( s, &vl_interface_io_systemd_Login, + &vl_interface_io_systemd_Shutdown, &vl_interface_io_systemd_service); if (r < 0) - return log_error_errno(r, "Failed to add Login interface to varlink server: %m"); + return log_error_errno(r, "Failed to add varlink interfaces: %m"); r = sd_varlink_server_bind_method_many( s, "io.systemd.Login.CreateSession", vl_method_create_session, "io.systemd.Login.ReleaseSession", vl_method_release_session, + "io.systemd.Shutdown.PowerOff", vl_method_power_off, + "io.systemd.Shutdown.Reboot", vl_method_reboot, + "io.systemd.Shutdown.Halt", vl_method_halt, + "io.systemd.Shutdown.KExec", vl_method_kexec, + "io.systemd.Shutdown.SoftReboot", vl_method_soft_reboot, "io.systemd.service.Ping", varlink_method_ping, "io.systemd.service.SetLogLevel", varlink_method_set_log_level, "io.systemd.service.GetEnvironment", varlink_method_get_environment); diff --git a/src/login/meson.build b/src/login/meson.build index d6654ff5ced50..390960d5f6c10 100644 --- a/src/login/meson.build +++ b/src/login/meson.build @@ -21,6 +21,7 @@ systemd_logind_extract_sources = files( 'logind-session-dbus.c', 'logind-session-device.c', 'logind-session.c', + 'logind-shutdown.c', 'logind-user-dbus.c', 'logind-user.c', 'logind-utmp.c', diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index cf8fe30ebeac1..4e571e705655d 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -205,7 +205,7 @@ static int acquire_user_record(pam_handle_t *pamh, UserRecord **ret_record) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; /* Parse cached record */ - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return pam_syslog_errno(pamh, LOG_ERR, r, "Failed to parse JSON user record: %m"); @@ -279,7 +279,7 @@ static int socket_from_display(const char *display) { if (!display_is_local(display)) return -EINVAL; - k = strspn(display+1, "0123456789"); + k = strspn(display + 1, DIGITS); /* Try abstract socket first. */ f = new(char, STRLEN("@/tmp/.X11-unix/X") + k + 1); @@ -1144,8 +1144,8 @@ static int register_session( SD_JSON_BUILD_PAIR_UNSIGNED("UID", ur->uid), JSON_BUILD_PAIR_PIDREF("PID", &pidref), JSON_BUILD_PAIR_STRING_NON_EMPTY("Service", c->service), - SD_JSON_BUILD_PAIR("Type", JSON_BUILD_STRING_UNDERSCORIFY(c->type)), - SD_JSON_BUILD_PAIR("Class", JSON_BUILD_STRING_UNDERSCORIFY(c->class)), + JSON_BUILD_PAIR_ENUM("Type", c->type), + JSON_BUILD_PAIR_ENUM("Class", c->class), JSON_BUILD_PAIR_STRING_NON_EMPTY("Desktop", c->desktop), JSON_BUILD_PAIR_STRING_NON_EMPTY("Seat", c->seat), SD_JSON_BUILD_PAIR_CONDITION(c->vtnr > 0, "VTNr", SD_JSON_BUILD_UNSIGNED(c->vtnr)), @@ -1750,7 +1750,7 @@ _public_ PAM_EXTERN int pam_sm_open_session( assert(pamh); - r = dlopen_libpam(); + r = dlopen_libpam(LOG_DEBUG); if (r < 0) return PAM_SERVICE_ERR; diff --git a/src/login/pam_systemd_loadkey.c b/src/login/pam_systemd_loadkey.c index 3e0df8c86b31b..93ccddc25b6a7 100644 --- a/src/login/pam_systemd_loadkey.c +++ b/src/login/pam_systemd_loadkey.c @@ -19,7 +19,7 @@ _public_ PAM_EXTERN int pam_sm_authenticate( assert(pamh); - r = dlopen_libpam(); + r = dlopen_libpam(LOG_DEBUG); if (r < 0) return PAM_SERVICE_ERR; diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c index 70774dcdd1b85..4fb70821123ae 100644 --- a/src/machine-id-setup/machine-id-setup-main.c +++ b/src/machine-id-setup/machine-id-setup-main.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" #include "build.h" #include "dissect-image.h" +#include "format-table.h" #include "id128-util.h" #include "image-policy.h" #include "log.h" @@ -13,6 +13,7 @@ #include "machine-id-setup.h" #include "main-func.h" #include "mount-util.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" @@ -28,104 +29,95 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *commands = NULL, *options = NULL; int r; r = terminal_urlify_man("systemd-machine-id-setup", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...]\n" - "\n%2$sInitialize /etc/machine-id from a random source.%4$s\n" - "\n%3$sCommands:%4$s\n" - " --commit Commit transient ID\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sOptions:%4$s\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY Specify disk image dissection policy\n" - " --print Print used machine ID\n" - "\nSee the %5$s for details.\n", + r = option_parser_get_help_table(&commands); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("Options", &options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, commands, options); + + printf("%s [OPTIONS...]\n\n" + "%sInitialize /etc/machine-id from a random source.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, ansi_highlight(), - ansi_underline(), ansi_normal(), - link); + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(commands); + if (r < 0) + return r; + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_COMMIT, - ARG_PRINT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "commit", no_argument, NULL, ARG_COMMIT }, - { "print", no_argument, NULL, ARG_PRINT }, - {} - }; - - int c, r; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ROOT: - r = parse_path_argument(optarg, true, &arg_root); - if (r < 0) - return r; + OPTION_LONG("commit", NULL, "Commit transient ID"): + arg_commit = true; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, false, &arg_image); + OPTION_GROUP("Options"): {} + + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(arg, true, &arg_root); if (r < 0) return r; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): + r = parse_path_argument(arg, false, &arg_image); if (r < 0) return r; break; - case ARG_COMMIT: - arg_commit = true; + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); + if (r < 0) + return r; break; - case ARG_PRINT: + OPTION_LONG("print", NULL, "Print used machine ID"): arg_print = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extraneous arguments"); diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c index 341c4a228df4d..4a61b48f3f77d 100644 --- a/src/machine/image-dbus.c +++ b/src/machine/image-dbus.c @@ -443,7 +443,7 @@ static int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, return 1; } -const sd_bus_vtable image_vtable[] = { +static const sd_bus_vtable image_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0), SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0), diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index b09a2facb0bfa..a9d15ca5f72b1 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -26,6 +26,7 @@ #include "signal-util.h" #include "string-util.h" #include "strv.h" +#include "user-util.h" static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_class, machine_class, MachineClass); static BUS_DEFINE_PROPERTY_GET2(property_get_state, "s", Machine, machine_get_state, machine_state_to_string); @@ -246,6 +247,25 @@ int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, s assert(message); + if (m->manager->runtime_scope != RUNTIME_SCOPE_USER) { + const char *details[] = { + "machine", m->name, + "verb", "get_os_release", + NULL + }; + + r = bus_verify_polkit_async( + message, + "org.freedesktop.machine1.inspect-machines", + details, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + } + r = machine_get_os_release(m, &l); if (r == -ENONET) return sd_bus_error_set(error, SD_BUS_ERROR_FAILED, "Machine does not contain OS release information."); @@ -366,21 +386,25 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu return r; user = isempty(user) ? "root" : user; - /* Ensure only root can shell into the root namespace, unless it's specifically the host machine, - * which is owned by uid 0 anyway and cannot be self-registered. This is to avoid unprivileged - * users registering a process they own in the root user namespace, and then shelling in as root - * or another user. Note that the shell operation is privileged and requires 'auth_admin', so we - * do not need to check the caller's uid, as that will be checked by polkit, and if they machine's - * and the caller's do not match, authorization will be required. It's only the case where the - * caller owns the machine that will be shortcut and needs to be checked here. */ - if (m->manager->runtime_scope != RUNTIME_SCOPE_USER && m->uid != 0 && m->class != MACHINE_HOST) { + if (!valid_user_group_name(user, VALID_USER_RELAX)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user name '%s'", user); + + /* Ensure only root can shell into the root namespace. This is to avoid unprivileged users registering + * a process they own in the root user namespace, and then shelling in as root or another user. Note that + * the shell operation is privileged and requires 'auth_admin', so we do not need to check the caller's uid, + * as that will be checked by polkit, and if the machine's and the caller's do not match, authorization + * will be required. It's only the case where the caller owns the machine that will be shortcut and needs + * to be checked here. */ + if (m->manager->runtime_scope != RUNTIME_SCOPE_USER && m->uid != 0) { + assert(m->class != MACHINE_HOST); + r = pidref_in_same_namespace(&PIDREF_MAKE_FROM_PID(1), &m->leader, NAMESPACE_USER); if (r < 0) return log_debug_errno( r, "Failed to check if machine '%s' is running in the root user namespace: %m", m->name); - if (r != 0) + if (r > 0) return sd_bus_error_set( error, SD_BUS_ERROR_ACCESS_DENIED, diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index a499dbc3be2d3..fcdeeb7ae8b10 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -142,6 +142,7 @@ int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink { "vSockCid", _SD_JSON_VARIANT_TYPE_INVALID, machine_cid, offsetof(Machine, vsock_cid), 0 }, { "sshAddress", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Machine, ssh_address), SD_JSON_STRICT }, { "sshPrivateKeyPath", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(Machine, ssh_private_key_path), 0 }, + { "controlAddress", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(Machine, control_address), SD_JSON_STRICT }, { "allocateUnit", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Machine, allocate_unit), 0 }, VARLINK_DISPATCH_POLKIT_FIELD, {} @@ -155,6 +156,9 @@ int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink if (r != 0) return r; + if (!MACHINE_CLASS_CAN_REGISTER(machine->class)) + return sd_varlink_error_invalid_parameter_name(link, "class"); + if (manager->runtime_scope != RUNTIME_SCOPE_USER) { r = varlink_verify_polkit_async( link, @@ -189,8 +193,10 @@ int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink if (r < 0) return r; - /* Ensure an unprivileged user cannot claim any process they don't control as their own machine */ - if (machine->uid != 0) { + /* In system scope, ensure an unprivileged user cannot claim any process they don't + * control as their own machine. In user scope the varlink socket is already + * protected by $XDG_RUNTIME_DIR permissions. */ + if (manager->runtime_scope != RUNTIME_SCOPE_USER && machine->uid != 0) { r = process_is_owned_by_uid(&machine->leader, machine->uid); if (r < 0) return r; @@ -355,10 +361,12 @@ int vl_method_terminate_internal(sd_varlink *link, sd_json_variant *parameters, return sd_varlink_reply(link, NULL); } +static JSON_DISPATCH_ENUM_DEFINE(dispatch_kill_whom, KillWhom, kill_whom_from_string); + typedef struct MachineKillParameters { const char *name; PidRef pidref; - const char *swhom; + KillWhom whom; int32_t signo; } MachineKillParameters; @@ -371,7 +379,7 @@ static void machine_kill_paramaters_done(MachineKillParameters *p) { int vl_method_kill(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { static const sd_json_dispatch_field dispatch_table[] = { VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineKillParameters), - { "whom", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MachineKillParameters, swhom), 0 }, + { "whom", SD_JSON_VARIANT_STRING, dispatch_kill_whom, offsetof(MachineKillParameters, whom), 0 }, { "signal", _SD_JSON_VARIANT_TYPE_INVALID , sd_json_dispatch_signal, offsetof(MachineKillParameters, signo), SD_JSON_MANDATORY }, VARLINK_DISPATCH_POLKIT_FIELD, {} @@ -380,8 +388,8 @@ int vl_method_kill(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met Manager *manager = ASSERT_PTR(userdata); _cleanup_(machine_kill_paramaters_done) MachineKillParameters p = { .pidref = PIDREF_NULL, + .whom = _KILL_WHOM_INVALID, }; - KillWhom whom; int r; assert(link); @@ -398,13 +406,7 @@ int vl_method_kill(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met if (r < 0) return r; - if (isempty(p.swhom)) - whom = KILL_ALL; - else { - whom = kill_whom_from_string(p.swhom); - if (whom < 0) - return sd_varlink_error_invalid_parameter_name(link, "whom"); - } + KillWhom whom = p.whom >= 0 ? p.whom : KILL_ALL; if (manager->runtime_scope != RUNTIME_SCOPE_USER) { r = varlink_verify_polkit_async_full( @@ -552,6 +554,25 @@ int vl_method_open(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met return r; if (manager->runtime_scope != RUNTIME_SCOPE_USER) { + /* Ensure only root can shell into the root namespace. This is to avoid unprivileged users + * registering a process they own in the root user namespace, and then shelling in as root + * or another user. Note that the shell operation is privileged and requires 'auth_admin', so we + * do not need to check the caller's uid, as that will be checked by polkit, and if the machine's + * and the caller's do not match, authorization will be required. It's only the case where the + * caller owns the machine that will be shortcut and needs to be checked here. */ + if (machine->uid != 0) { + assert(machine->class != MACHINE_HOST); + + r = pidref_in_same_namespace(&PIDREF_MAKE_FROM_PID(1), &machine->leader, NAMESPACE_USER); + if (r < 0) + return log_debug_errno( + r, + "Failed to check if machine '%s' is running in the root user namespace: %m", + machine->name); + if (r > 0) + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL); + } + _cleanup_strv_free_ char **polkit_details = NULL; polkit_details = machine_open_polkit_details(p.mode, machine->name, user, path, command_line); @@ -638,7 +659,7 @@ static void machine_map_paramaters_done(MachineMapParameters *p) { int vl_method_map_from(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { static const sd_json_dispatch_field dispatch_table[] = { - VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineOpenParameters), + VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineMapParameters), { "uid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(MachineMapParameters, uid), 0 }, { "gid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(MachineMapParameters, gid), 0 }, {} @@ -799,7 +820,7 @@ static void machine_mount_paramaters_done(MachineMountParameters *p) { int vl_method_bind_mount(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { static const sd_json_dispatch_field dispatch_table[] = { - VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineOpenParameters), + VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineMountParameters), { "source", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineMountParameters, src), SD_JSON_MANDATORY }, { "destination", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineMountParameters, dest), 0 }, { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MachineMountParameters, read_only), 0 }, diff --git a/src/machine/machine.c b/src/machine/machine.c index da26bdf7a0711..63fed79687e5d 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -154,6 +154,7 @@ Machine* machine_free(Machine *m) { free(m->netif); free(m->ssh_address); free(m->ssh_private_key_path); + free(m->control_address); return mfree(m); } @@ -245,6 +246,7 @@ int machine_save(Machine *m) { env_file_fputs_assignment(f, "SSH_ADDRESS=", m->ssh_address); env_file_fputs_assignment(f, "SSH_PRIVATE_KEY_PATH=", m->ssh_private_key_path); + env_file_fputs_assignment(f, "CONTROL_ADDRESS=", m->control_address); r = flink_tmpfile(f, temp_path, m->state_file, LINK_TMPFILE_REPLACE); if (r < 0) @@ -338,6 +340,7 @@ int machine_load(Machine *m) { "VSOCK_CID", &vsock_cid, "SSH_ADDRESS", &m->ssh_address, "SSH_PRIVATE_KEY_PATH", &m->ssh_private_key_path, + "CONTROL_ADDRESS", &m->control_address, "UID", &uid); if (r == -ENOENT) return 0; @@ -1103,11 +1106,10 @@ int machine_start_shell( char** machine_default_shell_args(const char *user) { _cleanup_strv_free_ char **args = NULL; - int r; assert(user); - args = new0(char*, 3 + 1); + args = new0(char*, 5 + 1); if (!args) return NULL; @@ -1119,14 +1121,19 @@ char** machine_default_shell_args(const char *user) { if (!args[1]) return NULL; - r = asprintf(&args[2], - "shell=$(getent passwd %s 2>/dev/null | { IFS=: read _ _ _ _ _ _ x; echo \"$x\"; })\n"\ - "exec \"${shell:-/bin/sh}\" -l", /* -l is means --login */ - user); - if (r < 0) { - args[2] = NULL; + args[2] = strdup( + "shell=$(getent passwd \"$1\" 2>/dev/null | { IFS=: read _ _ _ _ _ _ x; echo \"$x\"; })\n" + "exec \"${shell:-/bin/sh}\" -l"); /* -l means --login */ + if (!args[2]) + return NULL; + + args[3] = strdup("sh"); /* $0 placeholder for sh -c */ + if (!args[3]) + return NULL; + + args[4] = strdup(user); /* becomes $1 in the script */ + if (!args[4]) return NULL; - } return TAKE_PTR(args); } diff --git a/src/machine/machine.h b/src/machine/machine.h index d02bb9a965edf..6f6183b712d58 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -25,6 +25,8 @@ typedef enum MachineClass { _MACHINE_CLASS_INVALID = -EINVAL, } MachineClass; +#define MACHINE_CLASS_CAN_REGISTER(class) IN_SET((class), MACHINE_CONTAINER, MACHINE_VM) + typedef enum KillWhom { KILL_LEADER, KILL_SUPERVISOR, @@ -34,6 +36,17 @@ typedef enum KillWhom { } KillWhom; typedef struct Machine { + /* Note: machine objects registered with the --system instance can be allocated by privileged *and* + * unprivileged clients. We generally do this to make DNS-style name resolution work, and since + * that's a system-wide concept, the machine registrations need to be system-wide too. + * + * polkit manages access to machines registered by unprivileged clients. The general rule should be + * that local users (i.e. those with a seat) may register machines, and do basic interaction with + * their own machines without having to authenticate as administrator – however any more complex + * (such as: copying files in + out of a container; or logging in interactively) should only be + * available after administrator authentication, following the logic that users better use their own + * per-user instance of systemd-machined for that. */ + Manager *manager; char *name; @@ -83,6 +96,7 @@ typedef struct Machine { unsigned vsock_cid; char *ssh_address; char *ssh_private_key_path; + char *control_address; LIST_HEAD(Operation, operations); diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 9b2056918c03c..e8a30a89592a3 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -9,6 +9,7 @@ #include "sd-bus.h" #include "sd-event.h" #include "sd-journal.h" +#include "sd-varlink.h" #include "alloc-util.h" #include "ask-password-agent.h" @@ -45,6 +46,7 @@ #include "pager.h" #include "parse-argument.h" #include "parse-util.h" +#include "path-lookup.h" #include "path-util.h" #include "pidref.h" #include "polkit-agent.h" @@ -193,6 +195,7 @@ static int call_get_addresses( assert(name); assert(prefix); assert(prefix2); + assert(ret); r = bus_call_method(bus, bus_machine_mgr, "GetMachineAddresses", NULL, &reply, "s", name); if (r < 0) @@ -262,7 +265,7 @@ static int show_table(Table *table, const char *word) { if (OUTPUT_MODE_IS_JSON(arg_output)) r = table_print_json(table, NULL, output_mode_to_json_format_flags(arg_output) | SD_JSON_FORMAT_COLOR_AUTO); else - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); } @@ -277,7 +280,7 @@ static int show_table(Table *table, const char *word) { return 0; } -static int list_machines(int argc, char *argv[], void *userdata) { +static int verb_list_machines(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(table_unrefp) Table *table = NULL; @@ -354,7 +357,7 @@ static int list_machines(int argc, char *argv[], void *userdata) { return show_table(table, "machines"); } -static int list_images(int argc, char *argv[], void *userdata) { +static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; @@ -745,7 +748,7 @@ static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line return r; } -static int show_machine(int argc, char *argv[], void *userdata) { +static int verb_show_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; bool properties, new_line = false; sd_bus *bus = ASSERT_PTR(userdata); @@ -1036,7 +1039,7 @@ static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) return r; } -static int show_image(int argc, char *argv[], void *userdata) { +static int verb_show_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; bool properties, new_line = false; sd_bus *bus = ASSERT_PTR(userdata); @@ -1080,7 +1083,7 @@ static int show_image(int argc, char *argv[], void *userdata) { return r; } -static int kill_machine(int argc, char *argv[], void *userdata) { +static int verb_kill_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1105,34 +1108,73 @@ static int kill_machine(int argc, char *argv[], void *userdata) { return 0; } -static int reboot_machine(int argc, char *argv[], void *userdata) { - if (arg_runner == RUNNER_VMSPAWN) - return log_error_errno( - SYNTHETIC_ERRNO(EOPNOTSUPP), - "%s only support supported for --runner=nspawn", - streq(argv[0], "reboot") ? "Reboot" : "Restart"); +static int verb_machine_control_one(const char *machine_name, const char *method); + +static int verb_reboot_machine(int argc, char *argv[], uintptr_t data, void *userdata) { + sd_bus *bus = ASSERT_PTR(userdata); + int r; - arg_kill_whom = "leader"; - arg_signal = SIGINT; /* sysvinit + systemd */ + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - return kill_machine(argc, argv, userdata); + for (int i = 1; i < argc; i++) { + r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.Reboot"); + if (r >= 0) + continue; + if (r != -EOPNOTSUPP) + return r; + + /* Container fallback: SIGINT to init (sysvinit + systemd compatible) */ + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = bus_call_method(bus, bus_machine_mgr, "KillMachine", &error, NULL, + "ssi", argv[i], "leader", (int32_t) SIGINT); + if (r < 0) + return log_error_errno(r, "Could not reboot machine '%s': %s", argv[i], bus_error_message(&error, r)); + } + + return 0; } -static int poweroff_machine(int argc, char *argv[], void *userdata) { - arg_kill_whom = "leader"; - arg_signal = SIGRTMIN+4; /* only systemd */ +static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata) { + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - return kill_machine(argc, argv, userdata); + for (int i = 1; i < argc; i++) { + /* VM with varlink control socket: QMP graceful powerdown */ + r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.PowerOff"); + if (r >= 0) + continue; + if (r != -EOPNOTSUPP) + return r; + + /* Not a VM: signal-based poweroff */ + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = bus_call_method(bus, bus_machine_mgr, "KillMachine", &error, NULL, + "ssi", argv[i], "leader", (int32_t) (SIGRTMIN+4)); + if (r < 0) + return log_error_errno(r, "Could not kill machine: %s", bus_error_message(&error, r)); + } + + return 0; } -static int terminate_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; +static int verb_terminate_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); int r; (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); for (int i = 1; i < argc; i++) { + /* VM with varlink control socket: QMP quit (immediate termination) */ + r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.Terminate"); + if (r >= 0) + continue; + if (r != -EOPNOTSUPP) + return r; + + /* Not a VM or no varlink socket: fall back to machined */ + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; r = bus_call_method(bus, bus_machine_mgr, "TerminateMachine", &error, NULL, "s", argv[i]); if (r < 0) return log_error_errno(r, "Could not terminate machine: %s", bus_error_message(&error, r)); @@ -1141,6 +1183,99 @@ static int terminate_machine(int argc, char *argv[], void *userdata) { return 0; } +/* Look up the controlAddress of a machine by calling machined's Machine.List varlink interface. */ +static int machine_get_control_address(const char *machine_name, char **ret) { + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + sd_json_variant *reply = NULL; + const char *error_id = NULL; + int r; + + assert(machine_name); + assert(ret); + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return -EOPNOTSUPP; + + _cleanup_free_ char *p = NULL; + r = runtime_directory_generic(arg_runtime_scope, "systemd/machine/io.systemd.Machine", &p); + if (r < 0) + return log_error_errno(r, "Failed to determine Machine varlink socket path: %m"); + + r = sd_varlink_connect_address(&vl, p); + if (r < 0) + return log_error_errno(r, "Failed to connect to machined varlink: %m"); + + r = sd_varlink_callbo( + vl, + "io.systemd.Machine.List", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_STRING("name", machine_name)); + if (r < 0) + return log_error_errno(r, "Failed to list machine: %m"); + if (error_id) + return log_error_errno(sd_varlink_error_to_errno(error_id, reply), + "Failed to look up machine '%s': %s", machine_name, error_id); + + sd_json_variant *addr = sd_json_variant_by_key(reply, "controlAddress"); + if (!addr || !sd_json_variant_is_string(addr)) + return -EOPNOTSUPP; /* No varlink control socket, caller decides whether to log */ + + char *a = strdup(sd_json_variant_string(addr)); + if (!a) + return log_oom(); + + *ret = a; + return 0; +} + +static int verb_machine_control_one(const char *machine_name, const char *method) { + _cleanup_free_ char *address = NULL; + int r; + + r = machine_get_control_address(machine_name, &address); + if (r < 0) + return r; + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = sd_varlink_connect_address(&vl, address); + if (r < 0) + return log_error_errno(r, "Failed to connect to machine control socket: %m"); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_call(vl, method, /* parameters= */ NULL, &reply, &error_id); + if (r < 0) + return log_error_errno(r, "Failed to call %s: %m", method); + if (error_id) + return log_error_errno(sd_varlink_error_to_errno(error_id, reply), + "Machine control call failed: %s", error_id); + + return 0; +} + +static int verb_vm_control(int argc, char *argv[], const char *method) { + int r; + + for (int i = 1; i < argc; i++) { + r = verb_machine_control_one(argv[i], method); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[i]); + if (r < 0) + return r; + } + + return 0; +} + +static int verb_pause(int argc, char *argv[], uintptr_t _data, void *userdata) { + return verb_vm_control(argc, argv, "io.systemd.MachineInstance.Pause"); +} + +static int verb_resume(int argc, char *argv[], uintptr_t _data, void *userdata) { + return verb_vm_control(argc, argv, "io.systemd.MachineInstance.Resume"); +} + static const char *select_copy_method(bool copy_from, bool force) { if (force) return copy_from ? "CopyFromMachineWithFlags" : "CopyToMachineWithFlags"; @@ -1148,7 +1283,7 @@ static const char *select_copy_method(bool copy_from, bool force) { return copy_from ? "CopyFromMachine" : "CopyToMachine"; } -static int copy_files(int argc, char *argv[], void *userdata) { +static int verb_copy_files(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *abs_host_path = NULL; @@ -1203,7 +1338,7 @@ static int copy_files(int argc, char *argv[], void *userdata) { return 0; } -static int bind_mount(int argc, char *argv[], void *userdata) { +static int verb_bind_mount(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1308,6 +1443,9 @@ static int parse_machine_uid(const char *spec, const char **machine, char **uid) char *_uid = NULL; const char *_machine = NULL; + assert(uid); + assert(machine); + if (spec) { const char *at; @@ -1336,7 +1474,7 @@ static int parse_machine_uid(const char *spec, const char **machine, char **uid) return 0; } -static int login_machine(int argc, char *argv[], void *userdata) { +static int verb_login_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; @@ -1387,7 +1525,7 @@ static int login_machine(int argc, char *argv[], void *userdata) { return process_forward(event, slot, master, PTY_FORWARD_IGNORE_VHANGUP, machine); } -static int shell_machine(int argc, char *argv[], void *userdata) { +static int verb_shell_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; @@ -1515,7 +1653,7 @@ static int get_settings_path(const char *name, char **ret_path) { return -ENOENT; } -static int edit_settings(int argc, char *argv[], void *userdata) { +static int verb_edit_settings(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(edit_file_context_done) EditFileContext context = {}; int r; @@ -1585,7 +1723,7 @@ static int edit_settings(int argc, char *argv[], void *userdata) { return do_edit_files_and_install(&context); } -static int cat_settings(int argc, char *argv[], void *userdata) { +static int verb_cat_settings(int argc, char *argv[], uintptr_t _data, void *userdata) { int r = 0; if (arg_transport != BUS_TRANSPORT_LOCAL) @@ -1636,7 +1774,7 @@ static int cat_settings(int argc, char *argv[], void *userdata) { return r; } -static int remove_image(int argc, char *argv[], void *userdata) { +static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1663,7 +1801,7 @@ static int remove_image(int argc, char *argv[], void *userdata) { return 0; } -static int rename_image(int argc, char *argv[], void *userdata) { +static int verb_rename_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1683,7 +1821,7 @@ static int rename_image(int argc, char *argv[], void *userdata) { return 0; } -static int clone_image(int argc, char *argv[], void *userdata) { +static int verb_clone_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; sd_bus *bus = ASSERT_PTR(userdata); @@ -1707,7 +1845,7 @@ static int clone_image(int argc, char *argv[], void *userdata) { return 0; } -static int read_only_image(int argc, char *argv[], void *userdata) { +static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int b = true, r; @@ -1765,7 +1903,7 @@ static int make_service_name(const char *name, char **ret) { return 0; } -static int start_machine(int argc, char *argv[], void *userdata) { +static int verb_start_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; sd_bus *bus = ASSERT_PTR(userdata); @@ -1821,7 +1959,7 @@ static int start_machine(int argc, char *argv[], void *userdata) { return 0; } -static int enable_machine(int argc, char *argv[], void *userdata) { +static int verb_enable_machine(int argc, char *argv[], uintptr_t data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; const char *method; @@ -1909,15 +2047,15 @@ static int enable_machine(int argc, char *argv[], void *userdata) { return log_oom(); if (enable) - return start_machine(strv_length(new_args), new_args, userdata); + return verb_start_machine(strv_length(new_args), new_args, data, userdata); - return poweroff_machine(strv_length(new_args), new_args, userdata); + return verb_poweroff_machine(strv_length(new_args), new_args, data, userdata); } return 0; } -static int set_limit(int argc, char *argv[], void *userdata) { +static int verb_set_limit(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; uint64_t limit; @@ -1947,7 +2085,7 @@ static int set_limit(int argc, char *argv[], void *userdata) { return 0; } -static int clean_images(int argc, char *argv[], void *userdata) { +static int verb_clean_images(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; uint64_t usage, total = 0; @@ -2048,7 +2186,7 @@ static int chainload_importctl(int argc, char *argv[]) { return log_error_errno(r, "Failed to invoke 'importctl': %m"); } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -2073,10 +2211,12 @@ static int help(int argc, char *argv[], void *userdata) { " or on the local host\n" " enable NAME... Enable automatic container start at boot\n" " disable NAME... Disable automatic container start at boot\n" - " poweroff NAME... Power off one or more containers\n" - " reboot NAME... Reboot one or more containers\n" - " terminate NAME... Terminate one or more VMs/containers\n" - " kill NAME... Send signal to processes of a VM/container\n" + " poweroff NAME... Power off one or more machines\n" + " reboot NAME... Reboot one or more machines\n" + " pause NAME... Pause one or more machines\n" + " resume NAME... Resume one or more paused machines\n" + " terminate NAME... Terminate one or more machines\n" + " kill NAME... Send signal to processes of a machine\n" " copy-to NAME PATH [PATH] Copy files from the host to a container\n" " copy-from NAME PATH [PATH] Copy files from a container to the host\n" " bind NAME PATH [PATH] Bind mount a path from the host into a container\n" @@ -2137,6 +2277,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -2257,7 +2401,7 @@ static int parse_argv(int argc, char *argv[]) { break; case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -2444,35 +2588,37 @@ static int parse_argv(int argc, char *argv[]) { static int machinectl_main(int argc, char *argv[], sd_bus *bus) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "list", VERB_ANY, 1, VERB_DEFAULT, list_machines }, - { "list-images", VERB_ANY, 1, 0, list_images }, - { "status", 2, VERB_ANY, 0, show_machine }, - { "image-status", VERB_ANY, VERB_ANY, 0, show_image }, - { "show", VERB_ANY, VERB_ANY, 0, show_machine }, - { "show-image", VERB_ANY, VERB_ANY, 0, show_image }, - { "terminate", 2, VERB_ANY, 0, terminate_machine }, - { "reboot", 2, VERB_ANY, 0, reboot_machine }, - { "restart", 2, VERB_ANY, 0, reboot_machine }, /* Convenience alias */ - { "poweroff", 2, VERB_ANY, 0, poweroff_machine }, - { "stop", 2, VERB_ANY, 0, poweroff_machine }, /* Convenience alias */ - { "kill", 2, VERB_ANY, 0, kill_machine }, - { "login", VERB_ANY, 2, 0, login_machine }, - { "shell", VERB_ANY, VERB_ANY, 0, shell_machine }, - { "bind", 3, 4, 0, bind_mount }, - { "edit", 2, VERB_ANY, 0, edit_settings }, - { "cat", 2, VERB_ANY, 0, cat_settings }, - { "copy-to", 3, 4, 0, copy_files }, - { "copy-from", 3, 4, 0, copy_files }, - { "remove", 2, VERB_ANY, 0, remove_image }, - { "rename", 3, 3, 0, rename_image }, - { "clone", 3, 3, 0, clone_image }, - { "read-only", 2, 3, 0, read_only_image }, - { "start", 2, VERB_ANY, 0, start_machine }, - { "enable", 2, VERB_ANY, 0, enable_machine }, - { "disable", 2, VERB_ANY, 0, enable_machine }, - { "set-limit", 2, 3, 0, set_limit }, - { "clean", VERB_ANY, 1, 0, clean_images }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list_machines }, + { "list-images", VERB_ANY, 1, 0, verb_list_images }, + { "status", 2, VERB_ANY, 0, verb_show_machine }, + { "image-status", VERB_ANY, VERB_ANY, 0, verb_show_image }, + { "show", VERB_ANY, VERB_ANY, 0, verb_show_machine }, + { "show-image", VERB_ANY, VERB_ANY, 0, verb_show_image }, + { "terminate", 2, VERB_ANY, 0, verb_terminate_machine }, + { "reboot", 2, VERB_ANY, 0, verb_reboot_machine }, + { "restart", 2, VERB_ANY, 0, verb_reboot_machine }, /* Convenience alias */ + { "poweroff", 2, VERB_ANY, 0, verb_poweroff_machine }, + { "stop", 2, VERB_ANY, 0, verb_poweroff_machine }, /* Convenience alias */ + { "kill", 2, VERB_ANY, 0, verb_kill_machine }, + { "pause", 2, VERB_ANY, 0, verb_pause }, + { "resume", 2, VERB_ANY, 0, verb_resume }, + { "login", VERB_ANY, 2, 0, verb_login_machine }, + { "shell", VERB_ANY, VERB_ANY, 0, verb_shell_machine }, + { "bind", 3, 4, 0, verb_bind_mount }, + { "edit", 2, VERB_ANY, 0, verb_edit_settings }, + { "cat", 2, VERB_ANY, 0, verb_cat_settings }, + { "copy-to", 3, 4, 0, verb_copy_files }, + { "copy-from", 3, 4, 0, verb_copy_files }, + { "remove", 2, VERB_ANY, 0, verb_remove_image }, + { "rename", 3, 3, 0, verb_rename_image }, + { "clone", 3, 3, 0, verb_clone_image }, + { "read-only", 2, 3, 0, verb_read_only_image }, + { "start", 2, VERB_ANY, 0, verb_start_machine }, + { "enable", 2, VERB_ANY, 0, verb_enable_machine }, + { "disable", 2, VERB_ANY, 0, verb_enable_machine }, + { "set-limit", 2, 3, 0, verb_set_limit }, + { "clean", VERB_ANY, 1, 0, verb_clean_images }, {} }; diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 0130fbf77988f..f5315b9763083 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -26,6 +26,7 @@ #include "machine.h" #include "machine-dbus.h" #include "machined.h" +#include "machined-dbus.h" #include "namespace-util.h" #include "operation.h" #include "os-util.h" @@ -255,6 +256,9 @@ static int machine_add_from_params( assert(manager); assert(message); assert(name); + assert(c == _MACHINE_CLASS_INVALID || MACHINE_CLASS_CAN_REGISTER(c)); + assert(leader_pidref); + assert(supervisor_pidref); assert(ret); if (leader_pidref->pid == 1) @@ -315,10 +319,8 @@ static int machine_add_from_params( details, &manager->polkit_registry, error); - if (r < 0) - return r; - if (r == 0) - return 0; /* Will call us back */ + if (r <= 0) + return r; /* 0 means Polkit will call us back, see method_create_machine() */ } r = manager_add_machine(manager, name, &m); @@ -434,7 +436,7 @@ static int method_create_or_register_machine( c = _MACHINE_CLASS_INVALID; else { c = machine_class_from_string(class); - if (c < 0) + if (c < 0 || !MACHINE_CLASS_CAN_REGISTER(c)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine class parameter"); } @@ -464,7 +466,7 @@ static int method_create_or_register_machine( supervisor_pidref = TAKE_PIDREF(client_pidref); } - if (hashmap_get(manager->machines, name)) + if (hashmap_contains(manager->machines, name)) return sd_bus_error_setf(error, BUS_ERROR_MACHINE_EXISTS, "Machine '%s' already exists", name); return machine_add_from_params( @@ -609,14 +611,14 @@ static int method_create_or_register_machine_ex( c = _MACHINE_CLASS_INVALID; else { c = machine_class_from_string(class); - if (c < 0) + if (c < 0 || !MACHINE_CLASS_CAN_REGISTER(c)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine class parameter"); } if (!isempty(root_directory) && (!path_is_absolute(root_directory) || !path_is_valid(root_directory))) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Root directory must be empty or an absolute path"); - if (hashmap_get(manager->machines, name)) + if (hashmap_contains(manager->machines, name)) return sd_bus_error_setf(error, BUS_ERROR_MACHINE_EXISTS, "Machine '%s' already exists", name); /* If a PID is specified that's the leader, but if the client process is different from it, than that's the supervisor */ @@ -1219,7 +1221,7 @@ static int method_map_to_machine_group(sd_bus_message *message, void *userdata, return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); } -const sd_bus_vtable manager_vtable[] = { +static const sd_bus_vtable manager_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("PoolPath", "s", property_get_pool_path, 0, 0), @@ -1451,8 +1453,8 @@ const BusObjectImplementation manager_object = { "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", .vtables = BUS_VTABLES(manager_vtable), - .children = BUS_IMPLEMENTATIONS( &machine_object, - &image_object ), + .children = BUS_IMPLEMENTATIONS(&machine_object, + &image_object), }; int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) { diff --git a/src/machine/machined-dbus.h b/src/machine/machined-dbus.h new file mode 100644 index 0000000000000..7a1d610c01980 --- /dev/null +++ b/src/machine/machined-dbus.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "machine-forward.h" + +extern const BusObjectImplementation manager_object; diff --git a/src/machine/machined-resolve-hook.c b/src/machine/machined-resolve-hook.c index d022edc1691fb..1d1a9ad0a9331 100644 --- a/src/machine/machined-resolve-hook.c +++ b/src/machine/machined-resolve-hook.c @@ -174,7 +174,7 @@ int vl_method_resolve_record( if (r == 0) break; - nxdomain = !!hashmap_get(m->machines, q); + nxdomain = hashmap_contains(m->machines, q); } } diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index 543e4c8ee7f9d..acc2137f830b8 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -476,9 +476,9 @@ static int list_machine_one_and_maybe_read_metadata(sd_varlink *link, Machine *m r = sd_json_buildo( &v, - SD_JSON_BUILD_PAIR("name", SD_JSON_BUILD_STRING(m->name)), + SD_JSON_BUILD_PAIR_STRING("name", m->name), SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->id), "id", SD_JSON_BUILD_ID128(m->id)), - SD_JSON_BUILD_PAIR("class", SD_JSON_BUILD_STRING(machine_class_to_string(m->class))), + JSON_BUILD_PAIR_ENUM("class", machine_class_to_string(m->class)), JSON_BUILD_PAIR_STRING_NON_EMPTY("service", m->service), JSON_BUILD_PAIR_STRING_NON_EMPTY("rootDirectory", m->root_directory), JSON_BUILD_PAIR_STRING_NON_EMPTY("unit", m->unit), @@ -489,6 +489,7 @@ static int list_machine_one_and_maybe_read_metadata(sd_varlink *link, Machine *m JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("vSockCid", m->vsock_cid, VMADDR_CID_ANY), JSON_BUILD_PAIR_STRING_NON_EMPTY("sshAddress", m->ssh_address), JSON_BUILD_PAIR_STRING_NON_EMPTY("sshPrivateKeyPath", m->ssh_private_key_path), + JSON_BUILD_PAIR_STRING_NON_EMPTY("controlAddress", m->control_address), JSON_BUILD_PAIR_VARIANT_NON_NULL("addresses", addr_array), JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY("OSRelease", os_release), JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("UIDShift", shift, UID_INVALID), @@ -534,7 +535,18 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl if (r != 0) return r; - r = varlink_set_sentinel(link, VARLINK_ERROR_MACHINE_NO_SUCH_MACHINE); + if (m->runtime_scope != RUNTIME_SCOPE_USER && should_acquire_metadata(p.acquire_metadata)) { + r = varlink_verify_polkit_async( + link, + m->system_bus, + "org.freedesktop.machine1.inspect-machines", + (const char**) STRV_MAKE("name", strna(p.name)), + &m->polkit_registry); + if (r <= 0) + return r; + } + + r = sd_varlink_set_sentinel(link, VARLINK_ERROR_MACHINE_NO_SUCH_MACHINE); if (r < 0) return r; @@ -681,7 +693,18 @@ static int vl_method_list_images(sd_varlink *link, sd_json_variant *parameters, if (r != 0) return r; - r = varlink_set_sentinel(link, VARLINK_ERROR_MACHINE_IMAGE_NO_SUCH_IMAGE); + if (m->runtime_scope != RUNTIME_SCOPE_USER && should_acquire_metadata(p.acquire_metadata)) { + r = varlink_verify_polkit_async( + link, + m->system_bus, + "org.freedesktop.machine1.inspect-images", + (const char**) STRV_MAKE("name", strna(p.image_name)), + &m->polkit_registry); + if (r <= 0) + return r; + } + + r = sd_varlink_set_sentinel(link, VARLINK_ERROR_MACHINE_IMAGE_NO_SUCH_IMAGE); if (r < 0) return r; diff --git a/src/machine/machined.c b/src/machine/machined.c index dfb01abea646c..7c1f57baac3de 100644 --- a/src/machine/machined.c +++ b/src/machine/machined.c @@ -23,6 +23,7 @@ #include "hostname-util.h" #include "machine.h" #include "machined.h" +#include "machined-dbus.h" #include "machined-varlink.h" #include "main-func.h" #include "mkdir-label.h" diff --git a/src/machine/machined.h b/src/machine/machined.h index 7c8922ed3e213..daba5a9bbd322 100644 --- a/src/machine/machined.h +++ b/src/machine/machined.h @@ -42,8 +42,6 @@ typedef struct Manager { int manager_add_machine(Manager *m, const char *name, Machine **ret); int manager_get_machine_by_pidref(Manager *m, const PidRef *pidref, Machine **ret); -extern const BusObjectImplementation manager_object; - int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error); int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/machine/org.freedesktop.machine1.policy b/src/machine/org.freedesktop.machine1.policy index d5b8d83d2aade..f8f498f8cfc91 100644 --- a/src/machine/org.freedesktop.machine1.policy +++ b/src/machine/org.freedesktop.machine1.policy @@ -88,7 +88,17 @@ auth_admin auth_admin_keep - org.freedesktop.login1.shell org.freedesktop.login1.login + org.freedesktop.login1.shell org.freedesktop.login1.login org.freedesktop.machine1.inspect-machines + + + + Inspect local virtual machines and containers + Authentication is required to inspect local virtual machines and containers. + + auth_admin + auth_admin + auth_admin_keep + @@ -120,6 +130,17 @@ auth_admin auth_admin_keep + org.freedesktop.machine1.inspect-images + + + + Inspect local virtual machine and container images + Authentication is required to inspect local virtual machine and container images. + + auth_admin + auth_admin + auth_admin_keep + diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index 515e7588b0706..44619cab7db4b 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-json.h" @@ -8,14 +7,16 @@ #include "alloc-util.h" #include "ask-password-api.h" #include "build.h" +#include "crypto-util.h" #include "efi-loader.h" #include "efivars.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "hexdecoct.h" #include "log.h" #include "main-func.h" -#include "openssl-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -63,70 +64,58 @@ static void free_sections(char*(*sections)[_UNIFIED_SECTION_MAX]) { STATIC_DESTRUCTOR_REGISTER(arg_sections, free_sections); -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL, *options2 = NULL; int r; r = terminal_urlify_man("systemd-measure", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sPre-calculate and sign PCR hash for a unified kernel image (UKI).%6$s\n" - "\n%3$sCommands:%4$s\n" - " status Show current PCR values\n" - " calculate Calculate expected PCR values\n" - " sign Calculate and sign expected PCR values\n" - " policy-digest Calculate expected TPM2 policy digests\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --no-pager Do not pipe output into a pager\n" - " -c --current Use current PCR values\n" - " --phase=PHASE Specify a boot phase to sign for\n" - " --bank=DIGEST Select TPM bank (SHA1, SHA256, SHA384, SHA512)\n" - " --tpm2-device=PATH Use specified TPM2 device\n" - " --private-key=KEY Private key (PEM) to sign with\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used for signing\n" - " --public-key=KEY Public key (PEM) to validate against\n" - " --certificate=PATH|URI\n" - " PEM certificate to use for signing, or a provider\n" - " specific designation if --certificate-source= is used\n" - " --certificate-source=file|provider:PROVIDER\n" - " Specify how to interpret the certificate from\n" - " --certificate=. Allows the certificate to be loaded\n" - " from an OpenSSL provider\n" - " --json=MODE Output as JSON\n" - " -j Same as --json=pretty on tty, --json=short otherwise\n" - " --append=PATH Load specified JSON signature, and append new signature to it\n" - "\n%3$sUKI PE Section Options:%4$s %3$sUKI PE Section%4$s\n" - " --linux=PATH Path to Linux kernel image file %7$s .linux\n" - " --osrel=PATH Path to os-release file %7$s .osrel\n" - " --cmdline=PATH Path to file with kernel command line %7$s .cmdline\n" - " --initrd=PATH Path to initrd image file %7$s .initrd\n" - " --ucode=PATH Path to microcode image file %7$s .ucode\n" - " --splash=PATH Path to splash bitmap file %7$s .splash\n" - " --dtb=PATH Path to DeviceTree file %7$s .dtb\n" - " --dtbauto=PATH Path to DeviceTree file for auto selection %7$s .dtbauto\n" - " --uname=PATH Path to 'uname -r' file %7$s .uname\n" - " --sbat=PATH Path to SBAT file %7$s .sbat\n" - " --pcrpkey=PATH Path to public key for PCR signatures %7$s .pcrpkey\n" - " --profile=PATH Path to profile file %7$s .profile\n" - " --hwids=PATH Path to HWIDs file %7$s .hwids\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("UKI PE Section Options", &options2); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options, options2); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sPre-calculate and sign PCR hash for a unified kernel image (UKI).%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal(), - glyph(GLYPH_ARROW_RIGHT)); + ansi_highlight(), ansi_normal(), + ansi_underline(), ansi_normal()); + + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\n%sUKI PE Section Options:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(options2); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } +VERB_COMMON_HELP_HIDDEN(help); + static char *normalize_phase(const char *s) { _cleanup_strv_free_ char **l = NULL; @@ -142,128 +131,94 @@ static char *normalize_phase(const char *s) { return strv_join(strv_remove(l, ""), ":"); } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - _ARG_SECTION_FIRST, - ARG_LINUX = _ARG_SECTION_FIRST, - ARG_OSREL, - ARG_CMDLINE, - ARG_INITRD, - ARG_UCODE, - ARG_SPLASH, - ARG_DTB, - ARG_UNAME, - ARG_SBAT, - _ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */ - ARG_PCRPKEY, - ARG_PROFILE, - ARG_DTBAUTO, - ARG_HWIDS, - _ARG_SECTION_LAST, - ARG_EFIFW = _ARG_SECTION_LAST, - ARG_BANK, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - ARG_PUBLIC_KEY, - ARG_CERTIFICATE, - ARG_CERTIFICATE_SOURCE, - ARG_TPM2_DEVICE, - ARG_JSON, - ARG_PHASE, - ARG_APPEND, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "version", no_argument, NULL, ARG_VERSION }, - { "linux", required_argument, NULL, ARG_LINUX }, - { "osrel", required_argument, NULL, ARG_OSREL }, - { "cmdline", required_argument, NULL, ARG_CMDLINE }, - { "initrd", required_argument, NULL, ARG_INITRD }, - { "ucode", required_argument, NULL, ARG_UCODE }, - { "splash", required_argument, NULL, ARG_SPLASH }, - { "dtb", required_argument, NULL, ARG_DTB }, - { "dtbauto", required_argument, NULL, ARG_DTBAUTO }, - { "uname", required_argument, NULL, ARG_UNAME }, - { "sbat", required_argument, NULL, ARG_SBAT }, - { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, - { "profile", required_argument, NULL, ARG_PROFILE }, - { "hwids", required_argument, NULL, ARG_HWIDS }, - { "current", no_argument, NULL, 'c' }, - { "bank", required_argument, NULL, ARG_BANK }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - { "public-key", required_argument, NULL, ARG_PUBLIC_KEY }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, - { "json", required_argument, NULL, ARG_JSON }, - { "phase", required_argument, NULL, ARG_PHASE }, - { "append", required_argument, NULL, ARG_APPEND }, - {} - }; - - int c, r; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - /* Make sure the arguments list and the section list, stays in sync */ - assert_cc(_ARG_SECTION_FIRST + _UNIFIED_SECTION_MAX == _ARG_SECTION_LAST + 1); + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + int r; - while ((c = getopt_long(argc, argv, "hjc", options, NULL)) >= 0) + FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) switch (c) { - case 'h': - help(0, NULL, NULL); - return 0; + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case _ARG_SECTION_FIRST..._ARG_SECTION_LAST: { - UnifiedSection section = c - _ARG_SECTION_FIRST; + OPTION('c', "current", NULL, + "Use current PCR values"): + arg_current = true; + break; + + OPTION_LONG("phase", "PHASE", + "Specify a boot phase to sign for"): { + char *n; + + n = normalize_phase(arg); + if (!n) + return log_oom(); - r = parse_path_argument(optarg, /* suppress_root= */ false, arg_sections + section); + r = strv_consume(&arg_phase, TAKE_PTR(n)); if (r < 0) return r; - break; - } - case 'c': - arg_current = true; break; + } - case ARG_BANK: { + OPTION_LONG("bank", "DIGEST", + "Select TPM bank (SHA1, SHA256, SHA384, SHA512)"): { const EVP_MD *implementation; - implementation = EVP_get_digestbyname(optarg); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + + implementation = sym_EVP_get_digestbyname(arg); if (!implementation) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", arg); - if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0) + if (strv_extend(&arg_banks, sym_EVP_MD_get0_name(implementation)) < 0) return log_oom(); break; } - case ARG_PRIVATE_KEY: - r = free_and_strdup_warn(&arg_private_key, optarg); + OPTION_LONG("tpm2-device", "PATH", + "Use specified TPM2 device"): { + _cleanup_free_ char *device = NULL; + + if (streq(arg, "list")) + return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); + + if (!streq(arg, "auto")) { + device = strdup(arg); + if (!device) + return log_oom(); + } + + free_and_replace(arg_tpm2_device, device); + break; + } + + OPTION_COMMON_PRIVATE_KEY("Private key (PEM) to sign with"): + r = free_and_strdup_warn(&arg_private_key, arg); if (r < 0) return r; break; - case ARG_PRIVATE_KEY_SOURCE: + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - optarg, + arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) @@ -271,81 +226,87 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_PUBLIC_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_public_key); + OPTION_LONG("public-key", "KEY", + "Public key (PEM) to validate against"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_public_key); if (r < 0) return r; break; - case ARG_CERTIFICATE: - r = free_and_strdup_warn(&arg_certificate, optarg); + OPTION_COMMON_CERTIFICATE("PEM certificate to use for signing"): + r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - optarg, + arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) return r; break; - case ARG_TPM2_DEVICE: { - _cleanup_free_ char *device = NULL; - - if (streq(optarg, "list")) - return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - - if (!streq(optarg, "auto")) { - device = strdup(optarg); - if (!device) - return log_oom(); - } - - free_and_replace(arg_tpm2_device, device); - break; - } - - case 'j': - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; - break; - - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; break; - case ARG_PHASE: { - char *n; - - n = normalize_phase(optarg); - if (!n) - return log_oom(); + OPTION_COMMON_LOWERCASE_J: + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + break; - r = strv_consume(&arg_phase, TAKE_PTR(n)); + OPTION_LONG("append", "PATH", + "Load specified JSON signature, and append new signature to it"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_append); if (r < 0) return r; break; - } - case ARG_APPEND: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_append); + OPTION_GROUP("UKI PE Section Options"): {} + + OPTION_LONG_DATA("linux", "PATH", UNIFIED_SECTION_LINUX, + "Path to Linux kernel image file (→ .linux)"): {} + OPTION_LONG_DATA("osrel", "PATH", UNIFIED_SECTION_OSREL, + "Path to os-release file (→ .osrel)"): {} + OPTION_LONG_DATA("cmdline", "PATH", UNIFIED_SECTION_CMDLINE, + "Path to file with kernel command line (→ .cmdline)"): {} + OPTION_LONG_DATA("initrd", "PATH", UNIFIED_SECTION_INITRD, + "Path to initrd image file (→ .initrd)"): {} + OPTION_LONG_DATA("ucode", "PATH", UNIFIED_SECTION_UCODE, + "Path to microcode image file (→ .ucode)"): {} + OPTION_LONG_DATA("splash", "PATH", UNIFIED_SECTION_SPLASH, + "Path to splash bitmap file (→ .splash)"): {} + OPTION_LONG_DATA("dtb", "PATH", UNIFIED_SECTION_DTB, + "Path to DeviceTree file (→ .dtb)"): {} + OPTION_LONG_DATA("dtbauto", "PATH", UNIFIED_SECTION_DTBAUTO, + "Path to DeviceTree file for auto selection (→ .dtbauto)"): {} + OPTION_LONG_DATA("uname", "PATH", UNIFIED_SECTION_UNAME, + "Path to 'uname -r' file (→ .uname)"): {} + OPTION_LONG_DATA("sbat", "PATH", UNIFIED_SECTION_SBAT, + "Path to SBAT file (→ .sbat)"): {} + /* The .pcrsig section is not input for signing, hence not actually an argument here */ + OPTION_LONG_DATA("pcrpkey", "PATH", UNIFIED_SECTION_PCRPKEY, + "Path to public key for PCR signatures (→ .pcrpkey)"): {} + OPTION_LONG_DATA("profile", "PATH", UNIFIED_SECTION_PROFILE, + "Path to profile file (→ .profile)"): {} + OPTION_LONG_DATA("hwids", "PATH", UNIFIED_SECTION_HWIDS, + "Path to HWIDs file (→ .hwids)"): {} + OPTION_LONG_DATA("efifw", "PATH", UNIFIED_SECTION_EFIFW, + "Path to EFI firmware file (→ .efifw)"): {} + /* Make sure that if new sections are added, the list here is updated. */ + assert_cc(UNIFIED_SECTION_EFIFW + 1 == _UNIFIED_SECTION_MAX); + assert(opt->data < _UNIFIED_SECTION_MAX); + + r = parse_path_argument(arg, /* suppress_root= */ false, arg_sections + opt->data); if (r < 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_public_key && arg_certificate) @@ -387,9 +348,160 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); log_debug("Measuring boot phases: %s", j); + + *ret_args = option_parser_get_args(&state); return 1; } +static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) { + _cleanup_free_ char *s = NULL; + uint32_t v; + int r; + + r = efi_get_variable_string(varname, &s); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to read EFI variable '%s': %m", varname); + + r = safe_atou32(s, &v); + if (r < 0) + return log_error_errno(r, "Failed to parse EFI variable '%s': %s", varname, s); + + if (pcr != v) + log_warning("PCR number reported by stub for %s (%" PRIu32 ") different from our expectation (%" PRIu32 ").\n" + "The measurements are likely inconsistent.", description, v, pcr); + + return 0; +} + +static int validate_stub(void) { + uint64_t features; + bool found = false; + int r; + + if (!tpm2_is_fully_supported()) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Sorry, system lacks full TPM2 support."); + + r = efi_stub_get_features(&features); + if (r < 0) + return log_error_errno(r, "Unable to get stub features: %m"); + + if (!FLAGS_SET(features, EFI_STUB_FEATURE_THREE_PCRS)) + log_warning("Warning: current kernel image does not support measuring itself, the command line or initrd system extension images.\n" + "The PCR measurements seen are unlikely to be valid."); + + r = compare_reported_pcr_nr(TPM2_PCR_KERNEL_BOOT, EFI_LOADER_VARIABLE_STR("StubPcrKernelImage"), "kernel image"); + if (r < 0) + return r; + + STRV_FOREACH(bank, arg_banks) { + _cleanup_free_ char *b = NULL, *p = NULL; + + b = strdup(*bank); + if (!b) + return log_oom(); + + if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/", ascii_strlower(b)) < 0) + return log_oom(); + + if (access(p, F_OK) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to detect if '%s' exists: %m", b); + } else + found = true; + } + + if (!found) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "None of the select PCR banks appear to exist."); + + return 0; +} + +VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Show current PCR values"); +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + r = validate_stub(); + if (r < 0) + return r; + + STRV_FOREACH(bank, arg_banks) { + _cleanup_free_ char *b = NULL, *p = NULL, *s = NULL; + _cleanup_free_ void *h = NULL; + size_t l; + + b = strdup(*bank); + if (!b) + return log_oom(); + + if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%" PRIu32, ascii_strlower(b), (uint32_t) TPM2_PCR_KERNEL_BOOT) < 0) + return log_oom(); + + r = read_virtual_file(p, 4096, &s, NULL); + if (r == -ENOENT) + continue; + if (r < 0) + return log_error_errno(r, "Failed to read '%s': %m", p); + + r = unhexmem(strstrip(s), &h, &l); + if (r < 0) + return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); + + if (!sd_json_format_enabled(arg_json_format_flags)) { + _cleanup_free_ char *f = hexmem(h, l); + if (!f) + return log_oom(); + + if (bank == arg_banks) { + /* before the first line for each PCR, write a short descriptive text to + * stderr, and leave the primary content on stdout */ + fflush(stdout); + fprintf(stderr, "%s# PCR[%" PRIu32 "] %s%s%s\n", + ansi_grey(), + (uint32_t) TPM2_PCR_KERNEL_BOOT, + tpm2_pcr_index_to_string(TPM2_PCR_KERNEL_BOOT), + memeqzero(h, l) ? " (NOT SET!)" : "", + ansi_normal()); + fflush(stderr); + } + + printf("%" PRIu32 ":%s=%s\n", (uint32_t) TPM2_PCR_KERNEL_BOOT, b, f); + + } else { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *bv = NULL, *a = NULL; + + r = sd_json_buildo( + &bv, + SD_JSON_BUILD_PAIR_INTEGER("pcr", TPM2_PCR_KERNEL_BOOT), + SD_JSON_BUILD_PAIR_HEX("hash", h, l)); + if (r < 0) + return log_error_errno(r, "Failed to build JSON object: %m"); + + a = sd_json_variant_ref(sd_json_variant_by_key(v, b)); + + r = sd_json_variant_append_array(&a, bv); + if (r < 0) + return log_error_errno(r, "Failed to append PCR entry to JSON array: %m"); + + r = sd_json_variant_set_field(&v, b, a); + if (r < 0) + return log_error_errno(r, "Failed to add bank info to object: %m"); + } + } + + if (sd_json_format_enabled(arg_json_format_flags)) { + if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) + pager_open(arg_pager_flags); + + sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); + } + + return 0; +} + /* The PCR 11 state for one specific bank */ typedef struct PcrState { char *bank; @@ -421,7 +533,7 @@ static void evp_md_ctx_free_all(EVP_MD_CTX **md[]) { return; for (size_t i = 0; (*md)[i]; i++) - EVP_MD_CTX_free((*md)[i]); + sym_EVP_MD_CTX_free((*md)[i]); *md = mfree(*md); } @@ -438,22 +550,22 @@ static int pcr_state_extend(PcrState *pcr_state, const void *data, size_t sz) { /* Extends a (virtual) PCR by the given data */ - mc = EVP_MD_CTX_new(); + mc = sym_EVP_MD_CTX_new(); if (!mc) return log_oom(); - if (EVP_DigestInit_ex(mc, pcr_state->md, NULL) != 1) + if (sym_EVP_DigestInit_ex(mc, pcr_state->md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize %s context.", pcr_state->bank); /* First thing we do, is hash the old PCR value */ - if (EVP_DigestUpdate(mc, pcr_state->value, pcr_state->value_size) != 1) + if (sym_EVP_DigestUpdate(mc, pcr_state->value, pcr_state->value_size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); /* Then, we hash the new data */ - if (EVP_DigestUpdate(mc, data, sz) != 1) + if (sym_EVP_DigestUpdate(mc, data, sz) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); - if (EVP_DigestFinal_ex(mc, pcr_state->value, &value_size) != 1) + if (sym_EVP_DigestFinal_ex(mc, pcr_state->value, &value_size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context."); assert(value_size == pcr_state->value_size); @@ -521,11 +633,11 @@ static int measure_kernel(PcrState *pcr_states, size_t n) { return log_oom(); for (size_t i = 0; i < n; i++) { - mdctx[i] = EVP_MD_CTX_new(); + mdctx[i] = sym_EVP_MD_CTX_new(); if (!mdctx[i]) return log_oom(); - if (EVP_DigestInit_ex(mdctx[i], pcr_states[i].md, NULL) != 1) + if (sym_EVP_DigestInit_ex(mdctx[i], pcr_states[i].md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize data %s context.", pcr_states[i].bank); } @@ -540,7 +652,7 @@ static int measure_kernel(PcrState *pcr_states, size_t n) { break; for (size_t i = 0; i < n; i++) - if (EVP_DigestUpdate(mdctx[i], buffer, sz) != 1) + if (sym_EVP_DigestUpdate(mdctx[i], buffer, sz) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); m += sz; @@ -560,7 +672,7 @@ static int measure_kernel(PcrState *pcr_states, size_t n) { return log_oom(); /* Measure name of section */ - if (EVP_Digest(unified_sections[c], strlen(unified_sections[c]) + 1, data_hash, &data_hash_size, pcr_states[i].md, NULL) != 1) + if (sym_EVP_Digest(unified_sections[c], strlen(unified_sections[c]) + 1, data_hash, &data_hash_size, pcr_states[i].md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash section name with %s.", pcr_states[i].bank); assert(data_hash_size == (unsigned) pcr_states[i].value_size); @@ -570,7 +682,7 @@ static int measure_kernel(PcrState *pcr_states, size_t n) { return r; /* Retrieve hash of data and measure it */ - if (EVP_DigestFinal_ex(mdctx[i], data_hash, &data_hash_size) != 1) + if (sym_EVP_DigestFinal_ex(mdctx[i], data_hash, &data_hash_size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context."); assert(data_hash_size == (unsigned) pcr_states[i].value_size); @@ -611,7 +723,7 @@ static int measure_phase(PcrState *pcr_states, size_t n, const char *phase) { _cleanup_free_ void *b = NULL; int bsz; - bsz = EVP_MD_size(pcr_states[i].md); + bsz = sym_EVP_MD_get_size(pcr_states[i].md); assert(bsz > 0); b = malloc(bsz); @@ -619,7 +731,7 @@ static int measure_phase(PcrState *pcr_states, size_t n, const char *phase) { return log_oom(); /* First hash the word itself */ - if (EVP_Digest(*word, wl, b, NULL, pcr_states[i].md, NULL) != 1) + if (sym_EVP_Digest(*word, wl, b, NULL, pcr_states[i].md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash word '%s'.", *word); /* And then extend the PCR with the resulting hash */ @@ -636,6 +748,8 @@ static int pcr_states_allocate(PcrState **ret) { _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; size_t n = 0; + assert(ret); + pcr_states = new0(PcrState, strv_length(arg_banks) + 1); if (!pcr_states) return log_oom(); @@ -647,13 +761,13 @@ static int pcr_states_allocate(PcrState **ret) { _cleanup_free_ char *b = NULL; int sz; - assert_se(implementation = EVP_get_digestbyname(*d)); /* Must work, we already checked while parsing command line */ + assert_se(implementation = sym_EVP_get_digestbyname(*d)); /* Must work, we already checked while parsing command line */ - b = strdup(EVP_MD_name(implementation)); + b = strdup(sym_EVP_MD_get0_name(implementation)); if (!b) return log_oom(); - sz = EVP_MD_size(implementation); + sz = sym_EVP_MD_get_size(implementation); if (sz <= 0 || sz >= INT_MAX) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected digest size: %i", sz); @@ -706,7 +820,9 @@ static void pcr_states_restore(PcrState *pcr_states, size_t n) { } } -static int verb_calculate(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_calculate, "calculate", + "Calculate expected PCR values"); +static int verb_calculate(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; int r; @@ -817,13 +933,9 @@ static int build_policy_digest(bool sign) { assert(!strv_isempty(arg_phase)); if (arg_append) { - r = sd_json_parse_file(NULL, arg_append, 0, &v, NULL, NULL); + r = sd_json_parse_file(/* f= */ NULL, arg_append, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) - return log_error_errno(r, "Failed to parse '%s': %m", arg_append); - - if (!sd_json_variant_is_object(v)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "File '%s' is not a valid JSON object, refusing.", arg_append); + return log_error_errno(r, "Failed to parse JSON object '%s': %m", arg_append); } /* When signing/building digest we only support JSON output */ @@ -878,11 +990,11 @@ static int build_policy_digest(bool sign) { if (!pubkeyf) return log_error_errno(errno, "Failed to open public key file '%s': %m", arg_public_key); - pubkey = PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL); + pubkey = sym_PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL); if (!pubkey) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key '%s'.", arg_public_key); } else if (certificate) { - pubkey = X509_get_pubkey(certificate); + pubkey = sym_X509_get_pubkey(certificate); if (!pubkey) return log_error_errno( SYNTHETIC_ERRNO(EIO), @@ -918,7 +1030,7 @@ static int build_policy_digest(bool sign) { for (size_t i = 0; i < n; i++) { PcrState *p = pcr_states + i; - int tpmalg = tpm2_hash_alg_from_string(EVP_MD_name(p->md)); + int tpmalg = tpm2_hash_alg_from_string(sym_EVP_MD_get0_name(p->md)); if (tpmalg < 0) return log_error_errno(tpmalg, "Unsupported PCR bank"); @@ -935,17 +1047,20 @@ static int build_policy_digest(bool sign) { _cleanup_free_ void *sig = NULL; size_t ss = 0; if (privkey) { - r = digest_and_sign(p->md, privkey, pcr_policy_digest.buffer, pcr_policy_digest.size, &sig, &ss); + /* We always use SHA256 for signing currently. Regardless of the bank. */ + const EVP_MD *sha256 = ASSERT_PTR(sym_EVP_get_digestbyname("sha256")); + + r = digest_and_sign(sha256, privkey, pcr_policy_digest.buffer, pcr_policy_digest.size, &sig, &ss); if (r == -EADDRNOTAVAIL) - return log_error_errno(r, "Hash algorithm '%s' not available while signing. (Maybe OS security policy disables this algorithm?)", EVP_MD_name(p->md)); + return log_error_errno(r, "Hash algorithm '%s' not available while signing. (Maybe OS security policy disables this algorithm?)", sym_EVP_MD_get0_name(p->md)); if (r < 0) - return log_error_errno(r, "Failed to sign PCR policy with hash algorithm '%s': %m", EVP_MD_name(p->md)); + return log_error_errno(r, "Failed to sign PCR policy with hash algorithm '%s': %m", sym_EVP_MD_get0_name(p->md)); } _cleanup_free_ void *pubkey_fp = NULL; size_t pubkey_fp_size = 0; if (pubkey) { - r = pubkey_fingerprint(pubkey, EVP_sha256(), &pubkey_fp, &pubkey_fp_size); + r = pubkey_fingerprint(pubkey, sym_EVP_sha256(), &pubkey_fp, &pubkey_fp_size); if (r < 0) return r; } @@ -988,186 +1103,33 @@ static int build_policy_digest(bool sign) { return 0; } -static int verb_sign(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_sign, "sign", + "Calculate and sign expected PCR values"); +static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { return build_policy_digest(/* sign= */ true); } -static int verb_policy_digest(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_policy_digest, "policy-digest", + "Calculate expected TPM2 policy digests"); +static int verb_policy_digest(int argc, char *argv[], uintptr_t _data, void *userdata) { return build_policy_digest(/* sign= */ false); } -static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) { - _cleanup_free_ char *s = NULL; - uint32_t v; - int r; - - r = efi_get_variable_string(varname, &s); - if (r == -ENOENT) - return 0; - if (r < 0) - return log_error_errno(r, "Failed to read EFI variable '%s': %m", varname); - - r = safe_atou32(s, &v); - if (r < 0) - return log_error_errno(r, "Failed to parse EFI variable '%s': %s", varname, s); - - if (pcr != v) - log_warning("PCR number reported by stub for %s (%" PRIu32 ") different from our expectation (%" PRIu32 ").\n" - "The measurements are likely inconsistent.", description, v, pcr); - - return 0; -} - -static int validate_stub(void) { - uint64_t features; - bool found = false; - int r; - - if (!tpm2_is_fully_supported()) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Sorry, system lacks full TPM2 support."); - - r = efi_stub_get_features(&features); - if (r < 0) - return log_error_errno(r, "Unable to get stub features: %m"); - - if (!FLAGS_SET(features, EFI_STUB_FEATURE_THREE_PCRS)) - log_warning("Warning: current kernel image does not support measuring itself, the command line or initrd system extension images.\n" - "The PCR measurements seen are unlikely to be valid."); - - r = compare_reported_pcr_nr(TPM2_PCR_KERNEL_BOOT, EFI_LOADER_VARIABLE_STR("StubPcrKernelImage"), "kernel image"); - if (r < 0) - return r; - - STRV_FOREACH(bank, arg_banks) { - _cleanup_free_ char *b = NULL, *p = NULL; - - b = strdup(*bank); - if (!b) - return log_oom(); - - if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/", ascii_strlower(b)) < 0) - return log_oom(); - - if (access(p, F_OK) < 0) { - if (errno != ENOENT) - return log_error_errno(errno, "Failed to detect if '%s' exists: %m", b); - } else - found = true; - } - - if (!found) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "None of the select PCR banks appear to exist."); - - return 0; -} - -static int verb_status(int argc, char *argv[], void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - int r; - - r = validate_stub(); - if (r < 0) - return r; - - STRV_FOREACH(bank, arg_banks) { - _cleanup_free_ char *b = NULL, *p = NULL, *s = NULL; - _cleanup_free_ void *h = NULL; - size_t l; - - b = strdup(*bank); - if (!b) - return log_oom(); - - if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%" PRIu32, ascii_strlower(b), (uint32_t) TPM2_PCR_KERNEL_BOOT) < 0) - return log_oom(); - - r = read_virtual_file(p, 4096, &s, NULL); - if (r == -ENOENT) - continue; - if (r < 0) - return log_error_errno(r, "Failed to read '%s': %m", p); - - r = unhexmem(strstrip(s), &h, &l); - if (r < 0) - return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); - - if (!sd_json_format_enabled(arg_json_format_flags)) { - _cleanup_free_ char *f = NULL; - - f = hexmem(h, l); - if (!h) - return log_oom(); - - if (bank == arg_banks) { - /* before the first line for each PCR, write a short descriptive text to - * stderr, and leave the primary content on stdout */ - fflush(stdout); - fprintf(stderr, "%s# PCR[%" PRIu32 "] %s%s%s\n", - ansi_grey(), - (uint32_t) TPM2_PCR_KERNEL_BOOT, - tpm2_pcr_index_to_string(TPM2_PCR_KERNEL_BOOT), - memeqzero(h, l) ? " (NOT SET!)" : "", - ansi_normal()); - fflush(stderr); - } - - printf("%" PRIu32 ":%s=%s\n", (uint32_t) TPM2_PCR_KERNEL_BOOT, b, f); - - } else { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *bv = NULL, *a = NULL; - - r = sd_json_buildo( - &bv, - SD_JSON_BUILD_PAIR_INTEGER("pcr", TPM2_PCR_KERNEL_BOOT), - SD_JSON_BUILD_PAIR_HEX("hash", h, l)); - if (r < 0) - return log_error_errno(r, "Failed to build JSON object: %m"); - - a = sd_json_variant_ref(sd_json_variant_by_key(v, b)); - - r = sd_json_variant_append_array(&a, bv); - if (r < 0) - return log_error_errno(r, "Failed to append PCR entry to JSON array: %m"); - - r = sd_json_variant_set_field(&v, b, a); - if (r < 0) - return log_error_errno(r, "Failed to add bank info to object: %m"); - } - } - - if (sd_json_format_enabled(arg_json_format_flags)) { - if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) - pager_open(arg_pager_flags); - - sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); - } - - return 0; -} - -static int measure_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "calculate", VERB_ANY, 1, 0, verb_calculate }, - { "policy-digest", VERB_ANY, 1, 0, verb_policy_digest }, - { "sign", VERB_ANY, 1, 0, verb_sign }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return measure_main(argc, argv); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/measure/meson.build b/src/measure/meson.build index e4e4f579dfa06..ff777dc5d9842 100644 --- a/src/measure/meson.build +++ b/src/measure/meson.build @@ -9,6 +9,6 @@ executables += [ 'HAVE_TPM2', ], 'sources' : files('measure-tool.c'), - 'dependencies' : libopenssl, + 'dependencies' : libopenssl_cflags, }, ] diff --git a/src/modules-load/modules-load.c b/src/modules-load/modules-load.c index f644c9f0f0e27..5435de4e2e988 100644 --- a/src/modules-load/modules-load.c +++ b/src/modules-load/modules-load.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -15,10 +14,12 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "log.h" #include "macro.h" #include "main-func.h" #include "module-util.h" +#include "options.h" #include "ordered-set.h" #include "parse-util.h" #include "pretty-print.h" @@ -331,53 +332,46 @@ static unsigned determine_num_worker_threads(unsigned n_modules) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; + int r; if (terminal_urlify_man("systemd-modules-load.service", "8", &link) < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" - "Loads statically configured kernel modules.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + "Loads statically configured kernel modules.\n\n", + program_invocation_short_name); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + + FOREACH_OPTION(&state, c, /* arg= */ NULL, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } @@ -395,7 +389,8 @@ static int run(int argc, char *argv[]) { char *module; int ret = 0, r; - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -407,9 +402,9 @@ static int run(int argc, char *argv[]) { if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - if (argc > optind) { - for (int i = optind; i < argc; i++) { - r = apply_file_from_path(argv[i], &module_set); + if (!strv_isempty(args)) { + STRV_FOREACH(i, args) { + r = apply_file_from_path(*i, &module_set); if (r < 0) RET_GATHER(ret, r); } @@ -417,7 +412,7 @@ static int run(int argc, char *argv[]) { ConfFile **files = NULL; size_t n_files = 0; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); STRV_FOREACH(i, arg_proc_cmdline_modules) RET_GATHER(ret, modules_list_append_dup(&module_set, *i)); diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index 24b93e1239718..f1c4c90d76883 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -40,7 +40,7 @@ #include "unit-name.h" #include "user-util.h" -enum { +static enum { ACTION_DEFAULT, ACTION_MOUNT, ACTION_AUTOMOUNT, @@ -348,6 +348,7 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to parse timeout: %s", optarg); + arg_timeout_idle_set = true; break; case ARG_AUTOMOUNT_PROPERTY: @@ -1321,10 +1322,8 @@ static int acquire_removable(sd_device *d) { return r; r = device_in_subsystem(d, "block"); - if (r < 0) + if (r <= 0) return r; - if (r == 0) - return 0; } if (parse_boolean(v) <= 0) @@ -1414,9 +1413,9 @@ static int discover_device(void) { if (S_ISREG(st.st_mode)) return discover_loop_backing_file(); - if (!S_ISBLK(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unsupported mount source type for --discover: %s", arg_mount_what); + r = stat_verify_block(&st); + if (r < 0) + return log_error_errno(r, "Unsupported mount source type for --discover: %s", arg_mount_what); r = sd_device_new_from_stat_rdev(&d, &st); if (r < 0) diff --git a/src/mountfsd/mountfsd-manager.c b/src/mountfsd/mountfsd-manager.c index b4c5655c51d15..993dda950108e 100644 --- a/src/mountfsd/mountfsd-manager.c +++ b/src/mountfsd/mountfsd-manager.c @@ -69,6 +69,8 @@ int manager_new(Manager **ret) { _cleanup_(manager_freep) Manager *m = NULL; int r; + assert(ret); + m = new(Manager, 1); if (!m) return -ENOMEM; diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index 7115f33e36be9..54a5203da2cc6 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -158,16 +158,10 @@ static int validate_image_fd(int fd, MountImageParameters *p) { assert(fd >= 0); assert(p); - struct stat st; - if (fstat(fd, &st) < 0) - return -errno; - /* Only support regular files and block devices. Let's use stat_verify_regular() here for the nice - * error numbers it generates. */ - if (!S_ISBLK(st.st_mode)) { - r = stat_verify_regular(&st); - if (r < 0) - return r; - } + /* Only support regular files and block devices. */ + r = fd_verify_regular_or_block(fd); + if (r < 0) + return r; fl = fd_verify_safe_flags_full(fd, O_NONBLOCK); if (fl < 0) @@ -248,7 +242,7 @@ static int verify_trusted_image_fd_by_path(int fd) { if (!filename_is_valid(e)) continue; - r = chaseat(dir_fd, e, CHASE_SAFE|CHASE_TRIGGER_AUTOFS, NULL, &inode_fd); + r = chaseat(XAT_FDROOT, dir_fd, e, CHASE_SAFE|CHASE_TRIGGER_AUTOFS, NULL, &inode_fd); if (r < 0) return log_error_errno(r, "Couldn't verify that specified image '%s' is in search path '%s': %m", p, s); @@ -521,7 +515,7 @@ static int vl_method_mount_image( r = loop_device_make( image_fd, - p.read_only == 0 ? O_RDONLY : O_RDWR, + p.read_only > 0 ? O_RDONLY : -1, 0, UINT64_MAX, UINT32_MAX, @@ -532,7 +526,7 @@ static int vl_method_mount_image( return r; DissectImageFlags dissect_flags = - (p.read_only == 0 ? DISSECT_IMAGE_READ_ONLY : 0) | + (p.read_only > 0 ? DISSECT_IMAGE_READ_ONLY : 0) | (p.growfs != 0 ? DISSECT_IMAGE_GROWFS : 0) | DISSECT_IMAGE_DISCARD_ANY | DISSECT_IMAGE_FSCK | @@ -754,7 +748,7 @@ static int vl_method_mount_image( r = sd_json_variant_append_arraybo( &aj, - SD_JSON_BUILD_PAIR_STRING("designator", partition_designator_to_string(d)), + JSON_BUILD_PAIR_ENUM("designator", partition_designator_to_string(d)), SD_JSON_BUILD_PAIR_BOOLEAN("writable", pp->rw), SD_JSON_BUILD_PAIR_BOOLEAN("growFileSystem", pp->growfs), SD_JSON_BUILD_PAIR_CONDITION(pp->partno > 0, "partitionNumber", SD_JSON_BUILD_INTEGER(pp->partno)), @@ -866,7 +860,8 @@ static DirectoryOwnership validate_directory_fd( r = xstatx_full(fd, /* path= */ NULL, AT_EMPTY_PATH, - /* mandatory_mask= */ STATX_TYPE|STATX_UID|STATX_MNT_ID|STATX_INO, + /* xstatx_flags= */ XSTATX_MNT_ID_BEST, + /* mandatory_mask= */ STATX_TYPE|STATX_UID|STATX_INO, /* optional_mask= */ 0, /* mandatory_attributes= */ STATX_ATTR_MOUNT_ROOT, &stx); @@ -933,7 +928,8 @@ static DirectoryOwnership validate_directory_fd( r = xstatx_full(new_parent_fd, /* path= */ NULL, AT_EMPTY_PATH, - /* mandatory_mask= */ STATX_UID|STATX_MNT_ID|STATX_INO, + /* xstatx_flags= */ XSTATX_MNT_ID_BEST, + /* mandatory_mask= */ STATX_UID|STATX_INO, /* optional_mask= */ 0, /* mandatory_attributes= */ STATX_ATTR_MOUNT_ROOT, &new_stx); @@ -947,7 +943,10 @@ static DirectoryOwnership validate_directory_fd( return DIRECTORY_IS_OTHERWISE_OWNED; } - if (stx.stx_mnt_id != new_stx.stx_mnt_id) { + r = statx_mount_same(&stx, &new_stx); + if (r < 0) + return log_debug_errno(r, "Failed to compare mount IDs: %m"); + if (!r) { /* NB, this check is probably redundant, given we also check * STATX_ATTR_MOUNT_ROOT. The only reason we have it here is to provide extra safety * in case the mount tree is rearranged concurrently with our traversal, so that diff --git a/src/mute-console/mute-console.c b/src/mute-console/mute-console.c index b18e79d622c2e..b40f86fafb877 100644 --- a/src/mute-console/mute-console.c +++ b/src/mute-console/mute-console.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" @@ -15,8 +14,10 @@ #include "bus-util.h" #include "daemon-util.h" #include "errno-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "printk-util.h" @@ -30,79 +31,60 @@ static bool arg_varlink = false; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-mute-console", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...]\n" - "\n%sMute status output to the console.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --kernel=BOOL Mute kernel log output\n" - " --pid1=BOOL Mute PID 1 status output\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n\n" + "%sMute status output to the console.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_KERNEL, - ARG_PID1, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "kernel", required_argument, NULL, ARG_KERNEL }, - { "pid1", required_argument, NULL, ARG_PID1 }, - {} - }; - - int c, r; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_PID1: - r = parse_boolean_argument("--pid1=", optarg, &arg_mute_pid1); + OPTION_LONG("kernel", "BOOL", "Mute kernel log output"): + r = parse_boolean_argument("--kernel=", arg, &arg_mute_kernel); if (r < 0) return r; - break; - case ARG_KERNEL: - r = parse_boolean_argument("--kernel=", optarg, &arg_mute_kernel); + OPTION_LONG("pid1", "BOOL", "Mute PID 1 status output"): + r = parse_boolean_argument("--pid1=", arg, &arg_mute_pid1); if (r < 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); if (r < 0) diff --git a/src/network/bpf/sysctl-monitor/meson.build b/src/network/bpf/sysctl-monitor/meson.build deleted file mode 100644 index 8b0de886743f1..0000000000000 --- a/src/network/bpf/sysctl-monitor/meson.build +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -if conf.get('ENABLE_SYSCTL_BPF') != 1 - subdir_done() -endif - -sysctl_monitor_bpf_o_unstripped = custom_target( - input : 'sysctl-monitor.bpf.c', - output : 'sysctl-monitor.bpf.unstripped.o', - command : bpf_o_unstripped_cmd, - depends : vmlinux_h_dependency) - -sysctl_monitor_bpf_o = custom_target( - input : sysctl_monitor_bpf_o_unstripped, - output : 'sysctl-monitor.bpf.o', - command : bpf_o_cmd) - -sysctl_monitor_skel_h = custom_target( - input : sysctl_monitor_bpf_o, - output : 'sysctl-monitor.skel.h', - command : skel_h_cmd, - capture : true) - -generated_sources += sysctl_monitor_skel_h diff --git a/src/network/bpf/sysctl-monitor/sysctl-monitor-skel.h b/src/network/bpf/sysctl-monitor/sysctl-monitor-skel.h deleted file mode 100644 index e1ebc3e509a2c..0000000000000 --- a/src/network/bpf/sysctl-monitor/sysctl-monitor-skel.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -/* The SPDX header above is actually correct in claiming this was - * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that - * compatible with GPL we will claim this to be GPL however, which should be - * fine given that LGPL-2.1-or-later downgrades to GPL if needed. - */ - -#include "bpf-dlopen.h" /* IWYU pragma: keep */ - -/* libbpf is used via dlopen(), so rename symbols */ -#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton -#define bpf_object__load_skeleton sym_bpf_object__load_skeleton -#define bpf_object__open_skeleton sym_bpf_object__open_skeleton - -#include "bpf/sysctl-monitor/sysctl-monitor.skel.h" /* IWYU pragma: export */ diff --git a/src/network/generator/network-generator-main.c b/src/network/generator/network-generator-main.c index c449a6d305baf..f9acf405006ef 100644 --- a/src/network/generator/network-generator-main.c +++ b/src/network/generator/network-generator-main.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" @@ -8,15 +7,18 @@ #include "creds-util.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" #include "fs-util.h" #include "generator.h" #include "log.h" #include "main-func.h" #include "mkdir.h" #include "network-generator.h" +#include "options.h" #include "path-util.h" #include "proc-cmdline.h" #include "string-util.h" +#include "strv.h" #define NETWORK_UNIT_DIRECTORY "/run/systemd/network/" @@ -148,52 +150,47 @@ static int context_save(Context *context) { } static int help(void) { - printf("%s [OPTIONS...] [-- KERNEL_CMDLINE]\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --root=PATH Operate on an alternate filesystem root\n", + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] [-- KERNEL_CMDLINE]\n\n", program_invocation_short_name); + r = table_print_or_warn(options); + if (r < 0) + return r; + return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_ROOT, - }; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "root", required_argument, NULL, ARG_ROOT }, - {}, - }; - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ROOT: - arg_root = optarg; + OPTION_LONG("root", "PATH", + "Operate on an alternate filesystem root"): + arg_root = arg; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } @@ -205,20 +202,21 @@ static int run(int argc, char *argv[]) { umask(0022); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - if (optind >= argc) { + if (strv_isempty(args)) { r = proc_cmdline_parse(parse_cmdline_item, &context, 0); if (r < 0) return log_warning_errno(r, "Failed to parse kernel command line: %m"); } else { - for (int i = optind; i < argc; i++) { + STRV_FOREACH(a, args) { _cleanup_free_ char *word = NULL; char *value; - word = strdup(argv[i]); + word = strdup(*a); if (!word) return log_oom(); @@ -233,6 +231,8 @@ static int run(int argc, char *argv[]) { } } + context_finalize_bootif(&context); + r = context_merge_networks(&context); if (r < 0) return log_warning_errno(r, "Failed to merge multiple command line options: %m"); diff --git a/src/network/generator/network-generator.c b/src/network/generator/network-generator.c index 0988aef93f14b..5e322f41eae93 100644 --- a/src/network/generator/network-generator.c +++ b/src/network/generator/network-generator.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include "alloc-util.h" #include "extract-word.h" @@ -25,6 +26,8 @@ rd.route=/:[:] nameserver= [nameserver= ...] rd.peerdns=0 + BOOTIF= + rd.bootif=0 # Causes BOOTIF= to be ignored. # .link ifname=: @@ -38,8 +41,6 @@ # ignored bootdev= - BOOTIF= - rd.bootif=0 biosdevname=0 rd.neednet=1 */ @@ -119,6 +120,7 @@ static int address_new( assert(network); assert(IN_SET(family, AF_INET, AF_INET6)); assert(addr); + POINTER_MAY_BE_NULL(peer); address = new(Address, 1); if (!address) @@ -514,6 +516,40 @@ static int network_set_mac_address(Context *context, const char *ifname, const c return 0; } +static int network_set_bootif_mac_address(Context *context, const char *mac) { + int r; + + assert(context); + + if (isempty(mac)) + return 0; + + /* "BOOTIF" is a special placeholder interface name, used to configure the + * interface referred to by BOOTIF=. I.e., ip=...:BOOTIF:... is valid if and + * only if BOOTIF= is also set. */ + Network *network; + r = network_acquire(context, "BOOTIF", &network); + if (r < 0) + return log_debug_errno(r, "Failed to acquire network for BOOTIF: %m"); + + r = parse_hw_addr(mac, &network->match_mac); + if (r < 0) { + /* PXE bootloaders may provide the MAC with a "hardware type prefix", e.g. + * 01-12:34:56:78:90:ab, where 01 indicates ethernet. Technically, other + * hardware types are possible, but only ethernet is handled here. */ + const char *p = startswith(mac, "01-"); + if (p) + r = parse_hw_addr(p, &network->match_mac); + } + if (r < 0) + return log_debug_errno(r, "Invalid MAC address '%s' for BOOTIF", mac); + + if (!hw_addr_is_valid(&network->match_mac, ARPHRD_ETHER)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid MAC address '%s' for BOOTIF", mac); + + return 0; +} + static int network_set_address( Context *context, const char *ifname, @@ -1250,6 +1286,38 @@ static int parse_cmdline_ifname_policy(Context *context, const char *key, const return 0; } +static int parse_cmdline_rd_bootif(Context *context, const char *key, const char *value) { + int r; + + assert(context); + assert(key); + + /* rd.bootif= */ + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = value ? parse_boolean(value) : true; + if (r < 0) + return log_debug_errno(r, "Invalid boolean value '%s'", value); + + /* rd.bootif=0 => skip BOOTIF= parsing */ + context->skip_bootif = !r; + return 0; +} + +static int parse_cmdline_bootif_mac(Context *context, const char *key, const char *value) { + assert(context); + assert(key); + + /* BOOTIF= */ + + if (proc_cmdline_value_missing(key, value)) + return 0; + + return network_set_bootif_mac_address(context, value); +} + int parse_cmdline_item(const char *key, const char *value, void *data) { Context *context = ASSERT_PTR(data); @@ -1273,10 +1341,34 @@ int parse_cmdline_item(const char *key, const char *value, void *data) { return parse_cmdline_ifname(context, key, value); if (proc_cmdline_key_streq(key, "net.ifname_policy")) return parse_cmdline_ifname_policy(context, key, value); + if (proc_cmdline_key_streq(key, "rd.bootif")) + return parse_cmdline_rd_bootif(context, key, value); + if (proc_cmdline_key_streq(key, "BOOTIF")) + return parse_cmdline_bootif_mac(context, key, value); return 0; } +void context_finalize_bootif(Context *context) { + assert(context); + + Network *network = hashmap_get(context->networks_by_name, "BOOTIF"); + if (!network) + return; + + /* rd.bootif=0 disables BOOTIF= handling */ + if (context->skip_bootif) { + network_free(hashmap_remove_value(context->networks_by_name, "BOOTIF", network)); + return; + } + + if (hw_addr_is_null(&network->match_mac)) { + log_debug("Expected MAC address for BOOTIF, but BOOTIF= is unset."); + network_free(hashmap_remove_value(context->networks_by_name, "BOOTIF", network)); + return; + } +} + int context_merge_networks(Context *context) { Network *all, *network; int r; @@ -1368,6 +1460,8 @@ void network_dump(Network *network, FILE *f) { * physical interfaces. */ fputs("Kind=!*\n" "Type=!loopback\n", f); + else if (streq(network->ifname, "BOOTIF")) + fprintf(f, "MACAddress=%s\n", HW_ADDR_TO_STR(&network->match_mac)); else fprintf(f, "Name=%s\n", network->ifname); @@ -1439,12 +1533,11 @@ void netdev_dump(NetDev *netdev, FILE *f) { if (netdev->mtu > 0) fprintf(f, "MTUBytes=%" PRIu32 "\n", netdev->mtu); - if (streq(netdev->kind, "vlan")) { + if (streq(netdev->kind, "vlan")) fprintf(f, "\n[VLAN]\n" "Id=%u\n", netdev->vlan_id); - } } void link_dump(Link *link, FILE *f) { diff --git a/src/network/generator/network-generator.h b/src/network/generator/network-generator.h index e40aa42ff7e27..95a6379e4be4e 100644 --- a/src/network/generator/network-generator.h +++ b/src/network/generator/network-generator.h @@ -54,6 +54,8 @@ struct Route { struct Network { /* [Match] */ char *ifname; + /* Parsed from BOOTIF= parameter. */ + struct hw_addr_data match_mac; /* [Link] */ struct ether_addr mac; @@ -101,11 +103,15 @@ typedef struct Context { Hashmap *networks_by_name; Hashmap *netdevs_by_name; Hashmap *links_by_filename; + + /* If rd.bootif=0, ignore BOOTIF= parsing */ + bool skip_bootif; } Context; int parse_cmdline_item(const char *key, const char *value, void *data); int context_merge_networks(Context *context); void context_clear(Context *context); +void context_finalize_bootif(Context *context); Network *network_get(Context *context, const char *ifname); void network_dump(Network *network, FILE *f); diff --git a/src/network/generator/test-network-generator.c b/src/network/generator/test-network-generator.c index ff2187755345b..b62f13cc041b0 100644 --- a/src/network/generator/test-network-generator.c +++ b/src/network/generator/test-network-generator.c @@ -28,12 +28,39 @@ static void test_network_two(const char *ifname, ASSERT_OK(parse_cmdline_item(key1, value1, &context)); ASSERT_OK(parse_cmdline_item(key2, value2, &context)); + context_finalize_bootif(&context); ASSERT_OK(context_merge_networks(&context)); ASSERT_NOT_NULL((network = network_get(&context, ifname))); ASSERT_OK(network_format(network, &output)); ASSERT_STREQ(output, expected); } +static void test_network_three(const char *ifname, + const char *key1, const char *value1, + const char *key2, const char *value2, + const char *key3, const char *value3, + const char *expected) { + _cleanup_(context_clear) Context context = {}; + _cleanup_free_ char *output = NULL; + Network *network; + + log_debug("/* %s(%s=%s, %s=%s, %s=%s) */", __func__, key1, value1, key2, value2, key3, value3); + + ASSERT_OK(parse_cmdline_item(key1, value1, &context)); + ASSERT_OK(parse_cmdline_item(key2, value2, &context)); + ASSERT_OK(parse_cmdline_item(key3, value3, &context)); + context_finalize_bootif(&context); + ASSERT_OK(context_merge_networks(&context)); + + network = network_get(&context, ifname); + if (expected) { + ASSERT_NOT_NULL(network); + ASSERT_OK(network_format(network, &output)); + ASSERT_STREQ(output, expected); + } else + ASSERT_NULL(network); +} + static void test_netdev_one(const char *ifname, const char *key, const char *value, const char *expected) { _cleanup_(context_clear) Context context = {}; _cleanup_free_ char *output = NULL; @@ -573,5 +600,61 @@ int main(int argc, char *argv[]) { "Gateway=192.168.0.1\n" ); + test_network_two("BOOTIF", + "ip", "::::hogehoge:BOOTIF:dhcp", + "BOOTIF", "01-00:11:22:33:44:55", + "[Match]\n" + "MACAddress=00:11:22:33:44:55\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=ipv4\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + ); + + test_network_two("BOOTIF", + "ip", "::::hogehoge:BOOTIF:dhcp", + "BOOTIF", "00:11:22:33:44:55", + "[Match]\n" + "MACAddress=00:11:22:33:44:55\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=ipv4\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + ); + + test_network_two("BOOTIF", + "ip", "::::hogehoge:BOOTIF:dhcp", + "BOOTIF", "01-00-11-22-33-44-55", + "[Match]\n" + "MACAddress=00:11:22:33:44:55\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=ipv4\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + ); + + test_network_three("BOOTIF", + "ip", "::::hogehoge:BOOTIF:dhcp", + "BOOTIF", "01-00:11:22:33:44:55", + "rd.bootif", "1", + "[Match]\n" + "MACAddress=00:11:22:33:44:55\n" + "\n[Link]\n" + "\n[Network]\n" + "DHCP=ipv4\n" + "\n[DHCP]\n" + "Hostname=hogehoge\n" + ); + + test_network_three("BOOTIF", + "ip", "::::hogehoge:BOOTIF:dhcp", + "BOOTIF", "01-00:11:22:33:44:55", + "rd.bootif", "0", + NULL + ); + return 0; } diff --git a/src/network/meson.build b/src/network/meson.build index 85b57669e4602..134416bcc8dd8 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -1,7 +1,5 @@ # SPDX-License-Identifier: LGPL-2.1-or-later -subdir('bpf/sysctl-monitor') - systemd_networkd_sources = files( 'networkd.c' ) @@ -144,10 +142,6 @@ networkctl_sources = files( networkd_network_gperf_gperf = files('networkd-network-gperf.gperf') networkd_netdev_gperf_gperf = files('netdev/netdev-gperf.gperf') -if conf.get('ENABLE_SYSCTL_BPF') == 1 - systemd_networkd_extract_sources += sysctl_monitor_skel_h -endif - networkd_gperf_c = custom_target( input : 'networkd-gperf.gperf', output : 'networkd-gperf.c', @@ -209,6 +203,9 @@ executables += [ networkd_link_with, ], 'dependencies' : threads, + 'bpf_programs': [ + 'sysctl-monitor', + ] }, libexec_template + { 'name' : 'systemd-networkd-wait-online', @@ -258,6 +255,12 @@ executables += [ network_test_template + { 'sources' : files('test-networkd-util.c'), }, + test_template + { + 'sources' : files('test-modem-manager-mock.c'), + 'conditions' : ['ENABLE_NETWORKD'], + 'link_with' : [libshared], + 'type' : 'manual', + }, network_fuzz_template + { 'sources' : files('fuzz-netdev-parser.c'), }, diff --git a/src/network/netdev/bond.c b/src/network/netdev/bond.c index 0ae5fbdc43004..38f06d43b7fbd 100644 --- a/src/network/netdev/bond.c +++ b/src/network/netdev/bond.c @@ -73,7 +73,7 @@ static int netdev_bond_fill_message_create(NetDev *netdev, Link *link, sd_netlin return r; } - bool up = link && FLAGS_SET(link->flags, IFF_UP); + bool up = link && link_is_up(link); bool has_slaves = link && !set_isempty(link->slaves); if (b->mode != _NETDEV_BOND_MODE_INVALID && !up && !has_slaves) { diff --git a/src/network/netdev/fou-tunnel.c b/src/network/netdev/fou-tunnel.c index 786ac9e0dd4c6..e2c5525266039 100644 --- a/src/network/netdev/fou-tunnel.c +++ b/src/network/netdev/fou-tunnel.c @@ -86,6 +86,7 @@ static int netdev_create_fou_tunnel_message(NetDev *netdev, sd_netlink_message * assert(netdev); assert(netdev->manager); + assert(ret); r = sd_genl_message_new(netdev->manager->genl, FOU_GENL_NAME, FOU_CMD_ADD, &m); if (r < 0) diff --git a/src/network/netdev/l2tp-tunnel.c b/src/network/netdev/l2tp-tunnel.c index 1afe109c1b108..fad6f20a4295d 100644 --- a/src/network/netdev/l2tp-tunnel.c +++ b/src/network/netdev/l2tp-tunnel.c @@ -104,6 +104,7 @@ static int netdev_l2tp_create_message_tunnel(NetDev *netdev, union in_addr_union assert(local_address); assert(netdev); assert(netdev->manager); + assert(ret); _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; uint16_t encap_type; @@ -200,6 +201,7 @@ static int netdev_l2tp_create_message_session(NetDev *netdev, L2tpSession *sessi assert(netdev->manager); assert(session); assert(session->tunnel); + assert(ret); r = sd_genl_message_new(netdev->manager->genl, L2TP_GENL_NAME, L2TP_CMD_SESSION_CREATE, &m); if (r < 0) diff --git a/src/network/netdev/macsec.c b/src/network/netdev/macsec.c index 0b999b4b8a6c0..1d6aee249b911 100644 --- a/src/network/netdev/macsec.c +++ b/src/network/netdev/macsec.c @@ -125,6 +125,7 @@ static int macsec_receive_channel_new(MACsec *s, uint64_t sci, ReceiveChannel ** ReceiveChannel *c; assert(s); + assert(ret); c = new(ReceiveChannel, 1); if (!c) @@ -238,6 +239,7 @@ static int netdev_macsec_create_message(NetDev *netdev, int command, sd_netlink_ assert(netdev); assert(netdev->ifindex > 0); assert(netdev->manager); + assert(ret); r = sd_genl_message_new(netdev->manager->genl, MACSEC_GENL_NAME, command, &m); if (r < 0) diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf index 250b6cf7bcde9..269a974542408 100644 --- a/src/network/netdev/netdev-gperf.gperf +++ b/src/network/netdev/netdev-gperf.gperf @@ -249,7 +249,7 @@ Bridge.MulticastIGMPVersion, config_parse_bridge_igmp_version, Bridge.FDBMaxLearned, config_parse_bridge_fdb_max_learned, 0, offsetof(Bridge, fdb_max_learned) Bridge.LinkLocalLearning, config_parse_tristate, 0, offsetof(Bridge, linklocal_learn) VRF.TableId, config_parse_uint32, 0, offsetof(Vrf, table) /* deprecated */ -VRF.Table, config_parse_uint32, 0, offsetof(Vrf, table) +VRF.Table, config_parse_vrf_table, 0, offsetof(Vrf, table) BareUDP.DestinationPort, config_parse_ip_port, 0, offsetof(BareUDP, dest_port) BareUDP.MinSourcePort, config_parse_ip_port, 0, offsetof(BareUDP, min_port) BareUDP.EtherType, config_parse_bare_udp_iftype, 0, offsetof(BareUDP, iftype) diff --git a/src/network/netdev/netdev.c b/src/network/netdev/netdev.c index 393114aa7bae4..bde8d2db05fb3 100644 --- a/src/network/netdev/netdev.c +++ b/src/network/netdev/netdev.c @@ -277,6 +277,7 @@ static int netdev_attach_name_full(NetDev *netdev, const char *name, Hashmap **n assert(netdev); assert(name); + assert(netdevs); r = hashmap_ensure_put(netdevs, &string_hash_ops, name, netdev); if (r == -ENOMEM) diff --git a/src/network/netdev/vrf.c b/src/network/netdev/vrf.c index 540c269f2d38d..d91ccf394ae32 100644 --- a/src/network/netdev/vrf.c +++ b/src/network/netdev/vrf.c @@ -4,8 +4,40 @@ #include "sd-netlink.h" +#include "networkd-route-util.h" #include "vrf.h" +int config_parse_vrf_table( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Vrf *vrf = ASSERT_PTR(userdata); + uint32_t *table = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = manager_get_route_table_from_string(vrf->meta.manager, rvalue, table); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + return 0; +} + static int netdev_vrf_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) { assert(!link); assert(m); diff --git a/src/network/netdev/vrf.h b/src/network/netdev/vrf.h index 7bf94478567a7..a794c237c98de 100644 --- a/src/network/netdev/vrf.h +++ b/src/network/netdev/vrf.h @@ -11,3 +11,5 @@ typedef struct Vrf { DEFINE_NETDEV_CAST(VRF, Vrf); extern const NetDevVTable vrf_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_vrf_table); diff --git a/src/network/networkctl-address-label.c b/src/network/networkctl-address-label.c index 739c1d2e83964..c1e2cc0920278 100644 --- a/src/network/networkctl-address-label.c +++ b/src/network/networkctl-address-label.c @@ -82,14 +82,10 @@ static int dump_address_labels(sd_netlink *rtnl) { return table_log_add_error(r); } - r = table_print(table, NULL); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } -int list_address_labels(int argc, char *argv[], void *userdata) { +int verb_list_address_labels(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; int r; diff --git a/src/network/networkctl-address-label.h b/src/network/networkctl-address-label.h index eb3c722744f1e..0afbd2b2bc5ce 100644 --- a/src/network/networkctl-address-label.h +++ b/src/network/networkctl-address-label.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int list_address_labels(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_list_address_labels(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/network/networkctl-config-file.c b/src/network/networkctl-config-file.c index 9f0236b5406a6..43729c37653db 100644 --- a/src/network/networkctl-config-file.c +++ b/src/network/networkctl-config-file.c @@ -2,17 +2,12 @@ #include -#include "sd-bus.h" #include "sd-daemon.h" #include "sd-device.h" #include "sd-netlink.h" #include "sd-network.h" #include "alloc-util.h" -#include "bus-error.h" -#include "bus-locator.h" -#include "bus-util.h" -#include "bus-wait-for-jobs.h" #include "conf-files.h" #include "edit-util.h" #include "errno-util.h" @@ -395,48 +390,8 @@ static int add_config_to_edit( return edit_files_add(context, dropin_path, old_dropin, comment_paths); } -static int udevd_reload(sd_bus *bus) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - const char *job_path; - int r; - - assert(bus); - - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_error_errno(r, "Could not watch jobs: %m"); - - r = bus_call_method(bus, - bus_systemd_mgr, - "ReloadUnit", - &error, - &reply, - "ss", - "systemd-udevd.service", - "replace"); - if (r < 0) - return log_error_errno(r, "Failed to reload systemd-udevd: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "o", &job_path); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_wait_for_jobs_one(w, job_path, /* flags= */ 0, NULL); - if (r == -ENOEXEC) { - log_debug("systemd-udevd is not running, skipping reload."); - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to reload systemd-udevd: %m"); - - return 1; -} - static int reload_daemons(ReloadFlags flags) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r, ret = 1; + int ret = 1; if (arg_no_reload) return 0; @@ -449,28 +404,20 @@ static int reload_daemons(ReloadFlags flags) { return 0; } - r = sd_bus_open_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to connect to system bus: %m"); - if (FLAGS_SET(flags, RELOAD_UDEVD)) - RET_GATHER(ret, udevd_reload(bus)); + RET_GATHER(ret, reload_udevd()); if (FLAGS_SET(flags, RELOAD_NETWORKD)) { - if (networkd_is_running()) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL); - if (r < 0) - RET_GATHER(ret, log_error_errno(r, "Failed to reload systemd-networkd: %s", bus_error_message(&error, r))); - } else + if (!networkd_is_running()) log_debug("systemd-networkd is not running, skipping reload."); + else + RET_GATHER(ret, reload_networkd()); } return ret; } -int verb_edit(int argc, char *argv[], void *userdata) { +int verb_edit(int argc, char *argv[], uintptr_t _data, void *userdata) { char **args = ASSERT_PTR(strv_skip(argv, 1)); _cleanup_(edit_file_context_done) EditFileContext context = { .marker_start = DROPIN_MARKER_START, @@ -630,7 +577,7 @@ static int cat_files_by_link_config(const char *link_config, sd_netlink **rtnl, return cat_files_by_link_one(ifname, type, rtnl, /* ignore_missing= */ false, first); } -int verb_cat(int argc, char *argv[], void *userdata) { +int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; char **args = strv_skip(argv, 1); int r, ret = 0; @@ -683,7 +630,7 @@ int verb_cat(int argc, char *argv[], void *userdata) { return ret; } -int verb_mask(int argc, char *argv[], void *userdata) { +int verb_mask(int argc, char *argv[], uintptr_t _data, void *userdata) { ReloadFlags flags = 0; int r; @@ -747,7 +694,7 @@ int verb_mask(int argc, char *argv[], void *userdata) { return reload_daemons(flags); } -int verb_unmask(int argc, char *argv[], void *userdata) { +int verb_unmask(int argc, char *argv[], uintptr_t _data, void *userdata) { ReloadFlags flags = 0; int r; diff --git a/src/network/networkctl-config-file.h b/src/network/networkctl-config-file.h index 38210a8093b32..8d52d58ffcbe9 100644 --- a/src/network/networkctl-config-file.h +++ b/src/network/networkctl-config-file.h @@ -1,8 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_edit(int argc, char *argv[], void *userdata); -int verb_cat(int argc, char *argv[], void *userdata); +#include "shared-forward.h" -int verb_mask(int argc, char *argv[], void *userdata); -int verb_unmask(int argc, char *argv[], void *userdata); +int verb_edit(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata); + +int verb_mask(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_unmask(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/network/networkctl-link-info.c b/src/network/networkctl-link-info.c index 8f072f834ac70..0b40b442537ac 100644 --- a/src/network/networkctl-link-info.c +++ b/src/network/networkctl-link-info.c @@ -2,13 +2,10 @@ #include -#include "sd-bus.h" +#include "sd-json.h" #include "sd-netlink.h" #include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-error.h" -#include "bus-util.h" #include "device-util.h" #include "fd-util.h" #include "glob-util.h" @@ -28,6 +25,7 @@ LinkInfo* link_info_array_free(LinkInfo *array) { for (unsigned i = 0; array && array[i].needs_freeing; i++) { sd_device_unref(array[i].sd_device); + sd_json_variant_unref(array[i].description); free(array[i].netdev_kind); free(array[i].ssid); free(array[i].qdisc); @@ -280,33 +278,31 @@ static int decode_link( return 1; } -static int acquire_link_bitrates(sd_bus *bus, LinkInfo *link) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; +static int acquire_link_bitrates(LinkInfo *link) { int r; - assert(bus); assert(link); - r = link_get_property(bus, link->ifindex, &error, &reply, "org.freedesktop.network1.Link", "BitRates", "(tt)"); - if (r < 0) { - bool quiet = sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY, - BUS_ERROR_SPEED_METER_INACTIVE); - - return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, - r, "Failed to query link bit rates: %s", bus_error_message(&error, r)); - } - - r = sd_bus_message_read(reply, "(tt)", &link->tx_bitrate, &link->rx_bitrate); + sd_json_variant *v; + r = json_variant_find_object(link->description, STRV_MAKE("Interface", "BitRates"), &v); + if (r == -ENODATA) + return 0; if (r < 0) - return bus_log_parse_error(r); + return r; - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); + static const sd_json_dispatch_field dispatch_table[] = { + { "TxBitRate", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LinkInfo, tx_bitrate), SD_JSON_MANDATORY }, + { "RxBitRate", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LinkInfo, rx_bitrate), SD_JSON_MANDATORY }, + {} + }; - link->has_bitrates = link->tx_bitrate != UINT64_MAX && link->rx_bitrate != UINT64_MAX; + r = sd_json_dispatch(v, dispatch_table, + SD_JSON_LOG | SD_JSON_WARNING | SD_JSON_ALLOW_EXTENSIONS, + link); + if (r < 0) + return r; + link->has_bitrates = true; return 0; } @@ -356,7 +352,7 @@ static void acquire_wlan_link_info(LinkInfo *link) { link->has_wlan_link_info = r > 0 || k > 0; } -int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char * const *patterns, LinkInfo **ret) { +int acquire_link_info(sd_varlink *vl, sd_netlink *rtnl, char * const *patterns, LinkInfo **ret) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; _cleanup_(link_info_array_freep) LinkInfo *links = NULL; _cleanup_free_ bool *matched_patterns = NULL; @@ -402,6 +398,10 @@ int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char * const *patterns, Lin acquire_ether_link_info(&fd, &links[c]); acquire_wlan_link_info(&links[c]); + if (vl) + (void) acquire_link_description(vl, links[c].ifindex, &links[c].description); + (void) acquire_link_bitrates(&links[c]); + c++; } @@ -421,10 +421,6 @@ int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char * const *patterns, Lin typesafe_qsort(links, c, link_info_compare); - if (bus) - FOREACH_ARRAY(link, links, c) - (void) acquire_link_bitrates(bus, link); - *ret = TAKE_PTR(links); if (patterns && c == 0) diff --git a/src/network/networkctl-link-info.h b/src/network/networkctl-link-info.h index 268798dc7fa1f..ebea57348a30d 100644 --- a/src/network/networkctl-link-info.h +++ b/src/network/networkctl-link-info.h @@ -35,6 +35,7 @@ typedef struct LinkInfo { char name[IFNAMSIZ+1]; char *netdev_kind; sd_device *sd_device; + sd_json_variant *description; int ifindex; unsigned short iftype; struct hw_addr_data hw_address; @@ -133,4 +134,4 @@ typedef struct LinkInfo { LinkInfo* link_info_array_free(LinkInfo *array); DEFINE_TRIVIAL_CLEANUP_FUNC(LinkInfo*, link_info_array_free); -int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char * const *patterns, LinkInfo **ret); +int acquire_link_info(sd_varlink *vl, sd_netlink *rtnl, char * const *patterns, LinkInfo **ret); diff --git a/src/network/networkctl-list.c b/src/network/networkctl-list.c index 1ac6ad39a8a7e..6f3379e788d80 100644 --- a/src/network/networkctl-list.c +++ b/src/network/networkctl-list.c @@ -12,7 +12,7 @@ #include "networkctl-list.h" #include "networkctl-util.h" -int list_links(int argc, char *argv[], void *userdata) { +int verb_list_links(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; _cleanup_(link_info_array_freep) LinkInfo *links = NULL; _cleanup_(table_unrefp) Table *table = NULL; @@ -79,9 +79,9 @@ int list_links(int argc, char *argv[], void *userdata) { return table_log_add_error(r); } - r = table_print(table, NULL); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; if (arg_legend) printf("\n%i links listed.\n", c); diff --git a/src/network/networkctl-list.h b/src/network/networkctl-list.h index 0ee8dba8941c7..955797ea2f0b7 100644 --- a/src/network/networkctl-list.h +++ b/src/network/networkctl-list.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int list_links(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_list_links(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/network/networkctl-lldp.c b/src/network/networkctl-lldp.c index 300f7b26df975..009f70e4d7fc0 100644 --- a/src/network/networkctl-lldp.c +++ b/src/network/networkctl-lldp.c @@ -219,7 +219,7 @@ static int dump_lldp_neighbors_json(sd_json_variant *reply, char * const *patter return sd_json_variant_dump(v, arg_json_format_flags, NULL, NULL); } -int link_lldp_status(int argc, char *argv[], void *userdata) { +int verb_link_lldp_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; _cleanup_(table_unrefp) Table *table = NULL; sd_json_variant *reply; @@ -303,9 +303,9 @@ int link_lldp_status(int argc, char *argv[], void *userdata) { } } - r = table_print(table, NULL); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; if (arg_legend) { lldp_capabilities_legend(all); diff --git a/src/network/networkctl-lldp.h b/src/network/networkctl-lldp.h index 2225fc3abdc53..b1536fc36d7b3 100644 --- a/src/network/networkctl-lldp.h +++ b/src/network/networkctl-lldp.h @@ -4,4 +4,4 @@ #include "shared-forward.h" int dump_lldp_neighbors(sd_varlink *vl, Table *table, int ifindex); -int link_lldp_status(int argc, char *argv[], void *userdata); +int verb_link_lldp_status(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/network/networkctl-misc.c b/src/network/networkctl-misc.c index 73c79494a073b..0436346bc863e 100644 --- a/src/network/networkctl-misc.c +++ b/src/network/networkctl-misc.c @@ -1,10 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-bus.h" #include "sd-netlink.h" -#include "bus-error.h" -#include "bus-locator.h" #include "bus-util.h" #include "errno-util.h" #include "fd-util.h" @@ -46,36 +43,7 @@ static int parse_interfaces(sd_netlink **rtnl, char *argv[], OrderedSet **ret) { return 0; } -int link_up_down(int argc, char *argv[], void *userdata) { - int r, ret = 0; - - bool up = streq_ptr(argv[0], "up"); - - _cleanup_ordered_set_free_ OrderedSet *indexes = NULL; - r = parse_interfaces(/* rtnl= */ NULL, argv, &indexes); - if (r < 0) - return r; - - _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; - r = varlink_connect_networkd(&vl); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - void *p; - ORDERED_SET_FOREACH(p, indexes) - RET_GATHER(ret, varlink_callbo_and_log( - vl, - up ? "io.systemd.Network.Link.Up" : "io.systemd.Network.Link.Down", - /* reply= */ NULL, - SD_JSON_BUILD_PAIR_INTEGER("InterfaceIndex", PTR_TO_INT(p)), - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password))); - - return ret; -} - -int link_delete(int argc, char *argv[], void *userdata) { +int verb_link_delete(int argc, char *argv[], uintptr_t _data, void *userdata) { int r, ret = 0; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; @@ -104,25 +72,26 @@ int link_delete(int argc, char *argv[], void *userdata) { return ret; } -int link_bus_simple_method(int argc, char *argv[], void *userdata) { +int verb_link_varlink_simple_method(int argc, char *argv[], uintptr_t _data, void *userdata) { int r, ret = 0; - typedef struct LinkBusAction { + typedef struct LinkVarlinkAction { const char *verb; - const char *bus_method; - const char *error_message; - } LinkBusAction; - - static const LinkBusAction link_bus_action_table[] = { - { "renew", "RenewLink", "Failed to renew dynamic configuration of interface" }, - { "forcerenew", "ForceRenewLink", "Failed to forcibly renew dynamic configuration of interface" }, - { "reconfigure", "ReconfigureLink", "Failed to reconfigure network interface" }, + const char *method; + } LinkVarlinkAction; + + static const LinkVarlinkAction link_varlink_action_table[] = { + { "up", "io.systemd.Network.Link.Up", }, + { "down", "io.systemd.Network.Link.Down", }, + { "renew", "io.systemd.Network.Link.Renew", }, + { "forcerenew", "io.systemd.Network.Link.ForceRenew", }, + { "reconfigure", "io.systemd.Network.Link.Reconfigure", }, }; /* Common implementation for 'simple' method calls that just take an ifindex, and nothing else. */ - const LinkBusAction *a = NULL; - FOREACH_ELEMENT(i, link_bus_action_table) + const LinkVarlinkAction *a = NULL; + FOREACH_ELEMENT(i, link_varlink_action_table) if (streq(argv[0], i->verb)) { a = i; break; @@ -134,50 +103,30 @@ int link_bus_simple_method(int argc, char *argv[], void *userdata) { if (r < 0) return r; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - r = acquire_bus(&bus); + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; + r = varlink_connect_networkd(&vl); if (r < 0) return r; (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); void *p; - ORDERED_SET_FOREACH(p, indexes) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int index = PTR_TO_INT(p); - - r = bus_call_method(bus, bus_network_mgr, a->bus_method, &error, /* ret_reply= */ NULL, "i", index); - if (r < 0) { - RET_GATHER(ret, r); - log_error_errno(r, "%s %s: %s", - a->error_message, - FORMAT_IFNAME_FULL(index, FORMAT_IFNAME_IFINDEX), - bus_error_message(&error, r)); - } - } + ORDERED_SET_FOREACH(p, indexes) + RET_GATHER(ret, varlink_callbo_and_log( + vl, + a->method, + /* reply= */ NULL, + SD_JSON_BUILD_PAIR_INTEGER("InterfaceIndex", PTR_TO_INT(p)), + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password))); return ret; } -int verb_reload(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to reload network settings: %s", bus_error_message(&error, r)); - - return 0; +int verb_reload(int argc, char *argv[], uintptr_t _data, void *userdata) { + return reload_networkd(); } -int verb_persistent_storage(int argc, char *argv[], void *userdata) { +int verb_persistent_storage(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; bool ready; int r; diff --git a/src/network/networkctl-misc.h b/src/network/networkctl-misc.h index 4860c99ce393c..771761d05f268 100644 --- a/src/network/networkctl-misc.h +++ b/src/network/networkctl-misc.h @@ -1,8 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int link_up_down(int argc, char *argv[], void *userdata); -int link_delete(int argc, char *argv[], void *userdata); -int link_bus_simple_method(int argc, char *argv[], void *userdata); -int verb_reload(int argc, char *argv[], void *userdata); -int verb_persistent_storage(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_link_delete(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_link_varlink_simple_method(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_reload(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_persistent_storage(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/network/networkctl-status-link.c b/src/network/networkctl-status-link.c index 62f57c2b215d5..15c9c46336b3a 100644 --- a/src/network/networkctl-status-link.c +++ b/src/network/networkctl-status-link.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-bus.h" #include "sd-device.h" #include "sd-dhcp-client-id.h" #include "sd-dhcp-lease.h" @@ -12,8 +11,6 @@ #include "alloc-util.h" #include "bond-util.h" #include "bridge-util.h" -#include "bus-error.h" -#include "bus-util.h" #include "errno-util.h" #include "escape.h" #include "extract-word.h" @@ -21,7 +18,9 @@ #include "format-util.h" #include "geneve-util.h" #include "glyph-util.h" +#include "iovec-util.h" #include "ipvlan-util.h" +#include "json-util.h" #include "macvlan-util.h" #include "netif-util.h" #include "network-internal.h" @@ -40,87 +39,58 @@ #include "time-util.h" #include "udev-util.h" -static int dump_dhcp_leases(Table *table, const char *prefix, sd_bus *bus, const LinkInfo *link) { +typedef struct LeaseInfo { + const char *address; + struct iovec client_id; +} LeaseInfo; + +static void lease_info_done(LeaseInfo *p) { + assert(p); + + iovec_done(&p->client_id); +} + +static int dump_dhcp_leases(Table *table, const char *prefix, const LinkInfo *link) { _cleanup_strv_free_ char **buf = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; assert(table); assert(prefix); - assert(bus); assert(link); - r = link_get_property(bus, link->ifindex, &error, &reply, "org.freedesktop.network1.DHCPServer", "Leases", "a(uayayayayt)"); - if (r < 0) { - bool quiet = sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY); - - log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, - r, "Failed to query link DHCP leases: %s", bus_error_message(&error, r)); + sd_json_variant *leases; + r = json_variant_find_object(link->description, STRV_MAKE("Interface", "DHCPServer", "Leases"), &leases); + if (r == -ENODATA) return 0; - } - - r = sd_bus_message_enter_container(reply, 'a', "(uayayayayt)"); if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "uayayayayt")) > 0) { - _cleanup_free_ char *id = NULL, *ip = NULL; - const void *client_id, *addr, *gtw, *hwaddr; - size_t client_id_sz, sz; - uint64_t expiration; - uint32_t family; - - r = sd_bus_message_read(reply, "u", &family); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &client_id, &client_id_sz); - if (r < 0) - return bus_log_parse_error(r); + return r; - r = sd_bus_message_read_array(reply, 'y', &addr, &sz); - if (r < 0 || sz != 4) - return bus_log_parse_error(r); + static const sd_json_dispatch_field dispatch_table[] = { + { "AddressString", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LeaseInfo, address), SD_JSON_MANDATORY }, + { "ClientId", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(LeaseInfo, client_id), 0 }, + {} + }; - r = sd_bus_message_read_array(reply, 'y', >w, &sz); - if (r < 0 || sz != 4) - return bus_log_parse_error(r); + sd_json_variant *lease; + JSON_VARIANT_ARRAY_FOREACH(lease, leases) { + _cleanup_(lease_info_done) LeaseInfo info = {}; + _cleanup_free_ char *client_id = NULL; - r = sd_bus_message_read_array(reply, 'y', &hwaddr, &sz); + r = sd_json_dispatch(lease, dispatch_table, SD_JSON_LOG | SD_JSON_WARNING | SD_JSON_ALLOW_EXTENSIONS, &info); if (r < 0) - return bus_log_parse_error(r); + continue; - r = sd_bus_message_read_basic(reply, 't', &expiration); - if (r < 0) - return bus_log_parse_error(r); + if (info.client_id.iov_len > 0) + (void) sd_dhcp_client_id_to_string_from_raw(info.client_id.iov_base, info.client_id.iov_len, &client_id); - r = sd_dhcp_client_id_to_string_from_raw(client_id, client_id_sz, &id); - if (r < 0) - return bus_log_parse_error(r); - - r = in_addr_to_string(family, addr, &ip); - if (r < 0) - return bus_log_parse_error(r); - - r = strv_extendf(&buf, "%s (to %s)", ip, id); + r = strv_extendf(&buf, "%s%s%s%s", + info.address, + client_id ? " (to " : "", + strempty(client_id), + client_id ? ")" : ""); if (r < 0) return log_oom(); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); if (strv_isempty(buf)) { r = strv_extendf(&buf, "none"); @@ -259,7 +229,6 @@ static int format_config_files(char ***files, const char *main_config) { } static int link_status_one( - sd_bus *bus, sd_netlink *rtnl, sd_hwdb *hwdb, sd_varlink *vl, @@ -276,7 +245,6 @@ static int link_status_one( _cleanup_(table_unrefp) Table *table = NULL; int r; - assert(bus); assert(rtnl); assert(vl); assert(info); @@ -919,7 +887,7 @@ static int link_status_one( if (r < 0) return r; - r = dump_dhcp_leases(table, "Offered DHCP leases", bus, info); + r = dump_dhcp_leases(table, "Offered DHCP leases", info); if (r < 0) return r; @@ -932,15 +900,14 @@ static int link_status_one( on_color_operational, glyph(GLYPH_BLACK_CIRCLE), off_color_operational, info->ifindex, info->name); - r = table_print(table, NULL); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; return show_logs(info->ifindex, info->name); } -int link_status(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; +int verb_link_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; @@ -951,10 +918,6 @@ int link_status(int argc, char *argv[], void *userdata) { if (r != 0) return r; - r = acquire_bus(&bus); - if (r < 0) - return r; - pager_open(arg_pager_flags); r = sd_netlink_open(&rtnl); @@ -970,11 +933,11 @@ int link_status(int argc, char *argv[], void *userdata) { return r; if (arg_all) - c = acquire_link_info(bus, rtnl, NULL, &links); + c = acquire_link_info(vl, rtnl, NULL, &links); else if (argc <= 1) return system_status(rtnl, hwdb); else - c = acquire_link_info(bus, rtnl, argv + 1, &links); + c = acquire_link_info(vl, rtnl, argv + 1, &links); if (c < 0) return c; @@ -985,7 +948,7 @@ int link_status(int argc, char *argv[], void *userdata) { if (!first) putchar('\n'); - RET_GATHER(r, link_status_one(bus, rtnl, hwdb, vl, i)); + RET_GATHER(r, link_status_one(rtnl, hwdb, vl, i)); first = false; } diff --git a/src/network/networkctl-status-link.h b/src/network/networkctl-status-link.h index 1c1b4ea75a385..fcc0fa07289dd 100644 --- a/src/network/networkctl-status-link.h +++ b/src/network/networkctl-status-link.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int link_status(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_link_status(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/network/networkctl-status-system.c b/src/network/networkctl-status-system.c index bb1b5d1377bfa..a03004b882aad 100644 --- a/src/network/networkctl-status-system.c +++ b/src/network/networkctl-status-system.c @@ -20,6 +20,9 @@ static int ifindex_str_compare_func(char * const *a, char * const *b) { size_t al, bl; int r; + assert(a); + assert(b); + al = strlen_ptr(*a); bl = strlen_ptr(*b); @@ -123,9 +126,9 @@ int system_status(sd_netlink *rtnl, sd_hwdb *hwdb) { on_color_operational, glyph(GLYPH_BLACK_CIRCLE), off_color_operational, strna(netifs_joined)); - r = table_print(table, NULL); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; return show_logs(0, NULL); } diff --git a/src/network/networkctl-util.c b/src/network/networkctl-util.c index 00996e689f7d1..590c8a6abc2fb 100644 --- a/src/network/networkctl-util.c +++ b/src/network/networkctl-util.c @@ -3,14 +3,12 @@ #include #include -#include "sd-bus.h" - -#include "alloc-util.h" #include "ansi-color.h" +#include "bus-util.h" #include "log.h" #include "networkctl.h" #include "networkctl-util.h" -#include "stdio-util.h" +#include "polkit-agent.h" #include "string-util.h" #include "strv.h" #include "varlink-util.h" @@ -62,76 +60,57 @@ int varlink_connect_networkd(sd_varlink **ret_varlink) { return 0; } -bool networkd_is_running(void) { - static int cached = -1; +int reload_networkd(void) { + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; int r; - if (cached < 0) { - r = access("/run/systemd/netif/state", F_OK); - if (r < 0) { - if (errno != ENOENT) - log_debug_errno(errno, - "Failed to determine whether networkd is running, assuming it's not: %m"); + r = varlink_connect_networkd(&vl); + if (r < 0) + return r; - cached = false; - } else - cached = true; - } + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - return cached; + return varlink_callbo_and_log( + vl, + "io.systemd.service.Reload", + /* reply= */ NULL, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); } -int acquire_bus(sd_bus **ret) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; +int reload_udevd(void) { + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; int r; - assert(ret); - - r = sd_bus_open_system(&bus); + r = sd_varlink_connect_address(&vl, "/run/udev/io.systemd.Udev"); + if (r == -ENOENT) { + log_debug("systemd-udevd is not running, skipping reload."); + return 0; + } if (r < 0) - return log_error_errno(r, "Failed to connect to system bus: %m"); - - (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); + return log_error_errno(r, "Failed to connect to udev: %m"); - if (networkd_is_running()) { - r = varlink_connect_networkd(/* ret_varlink= */ NULL); - if (r < 0) - return r; - } else - log_warning("systemd-networkd is not running, output might be incomplete."); + (void) sd_varlink_set_description(vl, "udev"); - *ret = TAKE_PTR(bus); - return 0; + return varlink_call_and_log(vl, "io.systemd.service.Reload", /* parameters= */ NULL, /* reply= */ NULL); } -int link_get_property( - sd_bus *bus, - int ifindex, - sd_bus_error *error, - sd_bus_message **reply, - const char *iface, - const char *propname, - const char *type) { - - _cleanup_free_ char *path = NULL; - char ifindex_str[DECIMAL_STR_MAX(int)]; +bool networkd_is_running(void) { + static int cached = -1; int r; - assert(bus); - assert(ifindex >= 0); - assert(error); - assert(reply); - assert(iface); - assert(propname); - assert(type); - - xsprintf(ifindex_str, "%i", ifindex); + if (cached < 0) { + r = access("/run/systemd/netif/state", F_OK); + if (r < 0) { + if (errno != ENOENT) + log_debug_errno(errno, + "Failed to determine whether networkd is running, assuming it's not: %m"); - r = sd_bus_path_encode("/org/freedesktop/network1/link", ifindex_str, &path); - if (r < 0) - return r; + cached = false; + } else + cached = true; + } - return sd_bus_get_property(bus, "org.freedesktop.network1", path, iface, propname, error, reply, type); + return cached; } void operational_state_to_color(const char *name, const char *state, const char **on, const char **off) { @@ -196,3 +175,40 @@ void online_state_to_color(const char *state, const char **on, const char **off) *off = ""; } } + +int acquire_link_description(sd_varlink *vl, int ifindex, sd_json_variant **ret) { + int r; + + assert(vl); + assert(ifindex > 0); + assert(ret); + + sd_json_variant *v; /* borrowed from vl, do not unref */ + r = varlink_callbo_and_log( + vl, + "io.systemd.Network.Link.Describe", + &v, + SD_JSON_BUILD_PAIR_INTEGER("InterfaceIndex", ifindex)); + if (r < 0) + return r; + + *ret = sd_json_variant_ref(v); + return 0; +} + +int json_variant_find_object(sd_json_variant *v, char * const *object_names, sd_json_variant **ret) { + assert(object_names); + assert(ret); + + if (!v || sd_json_variant_is_null(v)) + return -ENODATA; + + STRV_FOREACH(name, object_names) { + v = sd_json_variant_by_key(v, *name); + if (!v || sd_json_variant_is_null(v)) + return -ENODATA; + } + + *ret = v; + return 0; +} diff --git a/src/network/networkctl-util.h b/src/network/networkctl-util.h index b2721f5ea2d83..6067602d1a0f5 100644 --- a/src/network/networkctl-util.h +++ b/src/network/networkctl-util.h @@ -4,17 +4,13 @@ #include "shared-forward.h" int varlink_connect_networkd(sd_varlink **ret_varlink); +int reload_networkd(void); +int reload_udevd(void); bool networkd_is_running(void); -int acquire_bus(sd_bus **ret); -int link_get_property( - sd_bus *bus, - int ifindex, - sd_bus_error *error, - sd_bus_message **reply, - const char *iface, - const char *propname, - const char *type); void operational_state_to_color(const char *name, const char *state, const char **on, const char **off); void setup_state_to_color(const char *state, const char **on, const char **off); void online_state_to_color(const char *state, const char **on, const char **off); + +int acquire_link_description(sd_varlink *vl, int ifindex, sd_json_variant **ret); +int json_variant_find_object(sd_json_variant *v, char * const *object_names, sd_json_variant **ret); diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 68acdcf60a7a2..7fe34ac1eb778 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -223,22 +223,22 @@ static int parse_argv(int argc, char *argv[]) { static int networkctl_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, list_links }, - { "status", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, link_status }, - { "lldp", VERB_ANY, VERB_ANY, 0, link_lldp_status }, - { "label", 1, 1, 0, list_address_labels }, - { "delete", 2, VERB_ANY, 0, link_delete }, - { "up", 2, VERB_ANY, 0, link_up_down }, - { "down", 2, VERB_ANY, 0, link_up_down }, - { "renew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_bus_simple_method }, - { "forcerenew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_bus_simple_method }, - { "reconfigure", 2, VERB_ANY, VERB_ONLINE_ONLY, link_bus_simple_method }, - { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, - { "edit", 2, VERB_ANY, 0, verb_edit }, - { "cat", 1, VERB_ANY, 0, verb_cat }, - { "mask", 2, VERB_ANY, 0, verb_mask }, - { "unmask", 2, VERB_ANY, 0, verb_unmask }, - { "persistent-storage", 2, 2, 0, verb_persistent_storage }, + { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, verb_list_links }, + { "status", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_link_status }, + { "lldp", VERB_ANY, VERB_ANY, 0, verb_link_lldp_status }, + { "label", 1, 1, 0, verb_list_address_labels }, + { "delete", 2, VERB_ANY, 0, verb_link_delete }, + { "up", 2, VERB_ANY, 0, verb_link_varlink_simple_method }, + { "down", 2, VERB_ANY, 0, verb_link_varlink_simple_method }, + { "renew", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_link_varlink_simple_method }, + { "forcerenew", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_link_varlink_simple_method }, + { "reconfigure", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_link_varlink_simple_method }, + { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, + { "edit", 2, VERB_ANY, 0, verb_edit }, + { "cat", 1, VERB_ANY, 0, verb_cat }, + { "mask", 2, VERB_ANY, 0, verb_mask }, + { "unmask", 2, VERB_ANY, 0, verb_unmask }, + { "persistent-storage", 2, 2, 0, verb_persistent_storage }, {} }; diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 5bb4cddbdac82..01eef634f433b 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -162,7 +162,7 @@ DEFINE_HASH_OPS( address_hash_func, address_compare_func); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( address_section_hash_ops, ConfigSection, config_section_hash_func, @@ -173,6 +173,8 @@ DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( int address_new(Address **ret) { _cleanup_(address_unrefp) Address *address = NULL; + assert(ret); + address = new(Address, 1); if (!address) return -ENOMEM; diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c index 619c2f6377093..3e7cca991b392 100644 --- a/src/network/networkd-dhcp-common.c +++ b/src/network/networkd-dhcp-common.c @@ -276,6 +276,7 @@ int link_get_captive_portal(Link *link, const char **ret) { int r; assert(link); + assert(ret); if (!link->network) { *ret = NULL; diff --git a/src/network/networkd-dhcp-prefix-delegation.c b/src/network/networkd-dhcp-prefix-delegation.c index f53d7d1c5aada..e734b0171d37a 100644 --- a/src/network/networkd-dhcp-prefix-delegation.c +++ b/src/network/networkd-dhcp-prefix-delegation.c @@ -557,6 +557,7 @@ static int dhcp_pd_get_preferred_subnet_prefix( assert(link->manager); assert(link->network); assert(pd_prefix); + assert(ret); if (link->network->dhcp_pd_subnet_id >= 0) { /* If the link has a preference for a particular subnet id try to allocate that */ diff --git a/src/network/networkd-dhcp-server-bus.c b/src/network/networkd-dhcp-server-bus.c index db2ee37f34be6..e5ee6d3f6d5c3 100644 --- a/src/network/networkd-dhcp-server-bus.c +++ b/src/network/networkd-dhcp-server-bus.c @@ -74,6 +74,46 @@ static int property_get_leases( return sd_bus_message_close_container(reply); } +static int property_get_pool_size( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + Link *l = ASSERT_PTR(userdata); + sd_dhcp_server *s; + uint32_t v; + + assert(reply); + + s = l->dhcp_server; + v = s && !sd_dhcp_server_is_in_relay_mode(s) ? s->pool_size : UINT32_MAX; + + return sd_bus_message_append_basic(reply, 'u', &v); +} + +static int property_get_pool_offset( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + Link *l = ASSERT_PTR(userdata); + sd_dhcp_server *s; + uint32_t v; + + assert(reply); + + s = l->dhcp_server; + v = s && !sd_dhcp_server_is_in_relay_mode(s) ? s->pool_offset : UINT32_MAX; + + return sd_bus_message_append_basic(reply, 'u', &v); +} + static int dhcp_server_emit_changed_strv(Link *link, char **properties) { _cleanup_free_ char *path = NULL; @@ -104,6 +144,8 @@ static const sd_bus_vtable dhcp_server_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Leases", "a(uayayayayt)", property_get_leases, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("PoolSize", "u", property_get_pool_size, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PoolOffset", "u", property_get_pool_offset, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_VTABLE_END }; diff --git a/src/network/networkd-dhcp-server-static-lease.c b/src/network/networkd-dhcp-server-static-lease.c index 2f04232c49f1c..2814be8987cca 100644 --- a/src/network/networkd-dhcp-server-static-lease.c +++ b/src/network/networkd-dhcp-server-static-lease.c @@ -188,6 +188,7 @@ int config_parse_dhcp_static_lease_hwaddr( if (isempty(rvalue)) { lease->client_id = mfree(lease->client_id); lease->client_id_size = 0; + TAKE_PTR(lease); return 0; } diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c index 24ae1bbe89087..c88488258f002 100644 --- a/src/network/networkd-dhcp-server.c +++ b/src/network/networkd-dhcp-server.c @@ -326,6 +326,7 @@ void manager_toggle_dhcp4_server_state(Manager *manager, bool start) { static int dhcp_server_find_uplink(Link *link, Link **ret) { assert(link); + assert(ret); if (link->network->dhcp_server_uplink_name) return link_get_by_name(link->manager, link->network->dhcp_server_uplink_name, ret); @@ -455,6 +456,9 @@ static int dhcp4_server_parse_dns_server_string_and_warn( struct in_addr **addresses, size_t *n_addresses) { + assert(addresses); + assert(n_addresses); + for (;;) { _cleanup_free_ char *word = NULL, *server_name = NULL; union in_addr_union address; diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index f274a0c4d94a0..dc6b78e326c34 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1733,6 +1733,8 @@ int dhcp4_update_mac(Link *link) { } int dhcp4_update_ipv6_connectivity(Link *link) { + int r; + assert(link); if (!link->network) @@ -1744,16 +1746,20 @@ int dhcp4_update_ipv6_connectivity(Link *link) { if (!link->dhcp_client) return 0; - /* If the client is running, set the current connectivity. */ - if (sd_dhcp_client_is_running(link->dhcp_client)) - return sd_dhcp_client_set_ipv6_connectivity(link->dhcp_client, link_has_ipv6_connectivity(link)); + bool have = link_has_ipv6_connectivity(link); + r = sd_dhcp_client_set_ipv6_connectivity(link->dhcp_client, have); + if (r < 0) + return r; - /* If the client has been already stopped or not started yet, let's check the current connectivity - * and start the client if necessary. */ - if (link_has_ipv6_connectivity(link)) - return 0; + /* If we do not have IPv6 connectivity, and the client has been already stopped or not started yet, + * let's start the client if possible. */ + if (!have && !sd_dhcp_client_is_running(link->dhcp_client)) { + r = dhcp4_start_full(link, /* set_ipv6_connectivity= */ false); + if (r < 0) + return r; + } - return dhcp4_start_full(link, /* set_ipv6_connectivity= */ false); + return 0; } int dhcp4_start_full(Link *link, bool set_ipv6_connectivity) { @@ -1805,8 +1811,10 @@ int dhcp4_renew(Link *link) { return dhcp4_start(link); /* The client may be waiting for IPv6 connectivity. Let's restart the client in that case. */ - if (dhcp_client_get_state(link->dhcp_client) != DHCP_STATE_BOUND) - return sd_dhcp_client_interrupt_ipv6_only_mode(link->dhcp_client); + if (sd_dhcp_client_is_waiting_for_ipv6_connectivity(link->dhcp_client)) { + sd_dhcp_client_stop(link->dhcp_client); + return dhcp4_start(link); + } /* Otherwise, send a RENEW command. */ return sd_dhcp_client_send_renew(link->dhcp_client); diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 26ab7ceb52944..c230a86587464 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -202,6 +202,10 @@ static int dhcp6_request_address( Address *existing; int r; + assert(link); + assert(server_address); + assert(ip6_addr); + r = address_new(&addr); if (r < 0) return log_oom(); diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index de1a8829ee94b..d5bed7718a0c5 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -26,6 +26,7 @@ #include "networkd-route.h" #include "networkd-route-util.h" #include "networkd-routing-policy-rule.h" +#include "networkd-speed-meter.h" #include "networkd-wwan.h" #include "ordered-set.h" #include "set.h" @@ -1419,35 +1420,30 @@ static int dhcp_client_private_options_append_json(Link *link, sd_json_variant * return 0; LIST_FOREACH(options, option, link->dhcp_lease->private_options) { - r = sd_json_variant_append_arraybo( &array, SD_JSON_BUILD_PAIR_UNSIGNED("Option", option->tag), SD_JSON_BUILD_PAIR_HEX("PrivateOptionData", option->data, option->length)); if (r < 0) - return 0; + return r; } return json_variant_set_field_non_null(v, "PrivateOptions", array); } static int dhcp_client_id_append_json(Link *link, sd_json_variant **v) { - const sd_dhcp_client_id *client_id; - const void *data; - size_t l; - int r; - assert(link); assert(v); if (!link->dhcp_client) return 0; - r = sd_dhcp_client_get_client_id(link->dhcp_client, &client_id); - if (r < 0) + const sd_dhcp_client_id *client_id; + if (sd_dhcp_client_get_client_id(link->dhcp_client, &client_id) < 0) return 0; - r = sd_dhcp_client_id_get_raw(client_id, &data, &l); - if (r < 0) + const void *data; + size_t l; + if (sd_dhcp_client_id_get_raw(client_id, &data, &l) < 0) return 0; return sd_json_variant_merge_objectbo(v, SD_JSON_BUILD_PAIR_BYTE_ARRAY("ClientIdentifier", data, l)); @@ -1548,10 +1544,10 @@ int link_build_json(Link *link, sd_json_variant **ret) { SD_JSON_BUILD_PAIR_STRING("AdministrativeState", link_state_to_string(link->state)), SD_JSON_BUILD_PAIR_STRING("OperationalState", link_operstate_to_string(link->operstate)), SD_JSON_BUILD_PAIR_STRING("CarrierState", link_carrier_state_to_string(link->carrier_state)), - SD_JSON_BUILD_PAIR_STRING("AddressState", link_address_state_to_string(link->address_state)), - SD_JSON_BUILD_PAIR_STRING("IPv4AddressState", link_address_state_to_string(link->ipv4_address_state)), - SD_JSON_BUILD_PAIR_STRING("IPv6AddressState", link_address_state_to_string(link->ipv6_address_state)), - SD_JSON_BUILD_PAIR_STRING("OnlineState", link_online_state_to_string(link->online_state))); + JSON_BUILD_PAIR_ENUM("AddressState", link_address_state_to_string(link->address_state)), + JSON_BUILD_PAIR_ENUM("IPv4AddressState", link_address_state_to_string(link->ipv4_address_state)), + JSON_BUILD_PAIR_ENUM("IPv6AddressState", link_address_state_to_string(link->ipv6_address_state)), + JSON_BUILD_PAIR_ENUM("OnlineState", link_online_state_to_string(link->online_state))); if (r < 0) return r; @@ -1639,6 +1635,20 @@ int link_build_json(Link *link, sd_json_variant **ret) { if (r < 0) return r; + /* Append BitRates if speed meter is active */ + uint64_t tx, rx; + link_get_bit_rates(link, &tx, &rx); + if (tx != UINT64_MAX && rx != UINT64_MAX) { + r = sd_json_variant_merge_objectbo( + &v, + SD_JSON_BUILD_PAIR("BitRates", + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_UNSIGNED("TxBitRate", tx), + SD_JSON_BUILD_PAIR_UNSIGNED("RxBitRate", rx)))); + if (r < 0) + return r; + } + *ret = TAKE_PTR(v); return 0; } diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c index 1b96fbdbd39af..a375d0c18b235 100644 --- a/src/network/networkd-link-bus.c +++ b/src/network/networkd-link-bus.c @@ -17,6 +17,7 @@ #include "networkd-link.h" #include "networkd-link-bus.h" #include "networkd-manager.h" +#include "networkd-speed-meter.h" #include "networkd-state-file.h" #include "ordered-set.h" #include "parse-util.h" @@ -42,32 +43,12 @@ static int property_get_bit_rates( sd_bus_error *error) { Link *link = ASSERT_PTR(userdata); - Manager *manager; - double interval_sec; uint64_t tx, rx; assert(bus); assert(reply); - manager = link->manager; - - if (!manager->use_speed_meter || - manager->speed_meter_usec_old == 0 || - !link->stats_updated) - return sd_bus_message_append(reply, "(tt)", UINT64_MAX, UINT64_MAX); - - assert(manager->speed_meter_usec_new > manager->speed_meter_usec_old); - interval_sec = (double) (manager->speed_meter_usec_new - manager->speed_meter_usec_old) / USEC_PER_SEC; - - if (link->stats_new.tx_bytes > link->stats_old.tx_bytes) - tx = (uint64_t) ((link->stats_new.tx_bytes - link->stats_old.tx_bytes) / interval_sec); - else - tx = (uint64_t) ((UINT64_MAX - (link->stats_old.tx_bytes - link->stats_new.tx_bytes)) / interval_sec); - - if (link->stats_new.rx_bytes > link->stats_old.rx_bytes) - rx = (uint64_t) ((link->stats_new.rx_bytes - link->stats_old.rx_bytes) / interval_sec); - else - rx = (uint64_t) ((UINT64_MAX - (link->stats_old.rx_bytes - link->stats_new.rx_bytes)) / interval_sec); + link_get_bit_rates(link, &tx, &rx); return sd_bus_message_append(reply, "(tt)", tx, rx); } @@ -672,7 +653,7 @@ int bus_link_method_reconfigure(sd_bus_message *message, void *userdata, sd_bus_ if (r == 0) return 1; /* Polkit will call us back */ - r = link_reconfigure_full(l, LINK_RECONFIGURE_UNCONDITIONALLY | LINK_RECONFIGURE_CLEANLY, message, /* counter= */ NULL); + r = link_reconfigure_full(l, LINK_RECONFIGURE_UNCONDITIONALLY | LINK_RECONFIGURE_CLEANLY, message, /* varlink= */ NULL, /* counter= */ NULL); if (r != 0) return r; /* Will reply later when r > 0. */ diff --git a/src/network/networkd-link-varlink.c b/src/network/networkd-link-varlink.c index ad7386dabff05..c802c7fb43f68 100644 --- a/src/network/networkd-link-varlink.c +++ b/src/network/networkd-link-varlink.c @@ -1,9 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dhcp-server.h" #include "sd-varlink.h" #include "bus-polkit.h" #include "json-util.h" +#include "networkd-dhcp4.h" +#include "networkd-json.h" #include "networkd-link.h" #include "networkd-link-varlink.h" #include "networkd-manager.h" @@ -65,6 +68,27 @@ int dispatch_link(sd_varlink *vlink, sd_json_variant *parameters, Manager *manag return 0; } +int vl_method_link_describe(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + Manager *manager = ASSERT_PTR(userdata); + Link *link; + int r; + + assert(vlink); + + r = dispatch_link(vlink, parameters, manager, DISPATCH_LINK_MANDATORY, &link); + if (r != 0) + return r; + + r = link_build_json(link, &v); + if (r < 0) + return log_link_error_errno(link, r, "Failed to format JSON data: %m"); + + return sd_varlink_replybo( + vlink, + SD_JSON_BUILD_PAIR_VARIANT("Interface", v)); +} + static int vl_method_link_up_or_down(sd_varlink *vlink, sd_json_variant *parameters, Manager *manager, bool up) { Link *link; int r; @@ -100,3 +124,98 @@ int vl_method_link_up(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink int vl_method_link_down(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { return vl_method_link_up_or_down(vlink, parameters, userdata, /* up= */ false); } + +int vl_method_link_renew(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Link *link; + int r; + + assert(vlink); + + r = dispatch_link(vlink, parameters, manager, DISPATCH_LINK_POLKIT | DISPATCH_LINK_MANDATORY, &link); + if (r != 0) + return r; + + if (!link->network) + return sd_varlink_error(vlink, "io.systemd.Network.Link.InterfaceUnmanaged", NULL); + + r = varlink_verify_polkit_async( + vlink, + manager->bus, + "org.freedesktop.network1.renew", + /* details= */ NULL, + &manager->polkit_registry); + if (r <= 0) + return r; + + r = dhcp4_renew(link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to renew DHCPv4 lease: %m"); + + return sd_varlink_reply(vlink, NULL); +} + +int vl_method_link_force_renew(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Link *link; + int r; + + assert(vlink); + + r = dispatch_link(vlink, parameters, manager, DISPATCH_LINK_POLKIT | DISPATCH_LINK_MANDATORY, &link); + if (r != 0) + return r; + + if (!link->network) + return sd_varlink_error(vlink, "io.systemd.Network.Link.InterfaceUnmanaged", NULL); + + r = varlink_verify_polkit_async( + vlink, + manager->bus, + "org.freedesktop.network1.forcerenew", + /* details= */ NULL, + &manager->polkit_registry); + if (r <= 0) + return r; + + if (sd_dhcp_server_is_running(link->dhcp_server)) { + r = sd_dhcp_server_forcerenew(link->dhcp_server); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to force-renew DHCP server leases: %m"); + } + + return sd_varlink_reply(vlink, NULL); +} + +int vl_method_link_reconfigure(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Link *link; + int r; + + assert(vlink); + + r = dispatch_link(vlink, parameters, manager, DISPATCH_LINK_POLKIT | DISPATCH_LINK_MANDATORY, &link); + if (r != 0) + return r; + + r = varlink_verify_polkit_async( + vlink, + manager->bus, + "org.freedesktop.network1.reconfigure", + /* details= */ NULL, + &manager->polkit_registry); + if (r <= 0) + return r; + + r = link_reconfigure_full(link, + LINK_RECONFIGURE_UNCONDITIONALLY | LINK_RECONFIGURE_CLEANLY, + /* message= */ NULL, + /* varlink= */ vlink, + /* counter= */ NULL); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to reconfigure link: %m"); + if (r > 0) + return 0; /* Reply will be sent asynchronously via vlink */ + + return sd_varlink_reply(vlink, NULL); +} diff --git a/src/network/networkd-link-varlink.h b/src/network/networkd-link-varlink.h index 60468519ca1ca..7be68fee46582 100644 --- a/src/network/networkd-link-varlink.h +++ b/src/network/networkd-link-varlink.h @@ -10,5 +10,9 @@ typedef enum DispatchLinkFlag { int dispatch_link(sd_varlink *vlink, sd_json_variant *parameters, Manager *manager, DispatchLinkFlag flags, Link **ret); +int vl_method_link_describe(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_link_up(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_link_down(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_link_renew(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_link_force_renew(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_link_reconfigure(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index cb79ddb9eb74a..c6bc8fc4b1c4d 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -18,6 +18,7 @@ #include "sd-ndisc.h" #include "sd-netlink.h" #include "sd-radv.h" +#include "sd-varlink.h" #include "alloc-util.h" #include "arphrd-util.h" @@ -815,7 +816,6 @@ int link_ipv6ll_gained(Link *link) { int link_handle_bound_to_list(Link *link) { bool required_up = false; - bool link_is_up = false; Link *l; assert(link); @@ -826,18 +826,15 @@ int link_handle_bound_to_list(Link *link) { if (hashmap_isempty(link->bound_to_links)) return 0; - if (link->flags & IFF_UP) - link_is_up = true; - HASHMAP_FOREACH(l, link->bound_to_links) if (link_has_carrier(l)) { required_up = true; break; } - if (!required_up && link_is_up) + if (!required_up && link_is_up(link)) return link_request_to_bring_up_or_down(link, /* up= */ false); - if (required_up && !link_is_up) + if (required_up && !link_is_up(link)) return link_request_to_bring_up_or_down(link, /* up= */ true); return 0; @@ -865,11 +862,12 @@ static int link_put_carrier(Link *link, Link *carrier, Hashmap **h) { assert(link); assert(carrier); + assert(h); if (link == carrier) return 0; - if (hashmap_get(*h, INT_TO_PTR(carrier->ifindex))) + if (hashmap_contains(*h, INT_TO_PTR(carrier->ifindex))) return 0; r = hashmap_ensure_put(h, NULL, INT_TO_PTR(carrier->ifindex), carrier); @@ -1513,6 +1511,7 @@ typedef struct LinkReconfigurationData { Link *link; LinkReconfigurationFlag flags; sd_bus_message *message; + sd_varlink *varlink; unsigned *counter; } LinkReconfigurationData; @@ -1522,6 +1521,7 @@ static LinkReconfigurationData* link_reconfiguration_data_free(LinkReconfigurati link_unref(data->link); sd_bus_message_unref(data->message); + sd_varlink_unref(data->varlink); return mfree(data); } @@ -1532,24 +1532,30 @@ static void link_reconfiguration_data_destroy_callback(LinkReconfigurationData * int r; assert(data); + assert(!data->message || !data->varlink); /* D-Bus and Varlink callers are mutually exclusive */ - if (data->message) { - if (data->counter) { - assert(*data->counter > 0); - (*data->counter)--; - } + if (data->counter) { + assert(*data->counter > 0); + (*data->counter)--; + } - if (!data->counter || *data->counter <= 0) { - /* Update the state files before replying the bus method. Otherwise, - * systemd-networkd-wait-online following networkctl reload/reconfigure may read an - * outdated state file and wrongly handle an interface is already in the configured - * state. */ - (void) manager_clean_all(data->manager); + if (!data->counter || *data->counter == 0) { + /* Update the state files before replying. Otherwise, systemd-networkd-wait-online following + * networkctl reload/reconfigure may read an outdated state file and wrongly consider an + * interface already in the configured state. */ + (void) manager_clean_all(data->manager); + if (data->message) { r = sd_bus_reply_method_return(data->message, NULL); if (r < 0) log_warning_errno(r, "Failed to reply for DBus method, ignoring: %m"); } + + if (data->varlink) { + r = sd_varlink_reply(data->varlink, NULL); + if (r < 0) + log_warning_errno(r, "Failed to reply to Varlink request, ignoring: %m"); + } } link_reconfiguration_data_free(data); @@ -1572,7 +1578,7 @@ static int link_reconfigure_handler(sd_netlink *rtnl, sd_netlink_message *m, Lin return r; } -int link_reconfigure_full(Link *link, LinkReconfigurationFlag flags, sd_bus_message *message, unsigned *counter) { +int link_reconfigure_full(Link *link, LinkReconfigurationFlag flags, sd_bus_message *message, sd_varlink *varlink, unsigned *counter) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; _cleanup_(link_reconfiguration_data_freep) LinkReconfigurationData *data = NULL; int r; @@ -1580,6 +1586,7 @@ int link_reconfigure_full(Link *link, LinkReconfigurationFlag flags, sd_bus_mess assert(link); assert(link->manager); assert(link->manager->rtnl); + assert(!message || !varlink); /* D-Bus and Varlink callers are mutually exclusive */ /* When the link is in the pending or initialized state, link_reconfigure_impl() will be called later * by link_initialized() or link_initialized_and_synced(). To prevent the function from being called @@ -1598,6 +1605,7 @@ int link_reconfigure_full(Link *link, LinkReconfigurationFlag flags, sd_bus_mess .link = link_ref(link), .flags = flags, .message = sd_bus_message_ref(message), /* message may be NULL, but _ref() works fine. */ + .varlink = sd_varlink_ref(varlink), /* varlink may be NULL, but _ref() works fine. */ .counter = counter, }; @@ -2012,7 +2020,7 @@ void link_update_operstate(Link *link, bool also_update_master) { carrier_state = LINK_CARRIER_STATE_ENSLAVED; else carrier_state = LINK_CARRIER_STATE_CARRIER; - } else if (link->flags & IFF_UP) + } else if (link_is_up(link)) carrier_state = LINK_CARRIER_STATE_NO_CARRIER; else carrier_state = LINK_CARRIER_STATE_OFF; @@ -2147,6 +2155,11 @@ bool link_has_carrier(Link *link) { return netif_has_carrier(link->kernel_operstate, link->flags); } +bool link_is_up(Link *link) { + assert(link); + return FLAGS_SET(link->flags, IFF_UP); +} + bool link_multicast_enabled(Link *link) { assert(link); @@ -2226,7 +2239,7 @@ static int link_update_flags(Link *link, sd_netlink_message *message) { log_link_debug(link, "Unknown link flags lost, ignoring: %#.5x", unknown_flags_removed); } - link_was_admin_up = link->flags & IFF_UP; + link_was_admin_up = link_is_up(link); had_carrier = link_has_carrier(link); link->flags = flags; @@ -2236,9 +2249,9 @@ static int link_update_flags(Link *link, sd_netlink_message *message) { r = 0; - if (!link_was_admin_up && (link->flags & IFF_UP)) + if (!link_was_admin_up && link_is_up(link)) r = link_admin_state_up(link); - else if (link_was_admin_up && !(link->flags & IFF_UP)) + else if (link_was_admin_up && !link_is_up(link)) r = link_admin_state_down(link); if (r < 0) return r; diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index c5b9421bc0b2b..33c2fd35a5c21 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -229,6 +229,7 @@ void link_check_ready(Link *link); void link_update_operstate(Link *link, bool also_update_master); bool link_has_carrier(Link *link); +bool link_is_up(Link *link); bool link_multicast_enabled(Link *link); bool link_ipv6_enabled(Link *link); @@ -242,9 +243,9 @@ DECLARE_STRING_TABLE_LOOKUP(link_state, LinkState); int link_request_stacked_netdevs(Link *link, NetDevLocalAddressType type); int link_reconfigure_impl(Link *link, LinkReconfigurationFlag flags); -int link_reconfigure_full(Link *link, LinkReconfigurationFlag flags, sd_bus_message *message, unsigned *counter); +int link_reconfigure_full(Link *link, LinkReconfigurationFlag flags, sd_bus_message *message, sd_varlink *varlink, unsigned *counter); static inline int link_reconfigure(Link *link, LinkReconfigurationFlag flags) { - return link_reconfigure_full(link, flags, NULL, NULL); + return link_reconfigure_full(link, flags, NULL, NULL, NULL); } int link_check_initialized(Link *link); diff --git a/src/network/networkd-manager-bus.c b/src/network/networkd-manager-bus.c index a98561b25e77a..c335d34ff1c26 100644 --- a/src/network/networkd-manager-bus.c +++ b/src/network/networkd-manager-bus.c @@ -215,7 +215,7 @@ static int bus_method_reload(sd_bus_message *message, void *userdata, sd_bus_err if (r == 0) return 1; /* Polkit will call us back */ - r = manager_reload(manager, message); + r = manager_reload(manager, message, /* varlink= */ NULL); if (r < 0) return r; diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c index 49bc82678d090..d12847b089f43 100644 --- a/src/network/networkd-manager-varlink.c +++ b/src/network/networkd-manager-varlink.c @@ -54,11 +54,11 @@ static int vl_method_get_states(sd_varlink *link, sd_json_variant *parameters, s return sd_varlink_replybo( link, - SD_JSON_BUILD_PAIR_STRING("AddressState", link_address_state_to_string(m->address_state)), - SD_JSON_BUILD_PAIR_STRING("IPv4AddressState", link_address_state_to_string(m->ipv4_address_state)), - SD_JSON_BUILD_PAIR_STRING("IPv6AddressState", link_address_state_to_string(m->ipv6_address_state)), + JSON_BUILD_PAIR_ENUM("AddressState", link_address_state_to_string(m->address_state)), + JSON_BUILD_PAIR_ENUM("IPv4AddressState", link_address_state_to_string(m->ipv4_address_state)), + JSON_BUILD_PAIR_ENUM("IPv6AddressState", link_address_state_to_string(m->ipv6_address_state)), SD_JSON_BUILD_PAIR_STRING("CarrierState", link_carrier_state_to_string(m->carrier_state)), - SD_JSON_BUILD_PAIR_CONDITION(m->online_state >= 0, "OnlineState", SD_JSON_BUILD_STRING(link_online_state_to_string(m->online_state))), + SD_JSON_BUILD_PAIR_CONDITION(m->online_state >= 0, "OnlineState", JSON_BUILD_STRING_UNDERSCORIFY(link_online_state_to_string(m->online_state))), SD_JSON_BUILD_PAIR_STRING("OperationalState", link_operstate_to_string(m->operational_state))); } @@ -236,6 +236,38 @@ static int vl_method_set_persistent_storage(sd_varlink *vlink, sd_json_variant * return sd_varlink_reply(vlink, NULL); } +static int vl_method_reload(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + int r; + + assert(vlink); + + if (m->reloading > 0) + return sd_varlink_error(vlink, "io.systemd.Network.AlreadyReloading", NULL); + + r = sd_varlink_dispatch(vlink, parameters, dispatch_table_polkit_only, /* userdata= */ NULL); + if (r != 0) + return r; + + r = varlink_verify_polkit_async( + vlink, + m->bus, + "org.freedesktop.network1.reload", + /* details= */ NULL, + &m->polkit_registry); + if (r <= 0) + return r; + + r = manager_reload(m, /* message= */ NULL, vlink); + if (r < 0) + return log_error_errno(r, "Failed to reload: %m"); + + if (m->reloading > 0) + return 0; /* Reply will be sent asynchronously. */ + + return sd_varlink_reply(vlink, NULL); +} + int manager_varlink_init(Manager *m, int fd) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; _unused_ _cleanup_close_ int fd_close = fd; /* take possession */ @@ -271,9 +303,14 @@ int manager_varlink_init(Manager *m, int fd) { "io.systemd.Network.GetNamespaceId", vl_method_get_namespace_id, "io.systemd.Network.GetLLDPNeighbors", vl_method_get_lldp_neighbors, "io.systemd.Network.SetPersistentStorage", vl_method_set_persistent_storage, + "io.systemd.Network.Link.Describe", vl_method_link_describe, "io.systemd.Network.Link.Up", vl_method_link_up, "io.systemd.Network.Link.Down", vl_method_link_down, + "io.systemd.Network.Link.Renew", vl_method_link_renew, + "io.systemd.Network.Link.ForceRenew", vl_method_link_force_renew, + "io.systemd.Network.Link.Reconfigure", vl_method_link_reconfigure, "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.Reload", vl_method_reload, "io.systemd.service.SetLogLevel", varlink_method_set_log_level, "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 62e52717585c1..f43709da356c9 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -533,7 +533,7 @@ static int signal_restart_callback(sd_event_source *s, const struct signalfd_sig static int signal_reload_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { Manager *m = ASSERT_PTR(userdata); - (void) manager_reload(m, /* message= */ NULL); + (void) manager_reload(m, /* message= */ NULL, /* varlink= */ NULL); return 0; } @@ -677,6 +677,8 @@ static int persistent_storage_open(void) { int manager_new(Manager **ret, bool test_mode) { _cleanup_(manager_freep) Manager *m = NULL; + assert(ret); + m = new(Manager, 1); if (!m) return -ENOMEM; @@ -1257,11 +1259,12 @@ int manager_set_timezone(Manager *m, const char *tz) { return 0; } -int manager_reload(Manager *m, sd_bus_message *message) { +int manager_reload(Manager *m, sd_bus_message *message, sd_varlink *varlink) { Link *link; int r; assert(m); + assert(!message || !varlink); /* D-Bus and Varlink callers are mutually exclusive */ log_debug("Reloading..."); (void) notify_reloading(); @@ -1279,8 +1282,12 @@ int manager_reload(Manager *m, sd_bus_message *message) { } HASHMAP_FOREACH(link, m->links_by_index) - (void) link_reconfigure_full(link, /* flags= */ 0, message, - /* counter= */ message ? &m->reloading : NULL); + (void) link_reconfigure_full( + link, + /* flags= */ 0, + message, + varlink, + /* counter= */ (message || varlink) ? &m->reloading : NULL); log_debug("Reloaded."); r = 0; diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index 7b014017200dd..bacf3df444474 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -151,7 +151,7 @@ int manager_enumerate(Manager *m); int manager_set_hostname(Manager *m, const char *hostname); int manager_set_timezone(Manager *m, const char *tz); -int manager_reload(Manager *m, sd_bus_message *message); +int manager_reload(Manager *m, sd_bus_message *message, sd_varlink *varlink); static inline Hashmap** manager_get_sysctl_shadow(Manager *manager) { #if ENABLE_SYSCTL_BPF diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 93965f52536ec..e6b03c0826cad 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -1909,6 +1909,8 @@ static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt, bool zero _cleanup_free_ NDiscDNSSL *s = NULL; NDiscDNSSL *dnssl; + /* Silence static analyzers */ + assert(strlen(*j) <= SIZE_MAX - ALIGN(sizeof(NDiscDNSSL)) - 1); s = malloc0(ALIGN(sizeof(NDiscDNSSL)) + strlen(*j) + 1); if (!s) return log_oom(); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 10566b7a4ed85..aaf974e312d67 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -168,6 +168,7 @@ Network.IPv6ProxyNDP, config_parse_tristate, Network.IPv6MTUBytes, config_parse_mtu, AF_INET6, offsetof(Network, ipv6_mtu) Network.IPv4AcceptLocal, config_parse_tristate, 0, offsetof(Network, ipv4_accept_local) Network.IPv4RouteLocalnet, config_parse_tristate, 0, offsetof(Network, ipv4_route_localnet) +Network.IPv4SrcValidMark, config_parse_tristate, 0, offsetof(Network, ipv4_src_valid_mark) Network.ActiveSlave, config_parse_bool, 0, offsetof(Network, active_slave) Network.PrimarySlave, config_parse_bool, 0, offsetof(Network, primary_slave) Network.IPv4ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp) @@ -493,9 +494,16 @@ CAN.ClassicDataLengthCode, config_parse_can_control_mode, CAN.Termination, config_parse_can_termination, 0, 0 IPoIB.Mode, config_parse_ipoib_mode, 0, offsetof(Network, ipoib_mode) IPoIB.IgnoreUserspaceMulticastGroups, config_parse_tristate, 0, offsetof(Network, ipoib_umcast) -ModemManager.SimpleConnectProperties, config_parse_strv, 0, offsetof(Network, mm_simple_connect_props) -ModemManager.RouteMetric, config_parse_mm_route_metric, 0, 0 -ModemManager.UseGateway, config_parse_tristate, 0, offsetof(Network, mm_use_gateway) +MobileNetwork.APN, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, mm_apn) +MobileNetwork.AllowRoaming, config_parse_bool, 0, offsetof(Network, mm_allow_roaming) +MobileNetwork.AllowedAuthenticationMechanisms, config_parse_mm_allowed_auth, 0, offsetof(Network, mm_allowed_auth) +MobileNetwork.IPFamily, config_parse_mm_ip_family, 0, offsetof(Network, mm_ip_family) +MobileNetwork.OperatorId, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, mm_operator_id) +MobileNetwork.User, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, mm_user) +MobileNetwork.Password, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, mm_password) +MobileNetwork.PIN, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, mm_pin) +MobileNetwork.RouteMetric, config_parse_mm_route_metric, 0, 0 +MobileNetwork.UseGateway, config_parse_tristate, 0, offsetof(Network, mm_use_gateway) QDisc.Parent, config_parse_qdisc_parent, _QDISC_KIND_INVALID, 0 QDisc.Handle, config_parse_qdisc_handle, _QDISC_KIND_INVALID, 0 BFIFO.Parent, config_parse_qdisc_parent, QDISC_KIND_BFIFO, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 8141a45432e45..3ffe1640e767b 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -479,6 +479,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .ip_forwarding = { -1, -1, }, .ipv4_accept_local = -1, .ipv4_route_localnet = -1, + .ipv4_src_valid_mark = -1, .ipv6_privacy_extensions = _IPV6_PRIVACY_EXTENSIONS_INVALID, .ipv6_dad_transmits = -1, .ipv6_proxy_ndp = -1, @@ -513,6 +514,9 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .ipoib_mode = _IP_OVER_INFINIBAND_MODE_INVALID, .ipoib_umcast = -1, + .mm_allow_roaming = true, + .mm_allowed_auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN, + .mm_ip_family = MM_BEARER_IP_FAMILY_NONE, .mm_use_gateway = -1, }; @@ -553,7 +557,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi "LLDP\0" "TrafficControlQueueingDiscipline\0" "CAN\0" - "ModemManager\0" + "MobileNetwork\0" "QDisc\0" "BFIFO\0" "CAKE\0" @@ -851,7 +855,11 @@ static Network *network_free(Network *network) { hashmap_free(network->tclasses_by_section); /* ModemManager */ - strv_free(network->mm_simple_connect_props); + free(network->mm_apn); + free(network->mm_operator_id); + free(network->mm_user); + free(network->mm_password); + free(network->mm_pin); return mfree(network); } diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index dcd9f68e78197..9a36c312f8920 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -21,6 +21,7 @@ #include "networkd-ndisc.h" #include "networkd-radv.h" #include "networkd-sysctl.h" +#include "networkd-wwan-bus.h" #include "resolve-util.h" typedef enum KeepConfiguration { @@ -331,6 +332,7 @@ typedef struct Network { int ip_forwarding[2]; int ipv4_accept_local; int ipv4_route_localnet; + int ipv4_src_valid_mark; int ipv6_dad_transmits; uint8_t ipv6_hop_limit; usec_t ipv6_retransmission_time; @@ -416,7 +418,14 @@ typedef struct Network { char **ntp; /* ModemManager support */ - char **mm_simple_connect_props; + char *mm_apn; + bool mm_allow_roaming; + MMBearerAllowedAuth mm_allowed_auth; + MMBearerIpFamily mm_ip_family; + char *mm_operator_id; + char *mm_user; + char *mm_password; + char *mm_pin; int mm_use_gateway; uint32_t mm_route_metric; bool mm_route_metric_set; diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c index 5e5f0d0e6858b..9a32a17050d05 100644 --- a/src/network/networkd-nexthop.c +++ b/src/network/networkd-nexthop.c @@ -131,6 +131,8 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( int nexthop_new(NextHop **ret) { _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL; + assert(ret); + nexthop = new(NextHop, 1); if (!nexthop) return -ENOMEM; @@ -772,26 +774,33 @@ static bool nexthop_is_ready_to_configure(Link *link, const NextHop *nexthop) { if (!link_is_ready_to_configure(link, false)) return false; + /* Currently, we support the following three types of nexthops: + * 1. Simple nexthop - bound to the link, requires the underlying link is up. + * 2. Blackhole nexthop - not bound to the link. + * 3. Group nexthop - not bound to the link, but all group members must be configured first. + * + * Note, the kernel also supports fdb nexthop, but currently we do not support it. Note, fdb nexthop + * does not require IFF_UP. See rtm_to_nh_config() in net/ipv4/nexthop.c of kernel. */ + + /* Simple nexthop */ if (nexthop_bound_to_link(nexthop)) { assert(nexthop->ifindex == link->ifindex); - /* TODO: fdb nexthop does not require IFF_UP. The conditions below needs to be updated - * when fdb nexthop support is added. See rtm_to_nh_config() in net/ipv4/nexthop.c of - * kernel. */ - if (link->set_flags_messages > 0) - return false; - if (!FLAGS_SET(link->flags, IFF_UP)) - return false; + return gateway_is_ready(link, FLAGS_SET(nexthop->flags, RTNH_F_ONLINK), nexthop->family, &nexthop->gw.address); } - /* All group members must be configured first. */ + /* Blackhole nexthop */ + if (nexthop->blackhole) + return true; + + /* Group nexthop */ HASHMAP_FOREACH(nhg, nexthop->group) { r = nexthop_is_ready(link->manager, nhg->id, NULL); if (r <= 0) return r; } - return gateway_is_ready(link, FLAGS_SET(nexthop->flags, RTNH_F_ONLINK), nexthop->family, &nexthop->gw.address); + return true; } static int nexthop_process_request(Request *req, Link *link, NextHop *nexthop) { @@ -995,7 +1004,7 @@ void link_forget_nexthops(Link *link) { assert(link); assert(link->manager); assert(link->ifindex > 0); - assert(!FLAGS_SET(link->flags, IFF_UP)); + assert(!link_is_up(link)); /* See comments in link_forget_routes(). */ diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c index 0ef292bdf22f7..a71e01dfbf474 100644 --- a/src/network/networkd-radv.c +++ b/src/network/networkd-radv.c @@ -423,6 +423,7 @@ static int radv_find_uplink(Link *link, Link **ret) { int r; assert(link); + assert(ret); if (link->network->router_uplink_name) return link_get_by_name(link->manager, link->network->router_uplink_name, ret); diff --git a/src/network/networkd-route-util.c b/src/network/networkd-route-util.c index 37ace2d335c60..3a7156fc4129f 100644 --- a/src/network/networkd-route-util.c +++ b/src/network/networkd-route-util.c @@ -139,6 +139,12 @@ bool gateway_is_ready(Link *link, bool onlink, int family, const union in_addr_u assert(link); assert(link->manager); + if (link->set_flags_messages > 0) + return false; + + if (!link_is_up(link)) + return false; + if (onlink) return true; diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index ec2a7a334a65c..81cfe84add80a 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -234,7 +234,7 @@ DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( route_compare_func, route_unref); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( route_section_hash_ops, ConfigSection, config_section_hash_func, @@ -245,6 +245,8 @@ DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( int route_new(Route **ret) { _cleanup_(route_unrefp) Route *route = NULL; + assert(ret); + route = new(Route, 1); if (!route) return -ENOMEM; @@ -1641,7 +1643,7 @@ int link_drop_routes(Link *link, bool only_static) { void link_forget_routes(Link *link) { assert(link); assert(link->ifindex > 0); - assert(!FLAGS_SET(link->flags, IFF_UP)); + assert(!link_is_up(link)); /* When an interface went down, IPv4 non-local routes bound to the interface are silently removed by * the kernel, without any notifications. Let's forget them in that case. Otherwise, when the link diff --git a/src/network/networkd-routing-policy-rule.c b/src/network/networkd-routing-policy-rule.c index eb60d315bd200..1cefad29de50e 100644 --- a/src/network/networkd-routing-policy-rule.c +++ b/src/network/networkd-routing-policy-rule.c @@ -99,6 +99,8 @@ DEFINE_SECTION_CLEANUP_FUNCTIONS(RoutingPolicyRule, routing_policy_rule_unref); static int routing_policy_rule_new(RoutingPolicyRule **ret) { RoutingPolicyRule *rule; + assert(ret); + rule = new(RoutingPolicyRule, 1); if (!rule) return -ENOMEM; diff --git a/src/network/networkd-setlink.c b/src/network/networkd-setlink.c index 7069b101f9f55..f5a43788ef792 100644 --- a/src/network/networkd-setlink.c +++ b/src/network/networkd-setlink.c @@ -562,7 +562,7 @@ static int link_is_ready_to_set_link(Link *link, Request *req) { case REQUEST_TYPE_SET_LINK_CAN: /* Do not check link->set_flags_messages here, as it is ok even if link->flags * is outdated, and checking the counter causes a deadlock. */ - if (FLAGS_SET(link->flags, IFF_UP)) { + if (link_is_up(link)) { /* The CAN interface must be down to configure bitrate, etc... */ r = link_down_now(link); if (r < 0) @@ -626,14 +626,14 @@ static int link_is_ready_to_set_link(Link *link, Request *req) { /* Do not check link->set_flags_messages here, as it is ok even if link->flags is outdated, * and checking the counter causes a deadlock. */ - if (link->network->bond && FLAGS_SET(link->flags, IFF_UP)) { + if (link->network->bond && link_is_up(link)) { /* link must be down when joining to bond master. */ r = link_down_now(link); if (r < 0) return r; } - if (link->network->bridge && !FLAGS_SET(link->flags, IFF_UP) && link->dev) { + if (link->network->bridge && !link_is_up(link) && link->dev) { /* Some devices require the port to be up before joining the bridge. * * E.g. Texas Instruments SoC Ethernet running in switch mode: @@ -755,8 +755,7 @@ int link_request_to_set_addrgen_mode(Link *link) { * link goes down. Hence, we need to reset the interface. However, setting the mode by sysctl * does not need that. Let's use the sysctl interface when the link is already up. * See also issue #22424. */ - if (mode != IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_NONE && - FLAGS_SET(link->flags, IFF_UP)) { + if (mode != IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_NONE && link_is_up(link)) { r = link_set_ipv6ll_addrgen_mode(link, mode); if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 address generation mode, ignoring: %m"); @@ -1223,7 +1222,7 @@ static bool link_is_ready_to_bring_up_or_down(Link *link, bool up) { if (link_get_by_index(link->manager, link->dsa_master_ifindex, &master) < 0) return false; - if (!FLAGS_SET(master->flags, IFF_UP)) + if (!link_is_up(master)) return false; } diff --git a/src/network/networkd-speed-meter.c b/src/network/networkd-speed-meter.c index ade6c74331746..f04b11be9cf85 100644 --- a/src/network/networkd-speed-meter.c +++ b/src/network/networkd-speed-meter.c @@ -86,6 +86,38 @@ static int speed_meter_handler(sd_event_source *s, uint64_t usec, void *userdata return 0; } +void link_get_bit_rates(Link *link, uint64_t *ret_tx, uint64_t *ret_rx) { + Manager *manager; + double interval_sec; + + assert(link); + assert(ret_tx); + assert(ret_rx); + + manager = link->manager; + + if (!manager->use_speed_meter || + manager->speed_meter_usec_old == 0 || + !link->stats_updated) { + *ret_tx = UINT64_MAX; + *ret_rx = UINT64_MAX; + return; + } + + assert(manager->speed_meter_usec_new > manager->speed_meter_usec_old); + interval_sec = (double) (manager->speed_meter_usec_new - manager->speed_meter_usec_old) / USEC_PER_SEC; + + if (link->stats_new.tx_bytes > link->stats_old.tx_bytes) + *ret_tx = (uint64_t) ((link->stats_new.tx_bytes - link->stats_old.tx_bytes) / interval_sec); + else + *ret_tx = (uint64_t) ((UINT64_MAX - (link->stats_old.tx_bytes - link->stats_new.tx_bytes)) / interval_sec); + + if (link->stats_new.rx_bytes > link->stats_old.rx_bytes) + *ret_rx = (uint64_t) ((link->stats_new.rx_bytes - link->stats_old.rx_bytes) / interval_sec); + else + *ret_rx = (uint64_t) ((UINT64_MAX - (link->stats_old.rx_bytes - link->stats_new.rx_bytes)) / interval_sec); +} + int manager_start_speed_meter(Manager *manager) { _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; int r; diff --git a/src/network/networkd-speed-meter.h b/src/network/networkd-speed-meter.h index f7ef5a45697dd..8f98005322651 100644 --- a/src/network/networkd-speed-meter.h +++ b/src/network/networkd-speed-meter.h @@ -9,4 +9,5 @@ #define SPEED_METER_DEFAULT_TIME_INTERVAL (10 * USEC_PER_SEC) #define SPEED_METER_MINIMUM_TIME_INTERVAL (100 * USEC_PER_MSEC) +void link_get_bit_rates(Link *link, uint64_t *ret_tx, uint64_t *ret_rx); int manager_start_speed_meter(Manager *m); diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index b3b85f18f5c2e..7943314b06e3d 100644 --- a/src/network/networkd-state-file.c +++ b/src/network/networkd-state-file.c @@ -136,7 +136,7 @@ static int link_put_dns(Link *link, OrderedSet **s) { if (r >= 0) { struct in_addr_full **dot_servers; size_t n = 0; - CLEANUP_ARRAY(dot_servers, n, in_addr_full_array_free); + CLEANUP_ARRAY(dot_servers, n, in_addr_full_free_array); r = dns_resolvers_to_dot_addrs(resolvers, r, &dot_servers, &n); if (r < 0) @@ -165,7 +165,7 @@ static int link_put_dns(Link *link, OrderedSet **s) { if (r >= 0) { struct in_addr_full **dot_servers; size_t n = 0; - CLEANUP_ARRAY(dot_servers, n, in_addr_full_array_free); + CLEANUP_ARRAY(dot_servers, n, in_addr_full_free_array); r = dns_resolvers_to_dot_addrs(resolvers, r, &dot_servers, &n); if (r < 0) @@ -193,7 +193,7 @@ static int link_put_dns(Link *link, OrderedSet **s) { SET_FOREACH(a, link->ndisc_dnr) { struct in_addr_full **dot_servers = NULL; size_t n = 0; - CLEANUP_ARRAY(dot_servers, n, in_addr_full_array_free); + CLEANUP_ARRAY(dot_servers, n, in_addr_full_free_array); r = dns_resolvers_to_dot_addrs(&a->resolver, 1, &dot_servers, &n); if (r < 0) diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index 914fbccd09bf9..0801ba977a643 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -30,8 +30,8 @@ #if ENABLE_SYSCTL_BPF #include "bpf-link.h" -#include "bpf/sysctl-monitor/sysctl-monitor-skel.h" -#include "bpf/sysctl-monitor/sysctl-write-event.h" +#include "sysctl-monitor-skel.h" +#include "sysctl-write-event.h" static struct sysctl_monitor_bpf* sysctl_monitor_bpf_free(struct sysctl_monitor_bpf *obj) { sysctl_monitor_bpf__destroy(obj); @@ -108,7 +108,7 @@ int manager_install_sysctl_monitor(Manager *manager) { assert(manager); - r = dlopen_bpf(); + r = dlopen_bpf(LOG_DEBUG); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return log_debug_errno(r, "sysctl monitor disabled, as BPF support is not available."); if (r < 0) @@ -662,6 +662,20 @@ static int link_set_ipv4_route_localnet(Link *link) { return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "route_localnet", link->network->ipv4_route_localnet > 0, manager_get_sysctl_shadow(link->manager)); } +static int link_set_ipv4_src_valid_mark(Link *link) { + assert(link); + assert(link->manager); + assert(link->network); + + if (!link_is_configured_for_family(link, AF_INET)) + return 0; + + if (link->network->ipv4_src_valid_mark < 0) + return 0; + + return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "src_valid_mark", link->network->ipv4_src_valid_mark > 0, manager_get_sysctl_shadow(link->manager)); +} + static int link_set_ipv4_promote_secondaries(Link *link) { assert(link); assert(link->manager); @@ -750,6 +764,10 @@ int link_set_sysctl(Link *link) { if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv4 route_localnet flag for interface, ignoring: %m"); + r = link_set_ipv4_src_valid_mark(link); + if (r < 0) + log_link_warning_errno(link, r, "Cannot set IPv4 src_valid_mark flag for interface, ignoring: %m"); + r = link_set_ipv4_rp_filter(link); if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv4 reverse path filtering for interface, ignoring: %m"); diff --git a/src/network/networkd-varlink-metrics.c b/src/network/networkd-varlink-metrics.c index d37f65b43d2a1..50aacebbf8077 100644 --- a/src/network/networkd-varlink-metrics.c +++ b/src/network/networkd-varlink-metrics.c @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-json.h" #include "sd-varlink.h" +#include "alloc-util.h" #include "argv-util.h" #include "errno-util.h" #include "fd-util.h" @@ -99,6 +101,55 @@ static int managed_interfaces_build_json(MetricFamilyContext *context, void *use return metric_build_send_unsigned(context, /* object= */ NULL, count, /* fields= */ NULL); } +static int required_for_online_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Link *link; + int r; + + assert(context); + + HASHMAP_FOREACH(link, manager->links_by_index) { + if (!link->network) + continue; + + if (link->network->required_for_online == 0) { + r = metric_build_send_string( + context, + link->ifname, + "no", + /* fields= */ NULL); + } else { + LinkOperationalStateRange range; + link_required_operstate_for_online(link, &range); + + const char *min_str = link_operstate_to_string(range.min); + const char *max_str = link_operstate_to_string(range.max); + + if (range.min == range.max) + r = metric_build_send_string( + context, + link->ifname, + min_str, + /* fields= */ NULL); + else { + _cleanup_free_ char *value = NULL; + if (asprintf(&value, "%s:%s", min_str, max_str) < 0) + return -ENOMEM; + + r = metric_build_send_string( + context, + link->ifname, + value, + /* fields= */ NULL); + } + } + if (r < 0) + return r; + } + + return 0; +} + /* Keep metrics ordered alphabetically */ static const MetricFamily network_metric_family_table[] = { { @@ -143,6 +194,12 @@ static const MetricFamily network_metric_family_table[] = { .type = METRIC_FAMILY_TYPE_STRING, .generate = link_oper_state_build_json, }, + { + .name = METRIC_IO_SYSTEMD_NETWORK_PREFIX "RequiredForOnline", + .description = "Per interface metric: required operational state for online, or 'no' if not required", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = required_for_online_build_json, + }, {} }; diff --git a/src/network/networkd-wwan-bus.c b/src/network/networkd-wwan-bus.c index d87cdd3441185..19fcf081ae188 100644 --- a/src/network/networkd-wwan-bus.c +++ b/src/network/networkd-wwan-bus.c @@ -59,7 +59,6 @@ #include "networkd-manager.h" #include "networkd-wwan.h" #include "networkd-wwan-bus.h" -#include "parse-util.h" #include "string-util.h" #include "strv.h" @@ -507,78 +506,6 @@ static int modem_connect_handler(sd_bus_message *message, void *userdata, sd_bus return 0; } -static MMBearerIpFamily prop_iptype_lookup(const char *key) { - static const struct { - MMBearerIpFamily family; - const char *str; - } table[] = { - { MM_BEARER_IP_FAMILY_NONE, "none" }, - { MM_BEARER_IP_FAMILY_IPV4, "ipv4" }, - { MM_BEARER_IP_FAMILY_IPV6, "ipv6" }, - { MM_BEARER_IP_FAMILY_IPV4V6, "ipv4v6" }, - { MM_BEARER_IP_FAMILY_ANY, "any" }, - {} - }; - - assert(key); - - FOREACH_ELEMENT(item, table) - if (streq(item->str, key)) - return item->family; - - log_warning("ModemManager: ignoring unknown ip-type: %s, using any", key); - return MM_BEARER_IP_FAMILY_ANY; -} - -static MMBearerAllowedAuth prop_auth_lookup(const char *key) { - static const struct { - MMBearerAllowedAuth auth; - const char *str; - } table[] = { - { MM_BEARER_ALLOWED_AUTH_NONE, "none" }, - { MM_BEARER_ALLOWED_AUTH_PAP, "pap" }, - { MM_BEARER_ALLOWED_AUTH_CHAP, "chap" }, - { MM_BEARER_ALLOWED_AUTH_MSCHAP, "mschap" }, - { MM_BEARER_ALLOWED_AUTH_MSCHAPV2, "mschapv2" }, - { MM_BEARER_ALLOWED_AUTH_EAP, "eap" }, - {} - }; - - assert(key); - - FOREACH_ELEMENT(item, table) - if (streq(item->str, key)) - return item->auth; - - log_warning("ModemManager: ignoring unknown allowed-auth: %s, using none", key); - return MM_BEARER_ALLOWED_AUTH_NONE; -} - -static const char* prop_type_lookup(const char *key) { - static const struct { - const char *prop; - const char *type; - } table[] = { - { "apn", "s" }, - { "allowed-auth", "u" }, - { "user", "s" }, - { "password", "s" }, - { "ip-type", "u" }, - { "allow-roaming", "b" }, - { "pin", "s" }, - { "operator-id", "s" }, - {} - }; - - if (!key) - return NULL; - - FOREACH_ELEMENT(item, table) - if (streq(item->prop, key)) - return item->type; - return NULL; -} - static int bus_call_method_async_props( sd_bus *bus, sd_bus_slot **slot, @@ -591,6 +518,7 @@ static int bus_call_method_async_props( Link *link) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + Network *network = ASSERT_PTR(ASSERT_PTR(link)->network); int r; assert(bus); @@ -603,38 +531,48 @@ static int bus_call_method_async_props( if (r < 0) return bus_log_create_error(r); - STRV_FOREACH(prop, link->network->mm_simple_connect_props) { - const char *type; - _cleanup_free_ char *left = NULL, *right = NULL; + if (network->mm_apn) { + r = sd_bus_message_append(m, "{sv}", "apn", "s", network->mm_apn); + if (r < 0) + return bus_log_create_error(r); + } - r = split_pair(*prop, "=", &left, &right); + r = sd_bus_message_append(m, "{sv}", "allow-roaming", "b", network->mm_allow_roaming); + if (r < 0) + return bus_log_create_error(r); + + if (network->mm_allowed_auth != MM_BEARER_ALLOWED_AUTH_UNKNOWN) { + r = sd_bus_message_append(m, "{sv}", "allowed-auth", "u", (uint32_t) network->mm_allowed_auth); if (r < 0) - return log_warning_errno(SYNTHETIC_ERRNO(r), - "ModemManager: failed to parse simple connect option: %s, file: %s", - *prop, link->network->filename); - - type = prop_type_lookup(left); - if (!type) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "ModemManager: unknown simple connect option: %s, file: %s", - *prop, link->network->filename); - - if (streq(left, "ip-type")) { - MMBearerIpFamily ip_type = prop_iptype_lookup(right); - - r = sd_bus_message_append(m, "{sv}", left, type, (uint32_t)ip_type); - } if (streq(left, "allowed-auth")) { - MMBearerAllowedAuth auth = prop_auth_lookup(right); - - r = sd_bus_message_append(m, "{sv}", left, type, (uint32_t)auth); - } else if (streq(type, "b")) { - r = parse_boolean(right); - if (r < 0) - return -EINVAL; - r = sd_bus_message_append(m, "{sv}", left, type, r); - } else if (streq(type, "s")) - r = sd_bus_message_append(m, "{sv}", left, type, right); + return bus_log_create_error(r); + } + + if (network->mm_ip_family != MM_BEARER_IP_FAMILY_NONE) { + r = sd_bus_message_append(m, "{sv}", "ip-type", "u", (uint32_t) network->mm_ip_family); + if (r < 0) + return bus_log_create_error(r); + } + + if (network->mm_operator_id) { + r = sd_bus_message_append(m, "{sv}", "operator-id", "s", network->mm_operator_id); + if (r < 0) + return bus_log_create_error(r); + } + if (network->mm_user) { + r = sd_bus_message_append(m, "{sv}", "user", "s", network->mm_user); + if (r < 0) + return bus_log_create_error(r); + } + + if (network->mm_password) { + r = sd_bus_message_append(m, "{sv}", "password", "s", network->mm_password); + if (r < 0) + return bus_log_create_error(r); + } + + if (network->mm_pin) { + r = sd_bus_message_append(m, "{sv}", "pin", "s", network->mm_pin); if (r < 0) return bus_log_create_error(r); } @@ -666,18 +604,18 @@ static void modem_simple_connect(Modem *modem) { if (!modem->port_name) return; - (void) link_get_by_name(modem->manager, modem->port_name, &link); - if (!link) - return (void) log_debug("ModemManager: cannot find link for %s", modem->port_name); + r = link_get_by_name(modem->manager, modem->port_name, &link); + if (r < 0) + return (void) log_debug_errno(r, "ModemManager: cannot find link for %s: %m", modem->port_name); /* Check if .network file found at all */ if (!link->network) return (void) log_debug("ModemManager: no .network file provided for %s", modem->port_name); - /* Check if we are provided with simple connection properties */ - if (!link->network->mm_simple_connect_props) - return (void) log_debug("ModemManager: no simple connect properties provided for %s", + /* Check if we are provided with at least APN which is required. */ + if (!link->network->mm_apn) + return (void) log_debug("ModemManager: not enough simple connect properties provided for %s", modem->port_name); log_info("ModemManager: starting simple connect on %s %s interface %s", @@ -869,15 +807,10 @@ static int bearer_properties_changed_handler( if (!path) return 0; - if (bearer_get_by_path(manager, path, &modem, &b) < 0) { - /* - * Have new bearer: check if we have the corresponding modem - * for it which we might not during initialization. - */ - if (modem) - (void) bearer_new_and_initialize(modem, path); + if (bearer_get_by_path(manager, path, &modem, &b) < 0) + /* Unknown bearer, nothing to do. Modem-bearer association is handled + * by modem_map_bearers() during modem property initialization. */ return 0; - } if (b->slot_getall) { /* Not initialized yet. Re-initialize it. */ diff --git a/src/network/networkd-wwan.c b/src/network/networkd-wwan.c index 325d2b028188b..ddc5ca38b45db 100644 --- a/src/network/networkd-wwan.c +++ b/src/network/networkd-wwan.c @@ -2,6 +2,7 @@ #include "alloc-util.h" #include "bus-util.h" +#include "extract-word.h" #include "hashmap.h" #include "networkd-address.h" #include "networkd-dhcp4.h" @@ -36,7 +37,7 @@ Bearer* bearer_free(Bearer *b) { free(b->name); free(b->apn); - in_addr_full_array_free(b->dns, b->n_dns); + in_addr_full_free_array(b->dns, b->n_dns); return mfree(b); } @@ -151,11 +152,8 @@ Modem* modem_free(Modem *modem) { if (!modem) return NULL; - if (modem->bearers_by_name) - hashmap_free(modem->bearers_by_name); - - if (modem->bearers_by_path) - hashmap_free(modem->bearers_by_path); + hashmap_free(modem->bearers_by_name); + hashmap_free(modem->bearers_by_path); if (modem->manager) hashmap_remove_value(modem->manager->modems_by_path, modem->path, modem); @@ -237,6 +235,7 @@ int link_get_modem(Link *link, Modem **ret) { assert(link); assert(link->manager); assert(link->ifname); + assert(ret); HASHMAP_FOREACH(modem, link->manager->modems_by_path) if (modem->port_name && streq(modem->port_name, link->ifname)) { @@ -479,7 +478,7 @@ static int link_apply_bearer_impl(Link *link, Bearer *b) { if (r < 0) return r; - r = link_request_bearer_route(link, AF_INET6, &b->ip6_gateway, NULL); + r = link_request_bearer_route(link, AF_INET6, &b->ip6_gateway, &b->ip6_address); if (r < 0) return r; } @@ -529,7 +528,7 @@ static int link_apply_bearer_impl(Link *link, Bearer *b) { continue; r = route_remove(route, link->manager); - if (ret) + if (r < 0) ret = r; } @@ -620,7 +619,7 @@ int config_parse_mm_route_metric( void *data, void *userdata) { - Network *network = userdata; + Network *network = ASSERT_PTR(userdata); int r; assert(filename); @@ -639,3 +638,102 @@ int config_parse_mm_route_metric( network->mm_route_metric_set = true; return 0; } + +int config_parse_mm_allowed_auth( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + static const struct { + MMBearerAllowedAuth auth; + const char *str; + } allowed_auth_map[] = { + { MM_BEARER_ALLOWED_AUTH_NONE, "none" }, + { MM_BEARER_ALLOWED_AUTH_PAP, "pap" }, + { MM_BEARER_ALLOWED_AUTH_CHAP, "chap" }, + { MM_BEARER_ALLOWED_AUTH_MSCHAP, "mschap" }, + { MM_BEARER_ALLOWED_AUTH_MSCHAPV2, "mschapv2" }, + { MM_BEARER_ALLOWED_AUTH_EAP, "eap" }, + }; + MMBearerAllowedAuth *allowed_auth = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *allowed_auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN; + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *auth = NULL; + + r = extract_first_word(&p, &auth, /* separators */ NULL, /* flags */ 0); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + if (r == 0) + return 0; + + bool found = false; + FOREACH_ELEMENT(i, allowed_auth_map) + if (streq(auth, i->str)) { + *allowed_auth |= i->auth; + found = true; + break; + } + + if (!found) + log_syntax(unit, LOG_WARNING, filename, line, -EINVAL, + "Unknown auth value '%s', ignoring", auth); + } +} + +int config_parse_mm_ip_family( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + static const struct { + MMBearerIpFamily family; + const char *str; + } ip_family_map[] = { + { MM_BEARER_IP_FAMILY_IPV4, "ipv4" }, + { MM_BEARER_IP_FAMILY_IPV6, "ipv6" }, + { MM_BEARER_IP_FAMILY_IPV4V6, "both" }, + { MM_BEARER_IP_FAMILY_ANY, "any" }, + }; + MMBearerIpFamily *ip_family = ASSERT_PTR(data); + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *ip_family = MM_BEARER_IP_FAMILY_NONE; + return 0; + } + + FOREACH_ELEMENT(i, ip_family_map) + if (streq(rvalue, i->str)) { + *ip_family = i->family; + return 0; + } + + return log_syntax_parse_error(unit, filename, line, -EINVAL, lvalue, rvalue); +} diff --git a/src/network/networkd-wwan.h b/src/network/networkd-wwan.h index 962f76be2ec40..0542ac174d29d 100644 --- a/src/network/networkd-wwan.h +++ b/src/network/networkd-wwan.h @@ -77,4 +77,6 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Modem*, modem_free); int modem_get_by_path(Manager *m, const char *path, Modem **ret); int link_get_modem(Link *link, Modem **ret); +CONFIG_PARSER_PROTOTYPE(config_parse_mm_allowed_auth); +CONFIG_PARSER_PROTOTYPE(config_parse_mm_ip_family); CONFIG_PARSER_PROTOTYPE(config_parse_mm_route_metric); diff --git a/src/network/org.freedesktop.network1.policy b/src/network/org.freedesktop.network1.policy index 9d3ed87d6a70e..875e52708d4fa 100644 --- a/src/network/org.freedesktop.network1.policy +++ b/src/network/org.freedesktop.network1.policy @@ -141,7 +141,7 @@ DHCP server sends force renew message - Authentication is required to send force renew message. + Authentication is required to send a force renew message from the DHCP server. auth_admin auth_admin diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c index e16ace841e3ae..e0d383763f4a3 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -115,6 +115,8 @@ static int qdisc_new(QDiscKind kind, QDisc **ret) { _cleanup_(qdisc_unrefp) QDisc *qdisc = NULL; int r; + assert(ret); + if (kind == _QDISC_KIND_INVALID) { qdisc = new(QDisc, 1); if (!qdisc) diff --git a/src/network/tc/tclass.c b/src/network/tc/tclass.c index 3b53a59a1b686..5d7664866d4c5 100644 --- a/src/network/tc/tclass.c +++ b/src/network/tc/tclass.c @@ -77,6 +77,8 @@ static int tclass_new(TClassKind kind, TClass **ret) { _cleanup_(tclass_unrefp) TClass *tclass = NULL; int r; + assert(ret); + if (kind == _TCLASS_KIND_INVALID) { tclass = new(TClass, 1); if (!tclass) diff --git a/src/network/test-modem-manager-mock.c b/src/network/test-modem-manager-mock.c new file mode 100644 index 0000000000000..60f0dfa8d4ea2 --- /dev/null +++ b/src/network/test-modem-manager-mock.c @@ -0,0 +1,486 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* + * Minimal mock of ModemManager's D-Bus interface for testing systemd-networkd + * wwan/bearer support. + * + * Claims the org.freedesktop.ModemManager1 bus name and responds to: + * - GetManagedObjects on /org/freedesktop/ModemManager1 + * - GetAll on /org/freedesktop/ModemManager1/Bearer/0 + * - Simple.Connect on /org/freedesktop/ModemManager1/Modem/0 + */ + +#include + +#include "sd-bus.h" +#include "sd-daemon.h" +#include "sd-event.h" + +#include "alloc-util.h" +#include "build.h" +#include "log.h" +#include "main-func.h" +#include "parse-util.h" +#include "string-util.h" + +static char *arg_ifname = NULL; +static char *arg_ipv4_address = NULL; +static char *arg_ipv4_gateway = NULL; +static uint32_t arg_ipv4_prefix = 24; +static char *arg_ipv6_address = NULL; +static char *arg_ipv6_gateway = NULL; +static uint32_t arg_ipv6_prefix = 64; + +STATIC_DESTRUCTOR_REGISTER(arg_ifname, freep); +STATIC_DESTRUCTOR_REGISTER(arg_ipv4_address, freep); +STATIC_DESTRUCTOR_REGISTER(arg_ipv4_gateway, freep); +STATIC_DESTRUCTOR_REGISTER(arg_ipv6_address, freep); +STATIC_DESTRUCTOR_REGISTER(arg_ipv6_gateway, freep); + +/* ModemManager enum values */ +#define MM_BEARER_IP_METHOD_STATIC 2 +#define MM_MODEM_PORT_TYPE_NET 2 +#define MM_MODEM_STATE_CONNECTED 11 + +static int append_bearer_properties(sd_bus_message *reply) { + int r; + + /* a{sv} of bearer properties */ + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return r; + + /* Interface */ + r = sd_bus_message_append(reply, "{sv}", "Interface", "s", arg_ifname); + if (r < 0) + return r; + + /* Connected */ + r = sd_bus_message_append(reply, "{sv}", "Connected", "b", true); + if (r < 0) + return r; + + /* Ip4Config: a{sv} */ + if (arg_ipv4_address) { + r = sd_bus_message_open_container(reply, 'e', "sv"); + if (r < 0) + return r; + r = sd_bus_message_append_basic(reply, 's', "Ip4Config"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'v', "a{sv}"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "method", "u", (uint32_t) MM_BEARER_IP_METHOD_STATIC); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "address", "s", arg_ipv4_address); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "prefix", "u", arg_ipv4_prefix); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "gateway", "s", arg_ipv4_gateway); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "mtu", "u", (uint32_t) 1500); + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* a{sv} */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* v */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* e */ + if (r < 0) + return r; + } + + /* Ip6Config: a{sv} */ + if (arg_ipv6_address) { + r = sd_bus_message_open_container(reply, 'e', "sv"); + if (r < 0) + return r; + r = sd_bus_message_append_basic(reply, 's', "Ip6Config"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'v', "a{sv}"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "method", "u", (uint32_t) MM_BEARER_IP_METHOD_STATIC); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "address", "s", arg_ipv6_address); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "prefix", "u", arg_ipv6_prefix); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "gateway", "s", arg_ipv6_gateway); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "mtu", "u", (uint32_t) 1500); + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* a{sv} */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* v */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* e */ + if (r < 0) + return r; + } + + /* Properties: a{sv} with apn */ + r = sd_bus_message_open_container(reply, 'e', "sv"); + if (r < 0) + return r; + r = sd_bus_message_append_basic(reply, 's', "Properties"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'v', "a{sv}"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "apn", "s", "internet.test"); + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* a{sv} */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* v */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* e */ + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); /* outer a{sv} */ + if (r < 0) + return r; + + return 0; +} + +static int handle_get_managed_objects(sd_bus_message *msg, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + r = sd_bus_message_new_method_return(msg, &reply); + if (r < 0) + return r; + + /* a{oa{sa{sv}}} */ + r = sd_bus_message_open_container(reply, 'a', "{oa{sa{sv}}}"); + if (r < 0) + return r; + + /* Modem object */ + r = sd_bus_message_open_container(reply, 'e', "oa{sa{sv}}"); + if (r < 0) + return r; + r = sd_bus_message_append_basic(reply, 'o', "/org/freedesktop/ModemManager1/Modem/0"); + if (r < 0) + return r; + + /* Array of interfaces */ + r = sd_bus_message_open_container(reply, 'a', "{sa{sv}}"); + if (r < 0) + return r; + + /* org.freedesktop.ModemManager1.Modem interface */ + r = sd_bus_message_open_container(reply, 'e', "sa{sv}"); + if (r < 0) + return r; + r = sd_bus_message_append_basic(reply, 's', "org.freedesktop.ModemManager1.Modem"); + if (r < 0) + return r; + + /* Modem properties: a{sv} */ + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return r; + + /* Bearers: ao */ + r = sd_bus_message_append(reply, "{sv}", "Bearers", "ao", 1, "/org/freedesktop/ModemManager1/Bearer/0"); + if (r < 0) + return r; + + /* State: i (CONNECTED) */ + r = sd_bus_message_append(reply, "{sv}", "State", "i", (int32_t) MM_MODEM_STATE_CONNECTED); + if (r < 0) + return r; + + /* StateFailedReason: u (NONE) */ + r = sd_bus_message_append(reply, "{sv}", "StateFailedReason", "u", (uint32_t) 0); + if (r < 0) + return r; + + /* Manufacturer */ + r = sd_bus_message_append(reply, "{sv}", "Manufacturer", "s", "MockModem"); + if (r < 0) + return r; + + /* Model */ + r = sd_bus_message_append(reply, "{sv}", "Model", "s", "Virtual"); + if (r < 0) + return r; + + /* Ports: a(su) — array of structs with port name and type */ + r = sd_bus_message_open_container(reply, 'e', "sv"); + if (r < 0) + return r; + r = sd_bus_message_append_basic(reply, 's', "Ports"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'v', "a(su)"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'a', "(su)"); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "(su)", arg_ifname, (uint32_t) MM_MODEM_PORT_TYPE_NET); + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* a(su) */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* v */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* e */ + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); /* modem properties a{sv} */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* e sa{sv} */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* a{sa{sv}} */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* e oa{sa{sv}} */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* a{oa{sa{sv}}} */ + if (r < 0) + return r; + + r = sd_bus_send(NULL, reply, NULL); + if (r < 0) + return r; + + return 1; /* handled */ +} + +static int handle_get_all(sd_bus_message *msg, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + r = sd_bus_message_new_method_return(msg, &reply); + if (r < 0) + return r; + + /* bearer_get_all_handler() in networkd expects a leading interface name string + * before the a{sv} properties dict (it calls sd_bus_message_skip(message, "s")). */ + r = sd_bus_message_append_basic(reply, 's', "org.freedesktop.ModemManager1.Bearer"); + if (r < 0) + return r; + + r = append_bearer_properties(reply); + if (r < 0) + return r; + + r = sd_bus_send(NULL, reply, NULL); + if (r < 0) + return r; + + return 1; /* handled */ +} + +static int handle_simple_connect(sd_bus_message *msg, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + /* Return the bearer path */ + r = sd_bus_message_new_method_return(msg, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "o", "/org/freedesktop/ModemManager1/Bearer/0"); + if (r < 0) + return r; + + r = sd_bus_send(NULL, reply, NULL); + if (r < 0) + return r; + + return 1; /* handled */ +} + +static int filter_handler(sd_bus_message *m, void *userdata, sd_bus_error *error) { + const char *path, *interface, *member; + uint8_t type; + + if (sd_bus_message_get_type(m, &type) < 0 || type != SD_BUS_MESSAGE_METHOD_CALL) + return 0; + + path = sd_bus_message_get_path(m); + interface = sd_bus_message_get_interface(m); + member = sd_bus_message_get_member(m); + + if (!path || !interface || !member) + return 0; + + if (streq(path, "/org/freedesktop/ModemManager1") && + streq(interface, "org.freedesktop.DBus.ObjectManager") && + streq(member, "GetManagedObjects")) + return handle_get_managed_objects(m, userdata, error); + + if (startswith(path, "/org/freedesktop/ModemManager1/Bearer/") && + streq(interface, "org.freedesktop.DBus.Properties") && + streq(member, "GetAll")) + return handle_get_all(m, userdata, error); + + if (startswith(path, "/org/freedesktop/ModemManager1/Modem/") && + streq(interface, "org.freedesktop.ModemManager1.Modem.Simple") && + streq(member, "Connect")) + return handle_simple_connect(m, userdata, error); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_IFNAME = 0x100, + ARG_IPV4_ADDRESS, + ARG_IPV4_GATEWAY, + ARG_IPV4_PREFIX, + ARG_IPV6_ADDRESS, + ARG_IPV6_GATEWAY, + ARG_IPV6_PREFIX, + }; + + static const struct option options[] = { + { "ifname", required_argument, NULL, ARG_IFNAME }, + { "ipv4-address", required_argument, NULL, ARG_IPV4_ADDRESS }, + { "ipv4-gateway", required_argument, NULL, ARG_IPV4_GATEWAY }, + { "ipv4-prefix", required_argument, NULL, ARG_IPV4_PREFIX }, + { "ipv6-address", required_argument, NULL, ARG_IPV6_ADDRESS }, + { "ipv6-gateway", required_argument, NULL, ARG_IPV6_GATEWAY }, + { "ipv6-prefix", required_argument, NULL, ARG_IPV6_PREFIX }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + + int c, r; + + while ((c = getopt_long(argc, argv, "vh", options, NULL)) >= 0) + switch (c) { + case ARG_IFNAME: + if (free_and_strdup(&arg_ifname, optarg) < 0) + return log_oom(); + break; + case ARG_IPV4_ADDRESS: + if (free_and_strdup(&arg_ipv4_address, optarg) < 0) + return log_oom(); + break; + case ARG_IPV4_GATEWAY: + if (free_and_strdup(&arg_ipv4_gateway, optarg) < 0) + return log_oom(); + break; + case ARG_IPV4_PREFIX: + r = safe_atou32(optarg, &arg_ipv4_prefix); + if (r < 0) + return log_error_errno(r, "Failed to parse IPv4 prefix length: %m"); + break; + case ARG_IPV6_ADDRESS: + if (free_and_strdup(&arg_ipv6_address, optarg) < 0) + return log_oom(); + break; + case ARG_IPV6_GATEWAY: + if (free_and_strdup(&arg_ipv6_gateway, optarg) < 0) + return log_oom(); + break; + case ARG_IPV6_PREFIX: + r = safe_atou32(optarg, &arg_ipv6_prefix); + if (r < 0) + return log_error_errno(r, "Failed to parse IPv6 prefix length: %m"); + break; + case 'v': + return version(); + case 'h': + printf("Usage: %s [OPTIONS...]\n\n" + "Mock ModemManager D-Bus service for testing.\n\n" + " --ifname=NAME Interface name\n" + " --ipv4-address=ADDR IPv4 address\n" + " --ipv4-gateway=ADDR IPv4 gateway\n" + " --ipv4-prefix=LEN IPv4 prefix length\n" + " --ipv6-address=ADDR IPv6 address\n" + " --ipv6-gateway=ADDR IPv6 gateway\n" + " --ipv6-prefix=LEN IPv6 prefix length\n" + " -h, --help Show this help\n" + " -v, --version Show version\n", + program_invocation_short_name); + return 0; + default: + return -EINVAL; + } + + if (!arg_ifname) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--ifname is required"); + + return 1; /* work to do */ +} + +static int run(int argc, char *argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + r = sd_event_new(&event); + if (r < 0) + return log_error_errno(r, "Failed to create event loop: %m"); + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to system bus: %m"); + + r = sd_bus_add_filter(bus, NULL, filter_handler, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add filter: %m"); + + r = sd_bus_request_name(bus, "org.freedesktop.ModemManager1", 0); + if (r < 0) + return log_error_errno(r, "Failed to acquire bus name: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + (void) sd_notify(0, "READY=1"); + + return sd_event_loop(event); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/network/test-network.c b/src/network/test-network.c index 253e0d660d64f..ab3a86e10b193 100644 --- a/src/network/test-network.c +++ b/src/network/test-network.c @@ -11,6 +11,7 @@ #include "networkd-route-util.h" #include "strv.h" #include "tests.h" +#include "vrf.h" TEST(deserialize_in_addr) { _cleanup_free_ struct in_addr *addresses = NULL; @@ -128,7 +129,7 @@ TEST(route_tables) { test_route_tables_one(manager, "bbb", 11111); test_route_tables_one(manager, "ccc", 22222); - ASSERT_NULL(hashmap_get(manager->route_table_numbers_by_name, "ddd")); + ASSERT_FALSE(hashmap_contains(manager->route_table_numbers_by_name, "ddd")); test_route_tables_one(manager, "default", 253); test_route_tables_one(manager, "main", 254); @@ -150,6 +151,29 @@ TEST(route_tables) { test_route_tables_one(manager, "local", 255); } +TEST(vrf_table) { + _cleanup_(manager_freep) Manager *manager = NULL; + Vrf vrf = {}; + + ASSERT_OK(manager_new(&manager, /* test_mode= */ true)); + ASSERT_OK(manager_setup(manager)); + + vrf.meta.manager = manager; + + ASSERT_OK(config_parse_vrf_table("netdev", "filename", 1, "VRF", 1, "Table", 0, "default", &vrf.table, &vrf)); + ASSERT_EQ(vrf.table, 253U); + + ASSERT_OK(config_parse_route_table_names("manager", "filename", 1, "section", 1, "RouteTable", 0, "vrf-test:1234", manager, manager)); + ASSERT_OK(config_parse_vrf_table("netdev", "filename", 1, "VRF", 1, "Table", 0, "vrf-test", &vrf.table, &vrf)); + ASSERT_EQ(vrf.table, 1234U); + + ASSERT_OK(config_parse_vrf_table("netdev", "filename", 1, "VRF", 1, "Table", 0, "5678", &vrf.table, &vrf)); + ASSERT_EQ(vrf.table, 5678U); + + ASSERT_OK(config_parse_vrf_table("netdev", "filename", 1, "VRF", 1, "Table", 0, "no-such-table", &vrf.table, &vrf)); + ASSERT_EQ(vrf.table, 5678U); +} + TEST(manager_enumerate) { _cleanup_(manager_freep) Manager *manager = NULL; diff --git a/src/notify/notify.c b/src/notify/notify.c index 6a39147f99e1c..33d8fdf2c0912 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -16,13 +15,15 @@ #include "exit-status.h" #include "fd-util.h" #include "fdset.h" +#include "format-table.h" #include "format-util.h" +#include "help-util.h" #include "log.h" #include "main-func.h" #include "notify-recv.h" +#include "options.h" #include "parse-util.h" #include "pidref.h" -#include "pretty-print.h" #include "process-util.h" #include "signal-util.h" #include "string-util.h" @@ -56,41 +57,24 @@ STATIC_DESTRUCTOR_REGISTER(arg_fds, fdset_freep); STATIC_DESTRUCTOR_REGISTER(arg_fdname, freep); static int help(void) { - _cleanup_free_ char *link = NULL; int r; - r = terminal_urlify_man("systemd-notify", "1", &link); + _cleanup_(table_unrefp) Table *options = NULL; + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; - printf("%s [OPTIONS...] [VARIABLE=VALUE...]\n" - "%s [OPTIONS...] --exec [VARIABLE=VALUE...] ; -- CMDLINE...\n" - "%s [OPTIONS...] --fork -- CMDLINE...\n" - "\n%sNotify the init system about service status updates.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --ready Inform the service manager about service start-up/reload\n" - " completion\n" - " --reloading Inform the service manager about configuration reloading\n" - " --stopping Inform the service manager about service shutdown\n" - " --pid[=PID] Set main PID of daemon\n" - " --uid=USER Set user to send from\n" - " --status=TEXT Set status text\n" - " --booted Check if the system was booted up with systemd\n" - " --no-block Do not wait until operation finished\n" - " --exec Execute command line separated by ';' once done\n" - " --fd=FD Pass specified file descriptor with along with message\n" - " --fdname=NAME Name to assign to passed file descriptor(s)\n" - " --fork Receive notifications from child rather than sending them\n" - " -q --quiet Do not show PID of child when forking\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - program_invocation_short_name, - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + help_cmdline("[OPTIONS...] [VARIABLE=VALUE...]"); + help_cmdline("[OPTIONS...] --exec [VARIABLE=VALUE...] ; -- CMDLINE..."); + help_cmdline("[OPTIONS...] --fork -- CMDLINE..."); + help_abstract("Notify the service manager about service status updates."); + help_section("Options:"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-notify", "1"); return 0; } @@ -161,123 +145,87 @@ static int pidref_parent_if_applicable(PidRef *ret) { return pidref_set_self(ret); } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_READY = 0x100, - ARG_RELOADING, - ARG_STOPPING, - ARG_VERSION, - ARG_PID, - ARG_STATUS, - ARG_BOOTED, - ARG_UID, - ARG_NO_BLOCK, - ARG_EXEC, - ARG_FD, - ARG_FDNAME, - ARG_FORK, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "ready", no_argument, NULL, ARG_READY }, - { "reloading", no_argument, NULL, ARG_RELOADING }, - { "stopping", no_argument, NULL, ARG_STOPPING }, - { "pid", optional_argument, NULL, ARG_PID }, - { "status", required_argument, NULL, ARG_STATUS }, - { "booted", no_argument, NULL, ARG_BOOTED }, - { "uid", required_argument, NULL, ARG_UID }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "exec", no_argument, NULL, ARG_EXEC }, - { "fd", required_argument, NULL, ARG_FD }, - { "fdname", required_argument, NULL, ARG_FDNAME }, - { "fork", no_argument, NULL, ARG_FORK }, - { "quiet", no_argument, NULL, 'q' }, - {} - }; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { _cleanup_fdset_free_ FDSet *passed = NULL; bool do_exec = false; - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hq", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_READY: + OPTION_LONG("ready", NULL, + "Inform the service manager about service start-up/reload completion"): arg_ready = true; break; - case ARG_RELOADING: + OPTION_LONG("reloading", NULL, + "Inform the service manager about configuration reloading"): arg_reloading = true; break; - case ARG_STOPPING: + OPTION_LONG("stopping", NULL, + "Inform the service manager about service shutdown"): arg_stopping = true; break; - case ARG_PID: + OPTION_FULL(OPTION_OPTIONAL_ARG, /* sc= */ 0, "pid", "PID", + "Set main PID of daemon"): pidref_done(&arg_pid); - if (isempty(optarg) || streq(optarg, "auto")) + if (isempty(arg) || streq(arg, "auto")) r = pidref_parent_if_applicable(&arg_pid); - else if (streq(optarg, "parent")) + else if (streq(arg, "parent")) r = pidref_set_parent(&arg_pid); - else if (streq(optarg, "self")) + else if (streq(arg, "self")) r = pidref_set_self(&arg_pid); else - r = pidref_set_pidstr(&arg_pid, optarg); + r = pidref_set_pidstr(&arg_pid, arg); if (r < 0) - return log_error_errno(r, "Failed to refer to --pid='%s': %m", optarg); - + return log_error_errno(r, "Failed to refer to --pid='%s': %m", arg); break; - case ARG_STATUS: - arg_status = optarg; + OPTION_LONG("uid", "USER", "Set user to send from"): + r = get_user_creds(&arg, &arg_uid, &arg_gid, NULL, NULL, 0); + if (r == -ESRCH) /* If the user doesn't exist, then accept it anyway as numeric */ + r = parse_uid(arg, &arg_uid); + if (r < 0) + return log_error_errno(r, "Can't resolve user %s: %m", arg); break; - case ARG_BOOTED: - arg_action = ACTION_BOOTED; + OPTION_LONG("status", "TEXT", "Set status text"): + arg_status = arg; break; - case ARG_UID: { - const char *u = optarg; - - r = get_user_creds(&u, &arg_uid, &arg_gid, NULL, NULL, 0); - if (r == -ESRCH) /* If the user doesn't exist, then accept it anyway as numeric */ - r = parse_uid(u, &arg_uid); - if (r < 0) - return log_error_errno(r, "Can't resolve user %s: %m", optarg); - + OPTION_LONG("booted", NULL, "Check if the system was booted up with systemd"): + arg_action = ACTION_BOOTED; break; - } - case ARG_NO_BLOCK: + OPTION_LONG("no-block", NULL, "Do not wait until operation finished"): arg_no_block = true; break; - case ARG_EXEC: + OPTION_LONG("exec", NULL, "Execute command line separated by ';' once done"): do_exec = true; break; - case ARG_FD: { + OPTION_LONG("fd", "FD", "Pass specified file descriptor along with the message"): { _cleanup_close_ int owned_fd = -EBADF; - int fdnr; - fdnr = parse_fd(optarg); + int fdnr = parse_fd(arg); if (fdnr < 0) - return log_error_errno(fdnr, "Failed to parse file descriptor: %s", optarg); + return log_error_errno(fdnr, "Failed to parse file descriptor: %s", arg); if (!passed) { /* Take possession of all passed fds */ @@ -310,33 +258,28 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_FDNAME: - if (!fdname_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File descriptor name invalid: %s", optarg); + OPTION_LONG("fdname", "NAME", "Name to assign to passed file descriptors"): + if (!fdname_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File descriptor name invalid: %s", arg); - if (free_and_strdup(&arg_fdname, optarg) < 0) + if (free_and_strdup(&arg_fdname, arg) < 0) return log_oom(); break; - case ARG_FORK: + OPTION_LONG("fork", NULL, "Receive notifications from child rather than sending them"): arg_action = ACTION_FORK; break; - case 'q': + OPTION('q', "quiet", NULL, "Do not show PID of child when forking"): arg_quiet = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } bool have_env = arg_ready || arg_stopping || arg_reloading || arg_status || pidref_is_set(&arg_pid) || !fdset_isempty(arg_fds); + char **args = option_parser_get_args(&state); + switch (arg_action) { case ACTION_NOTIFY: { @@ -348,22 +291,22 @@ static int parse_argv(int argc, char *argv[]) { if (do_exec) { int i; - for (i = optind; i < argc; i++) - if (streq(argv[i], ";")) + for (i = 0; args[i]; i++) + if (streq(args[i], ";")) break; - if (i >= argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "If --exec is used argument list must contain ';' separator, refusing."); - if (i+1 == argc) + if (!args[i]) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "If --exec is used, argument list must contain ';' separator, refusing."); + if (!args[i + 1]) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty command line specified after ';' separator, refusing."); - arg_exec = strv_copy_n(argv + i + 1, argc - i - 1); + arg_exec = strv_copy(args + i + 1); if (!arg_exec) return log_oom(); - n_arg_env = i - optind; + n_arg_env = i; } else - n_arg_env = argc - optind; + n_arg_env = strv_length(args); have_env = have_env || n_arg_env > 0; if (!have_env) { @@ -376,7 +319,7 @@ static int parse_argv(int argc, char *argv[]) { } if (n_arg_env > 0) { - arg_env = strv_copy_n(argv + optind, n_arg_env); + arg_env = strv_copy_n(args, n_arg_env); if (!arg_env) return log_oom(); } @@ -388,13 +331,13 @@ static int parse_argv(int argc, char *argv[]) { } case ACTION_BOOTED: - if (argc > optind) + if (!strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--booted takes no parameters, refusing."); break; case ACTION_FORK: - if (optind >= argc) + if (strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--fork requires a command to be specified, refusing."); break; @@ -404,7 +347,10 @@ static int parse_argv(int argc, char *argv[]) { } if (have_env && arg_action != ACTION_NOTIFY) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--ready, --reloading, --stopping, --pid=, --status=, --fd= may not be combined with --fork or --booted, refusing."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--ready, --reloading, --stopping, --pid=, --status=, --fd= may not be combined with --fork or --booted, refusing."); + + *ret_args = args; return 1; } @@ -577,16 +523,18 @@ static int run(int argc, char* argv[]) { _cleanup_strv_free_ char **final_env = NULL; const char *our_env[10]; size_t i = 0; + char **args = NULL; /* unnecessary initialization to appease gcc */ int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; + assert(args); if (arg_action == ACTION_FORK) - return action_fork(argv + optind); + return action_fork(args); if (arg_action == ACTION_BOOTED) { r = sd_booted(); @@ -652,6 +600,7 @@ static int run(int argc, char* argv[]) { } our_env[i++] = NULL; + assert(i <= ELEMENTSOF(our_env)); final_env = strv_env_merge((char**) our_env, arg_env); if (!final_env) @@ -695,7 +644,7 @@ static int run(int argc, char* argv[]) { if (r == -E2BIG) return log_error_errno(r, "Too many file descriptors passed."); if (r < 0) - return log_error_errno(r, "Failed to notify init system: %m"); + return log_error_errno(r, "Failed to notify service manager: %m"); if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "No status data could be sent: $NOTIFY_SOCKET was not set"); diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index cdad70706e605..439e176e458b5 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -19,67 +19,68 @@ struct ConfigPerfItem; %struct-type %includes %% -Exec.Boot, config_parse_boot, 0, 0 -Exec.Ephemeral, config_parse_tristate, 0, offsetof(Settings, ephemeral) -Exec.ProcessTwo, config_parse_pid2, 0, 0 -Exec.Parameters, config_parse_strv, 0, offsetof(Settings, parameters) -Exec.Environment, config_parse_strv, 0, offsetof(Settings, environment) -Exec.User, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Settings, user) -Exec.Capability, config_parse_capability, 0, offsetof(Settings, capability) -Exec.AmbientCapability, config_parse_capability, 0, offsetof(Settings, ambient_capability) -Exec.DropCapability, config_parse_capability, 0, offsetof(Settings, drop_capability) -Exec.KillSignal, config_parse_signal, 0, offsetof(Settings, kill_signal) -Exec.Personality, config_parse_personality, 0, offsetof(Settings, personality) -Exec.MachineID, config_parse_id128, 0, offsetof(Settings, machine_id) -Exec.WorkingDirectory, config_parse_path, 0, offsetof(Settings, working_directory) -Exec.PivotRoot, config_parse_pivot_root, 0, 0 -Exec.PrivateUsers, config_parse_private_users, 0, 0 -Exec.PrivateUsersDelegate, config_parse_unsigned, 0, offsetof(Settings, delegate_container_ranges) -Exec.NotifyReady, config_parse_tristate, 0, offsetof(Settings, notify_ready) -Exec.SystemCallFilter, config_parse_syscall_filter, 0, 0 -Exec.LimitCPU, config_parse_rlimit, RLIMIT_CPU, offsetof(Settings, rlimit) -Exec.LimitFSIZE, config_parse_rlimit, RLIMIT_FSIZE, offsetof(Settings, rlimit) -Exec.LimitDATA, config_parse_rlimit, RLIMIT_DATA, offsetof(Settings, rlimit) -Exec.LimitSTACK, config_parse_rlimit, RLIMIT_STACK, offsetof(Settings, rlimit) -Exec.LimitCORE, config_parse_rlimit, RLIMIT_CORE, offsetof(Settings, rlimit) -Exec.LimitRSS, config_parse_rlimit, RLIMIT_RSS, offsetof(Settings, rlimit) -Exec.LimitNOFILE, config_parse_rlimit, RLIMIT_NOFILE, offsetof(Settings, rlimit) -Exec.LimitAS, config_parse_rlimit, RLIMIT_AS, offsetof(Settings, rlimit) -Exec.LimitNPROC, config_parse_rlimit, RLIMIT_NPROC, offsetof(Settings, rlimit) -Exec.LimitMEMLOCK, config_parse_rlimit, RLIMIT_MEMLOCK, offsetof(Settings, rlimit) -Exec.LimitLOCKS, config_parse_rlimit, RLIMIT_LOCKS, offsetof(Settings, rlimit) -Exec.LimitSIGPENDING, config_parse_rlimit, RLIMIT_SIGPENDING, offsetof(Settings, rlimit) -Exec.LimitMSGQUEUE, config_parse_rlimit, RLIMIT_MSGQUEUE, offsetof(Settings, rlimit) -Exec.LimitNICE, config_parse_rlimit, RLIMIT_NICE, offsetof(Settings, rlimit) -Exec.LimitRTPRIO, config_parse_rlimit, RLIMIT_RTPRIO, offsetof(Settings, rlimit) -Exec.LimitRTTIME, config_parse_rlimit, RLIMIT_RTTIME, offsetof(Settings, rlimit) -Exec.Hostname, config_parse_hostname, 0, offsetof(Settings, hostname) -Exec.NoNewPrivileges, config_parse_tristate, 0, offsetof(Settings, no_new_privileges) -Exec.OOMScoreAdjust, config_parse_oom_score_adjust, 0, 0 -Exec.CPUAffinity, config_parse_cpu_set, 0, offsetof(Settings, cpu_set) -Exec.ResolvConf, config_parse_resolv_conf, 0, offsetof(Settings, resolv_conf) -Exec.LinkJournal, config_parse_link_journal, 0, 0 -Exec.Timezone, config_parse_timezone_mode, 0, offsetof(Settings, timezone) -Exec.SuppressSync, config_parse_tristate, 0, offsetof(Settings, suppress_sync) -Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) -Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) -Files.Bind, config_parse_bind, 0, 0 -Files.BindReadOnly, config_parse_bind, 1, 0 -Files.TemporaryFileSystem, config_parse_tmpfs, 0, 0 -Files.Inaccessible, config_parse_inaccessible, 0, 0 -Files.Overlay, config_parse_overlay, 0, 0 -Files.OverlayReadOnly, config_parse_overlay, 1, 0 -Files.PrivateUsersChown, config_parse_userns_chown, 0, offsetof(Settings, userns_ownership) -Files.PrivateUsersOwnership, config_parse_userns_ownership, 0, offsetof(Settings, userns_ownership) -Files.BindUser, config_parse_bind_user, 0, offsetof(Settings, bind_user) -Files.BindUserShell, config_parse_bind_user_shell, 0, 0 -Network.Private, config_parse_tristate, 0, offsetof(Settings, private_network) -Network.NamespacePath, config_parse_path, 0, offsetof(Settings, network_namespace_path) -Network.Interface, config_parse_network_iface_pair, 0, offsetof(Settings, network_interfaces) -Network.MACVLAN, config_parse_macvlan_iface_pair, 0, offsetof(Settings, network_macvlan) -Network.IPVLAN, config_parse_ipvlan_iface_pair, 0, offsetof(Settings, network_ipvlan) -Network.VirtualEthernet, config_parse_tristate, 0, offsetof(Settings, network_veth) -Network.VirtualEthernetExtra, config_parse_veth_extra, 0, 0 -Network.Bridge, config_parse_ifname, 0, offsetof(Settings, network_bridge) -Network.Zone, config_parse_network_zone, 0, 0 -Network.Port, config_parse_expose_port, 0, 0 +Exec.Boot, config_parse_boot, 0, 0 +Exec.Ephemeral, config_parse_tristate, 0, offsetof(Settings, ephemeral) +Exec.ProcessTwo, config_parse_pid2, 0, 0 +Exec.Parameters, config_parse_strv, 0, offsetof(Settings, parameters) +Exec.Environment, config_parse_strv, 0, offsetof(Settings, environment) +Exec.User, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Settings, user) +Exec.Capability, config_parse_capability, 0, offsetof(Settings, capability) +Exec.AmbientCapability, config_parse_capability, 0, offsetof(Settings, ambient_capability) +Exec.DropCapability, config_parse_capability, 0, offsetof(Settings, drop_capability) +Exec.KillSignal, config_parse_signal, 0, offsetof(Settings, kill_signal) +Exec.Personality, config_parse_personality, 0, offsetof(Settings, personality) +Exec.MachineID, config_parse_id128, 0, offsetof(Settings, machine_id) +Exec.WorkingDirectory, config_parse_path, 0, offsetof(Settings, working_directory) +Exec.PivotRoot, config_parse_pivot_root, 0, 0 +Exec.PrivateUsers, config_parse_private_users, 0, 0 +Exec.PrivateUsersDelegate, config_parse_unsigned, 0, offsetof(Settings, delegate_container_ranges) +Exec.NotifyReady, config_parse_tristate, 0, offsetof(Settings, notify_ready) +Exec.SystemCallFilter, config_parse_syscall_filter, 0, 0 +Exec.LimitCPU, config_parse_rlimit, RLIMIT_CPU, offsetof(Settings, rlimit) +Exec.LimitFSIZE, config_parse_rlimit, RLIMIT_FSIZE, offsetof(Settings, rlimit) +Exec.LimitDATA, config_parse_rlimit, RLIMIT_DATA, offsetof(Settings, rlimit) +Exec.LimitSTACK, config_parse_rlimit, RLIMIT_STACK, offsetof(Settings, rlimit) +Exec.LimitCORE, config_parse_rlimit, RLIMIT_CORE, offsetof(Settings, rlimit) +Exec.LimitRSS, config_parse_rlimit, RLIMIT_RSS, offsetof(Settings, rlimit) +Exec.LimitNOFILE, config_parse_rlimit, RLIMIT_NOFILE, offsetof(Settings, rlimit) +Exec.LimitAS, config_parse_rlimit, RLIMIT_AS, offsetof(Settings, rlimit) +Exec.LimitNPROC, config_parse_rlimit, RLIMIT_NPROC, offsetof(Settings, rlimit) +Exec.LimitMEMLOCK, config_parse_rlimit, RLIMIT_MEMLOCK, offsetof(Settings, rlimit) +Exec.LimitLOCKS, config_parse_rlimit, RLIMIT_LOCKS, offsetof(Settings, rlimit) +Exec.LimitSIGPENDING, config_parse_rlimit, RLIMIT_SIGPENDING, offsetof(Settings, rlimit) +Exec.LimitMSGQUEUE, config_parse_rlimit, RLIMIT_MSGQUEUE, offsetof(Settings, rlimit) +Exec.LimitNICE, config_parse_rlimit, RLIMIT_NICE, offsetof(Settings, rlimit) +Exec.LimitRTPRIO, config_parse_rlimit, RLIMIT_RTPRIO, offsetof(Settings, rlimit) +Exec.LimitRTTIME, config_parse_rlimit, RLIMIT_RTTIME, offsetof(Settings, rlimit) +Exec.Hostname, config_parse_hostname, 0, offsetof(Settings, hostname) +Exec.NoNewPrivileges, config_parse_tristate, 0, offsetof(Settings, no_new_privileges) +Exec.OOMScoreAdjust, config_parse_oom_score_adjust, 0, 0 +Exec.CPUAffinity, config_parse_cpu_set, 0, offsetof(Settings, cpu_set) +Exec.ResolvConf, config_parse_resolv_conf, 0, offsetof(Settings, resolv_conf) +Exec.LinkJournal, config_parse_link_journal, 0, 0 +Exec.Timezone, config_parse_timezone_mode, 0, offsetof(Settings, timezone) +Exec.SuppressSync, config_parse_tristate, 0, offsetof(Settings, suppress_sync) +Exec.RestrictAddressFamilies, config_parse_restrict_address_families, 0, 0 +Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) +Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) +Files.Bind, config_parse_bind, 0, 0 +Files.BindReadOnly, config_parse_bind, 1, 0 +Files.TemporaryFileSystem, config_parse_tmpfs, 0, 0 +Files.Inaccessible, config_parse_inaccessible, 0, 0 +Files.Overlay, config_parse_overlay, 0, 0 +Files.OverlayReadOnly, config_parse_overlay, 1, 0 +Files.PrivateUsersChown, config_parse_userns_chown, 0, offsetof(Settings, userns_ownership) +Files.PrivateUsersOwnership, config_parse_userns_ownership, 0, offsetof(Settings, userns_ownership) +Files.BindUser, config_parse_bind_user, 0, offsetof(Settings, bind_user) +Files.BindUserShell, config_parse_bind_user_shell, 0, 0 +Network.Private, config_parse_tristate, 0, offsetof(Settings, private_network) +Network.NamespacePath, config_parse_path, 0, offsetof(Settings, network_namespace_path) +Network.Interface, config_parse_network_iface_pair, 0, offsetof(Settings, network_interfaces) +Network.MACVLAN, config_parse_macvlan_iface_pair, 0, offsetof(Settings, network_macvlan) +Network.IPVLAN, config_parse_ipvlan_iface_pair, 0, offsetof(Settings, network_ipvlan) +Network.VirtualEthernet, config_parse_tristate, 0, offsetof(Settings, network_veth) +Network.VirtualEthernetExtra, config_parse_veth_extra, 0, 0 +Network.Bridge, config_parse_ifname, 0, offsetof(Settings, network_bridge) +Network.Zone, config_parse_network_zone, 0, 0 +Network.Port, config_parse_expose_port, 0, 0 diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index cfb4aac6ff35b..a75582e2b4d6e 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -534,7 +534,7 @@ int mount_all(const char *dest, const char *selinux_apifs_context) { #define PROC_INACCESSIBLE_REG(path) \ - { "/run/systemd/inaccessible/reg", (path), NULL, NULL, MS_BIND, \ + { "/run/host/inaccessible/reg", (path), NULL, NULL, MS_BIND, \ MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO }, /* Bind mount first ... */ \ { NULL, (path), NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, \ MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO } /* Then, make it r/o */ @@ -757,12 +757,13 @@ int mount_all(const char *dest, } static int parse_mount_bind_options(const char *options, unsigned long *open_tree_flags, char **mount_opts, RemountIdmapping *idmapping) { - unsigned long flags = *open_tree_flags; + unsigned long flags = *ASSERT_PTR(open_tree_flags); char *opts = NULL; - RemountIdmapping new_idmapping = *idmapping; + RemountIdmapping new_idmapping = *ASSERT_PTR(idmapping); int r; assert(options); + assert(mount_opts); for (;;) { _cleanup_free_ char *word = NULL; @@ -1370,7 +1371,9 @@ int pivot_root_parse(char **pivot_root_new, char **pivot_root_old, const char *s if (!path_is_absolute(root_new)) return -EINVAL; - if (root_old && !path_is_absolute(root_old)) + if (!path_is_normalized(root_new)) + return -EINVAL; + if (root_old && (!path_is_absolute(root_old) || !path_is_normalized(root_old))) return -EINVAL; free_and_replace(*pivot_root_new, root_new); diff --git a/src/nspawn/nspawn-network.c b/src/nspawn/nspawn-network.c index c7e5c417cab4b..6949ecac724a9 100644 --- a/src/nspawn/nspawn-network.c +++ b/src/nspawn/nspawn-network.c @@ -177,6 +177,7 @@ int setup_veth(const char *machine_name, assert(machine_name); assert(pidref_is_set(pid)); assert(iface_name); + assert(provided_mac); /* Use two different interface name prefixes depending whether * we are in bridge mode or not. */ diff --git a/src/nspawn/nspawn-oci.c b/src/nspawn/nspawn-oci.c index 29091bd82c8f5..bd28a67e6b14c 100644 --- a/src/nspawn/nspawn-oci.c +++ b/src/nspawn/nspawn-oci.c @@ -22,6 +22,7 @@ #include "string-util.h" #include "strv.h" #include "time-util.h" +#include "user-util.h" /* TODO: * OCI runtime tool implementation @@ -685,6 +686,10 @@ static int oci_uid_gid_mappings(const char *name, sd_json_variant *v, sd_json_di if (r < 0) return r; + /* Silence static analyzers, sd_json_dispatch_uid_gid() already validates */ + assert(uid_is_valid(data.host_id)); + assert(uid_is_valid(data.container_id)); + if (data.range > UINT32_MAX - data.host_id || data.range > UINT32_MAX - data.container_id) return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL), @@ -1489,6 +1494,8 @@ static int oci_resources(const char *name, sd_json_variant *v, sd_json_dispatch_ static bool sysctl_key_valid(const char *s) { bool dot = true; + POINTER_MAY_BE_NULL(s); + /* Note that we are a bit stricter here than in systemd-sysctl, as that inherited semantics from the old sysctl * tool, which were really weird (as it swaps / and . in both ways) */ @@ -1546,7 +1553,6 @@ static int oci_sysctl(const char *name, sd_json_variant *v, sd_json_dispatch_fla #if HAVE_SECCOMP static int oci_seccomp_action_from_string(const char *name, uint32_t *ret) { - static const struct { const char *name; uint32_t action; @@ -1563,6 +1569,8 @@ static int oci_seccomp_action_from_string(const char *name, uint32_t *ret) { * here */ }; + assert(ret); + FOREACH_ELEMENT(i, table) if (streq_ptr(name, i->name)) { *ret = i->action; @@ -1573,7 +1581,6 @@ static int oci_seccomp_action_from_string(const char *name, uint32_t *ret) { } static int oci_seccomp_arch_from_string(const char *name, uint32_t *ret) { - static const struct { const char *name; uint32_t arch; @@ -1605,6 +1612,8 @@ static int oci_seccomp_arch_from_string(const char *name, uint32_t *ret) { { "SCMP_ARCH_X86_64", SCMP_ARCH_X86_64 }, }; + assert(ret); + FOREACH_ELEMENT(i, table) if (streq_ptr(i->name, name)) { *ret = i->arch; @@ -1615,7 +1624,6 @@ static int oci_seccomp_arch_from_string(const char *name, uint32_t *ret) { } static int oci_seccomp_compare_from_string(const char *name, enum scmp_compare *ret) { - static const struct { const char *name; enum scmp_compare op; @@ -1629,6 +1637,8 @@ static int oci_seccomp_compare_from_string(const char *name, enum scmp_compare * { "SCMP_CMP_MASKED_EQ", SCMP_CMP_MASKED_EQ }, }; + assert(ret); + FOREACH_ELEMENT(i, table) if (streq_ptr(i->name, name)) { *ret = i->op; @@ -1816,7 +1826,7 @@ static int oci_seccomp(const char *name, sd_json_variant *v, sd_json_dispatch_fl if (r < 0) return json_log(def, flags, r, "Unknown default action: %s", sd_json_variant_string(def)); - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return json_log(def, flags, r, "No support for libseccomp: %m"); @@ -2072,6 +2082,7 @@ int oci_load(FILE *f, const char *bundle, Settings **ret) { int r; assert_se(bundle); + assert(ret); path = strjoina(bundle, "/config.json"); diff --git a/src/nspawn/nspawn-register.c b/src/nspawn/nspawn-register.c index 04031adcc5ab5..ace0f6637a545 100644 --- a/src/nspawn/nspawn-register.c +++ b/src/nspawn/nspawn-register.c @@ -131,150 +131,6 @@ static int can_set_coredump_receive(sd_bus *bus) { return r >= 0; } -static int register_machine_ex( - sd_bus *bus, - const char *machine_name, - const PidRef *pid, - const char *directory, - sd_id128_t uuid, - int local_ifindex, - const char *service, - sd_bus_error *error) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert(bus); - assert(machine_name); - assert(service); - assert(error); - - r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "RegisterMachineEx"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", machine_name); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "(sv)(sv)(sv)", - "Id", "ay", SD_BUS_MESSAGE_APPEND_ID128(uuid), - "Service", "s", service, - "Class", "s", "container"); - if (r < 0) - return bus_log_create_error(r); - - if (pidref_is_set(pid)) { - if (pid->fd >= 0) { - r = sd_bus_message_append(m, "(sv)", "LeaderPIDFD", "h", pid->fd); - if (r < 0) - return bus_log_create_error(r); - } - - if (pid->fd_id > 0) { - r = sd_bus_message_append(m, "(sv)", "LeaderPIDFDID", "t", pid->fd_id); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "(sv)", "LeaderPID", "u", pid->pid); - if (r < 0) - return bus_log_create_error(r); - } - } - - if (!isempty(directory)) { - r = sd_bus_message_append(m, "(sv)", "RootDirectory", "s", directory); - if (r < 0) - return bus_log_create_error(r); - } - - if (local_ifindex > 0) { - r = sd_bus_message_append(m, "(sv)", "NetworkInterfaces", "ai", 1, local_ifindex); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - return sd_bus_call(bus, m, 0, error, NULL); -} - -int register_machine( - sd_bus *bus, - const char *machine_name, - const PidRef *pid, - const char *directory, - sd_id128_t uuid, - int local_ifindex, - const char *service) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(machine_name); - assert(service); - - r = register_machine_ex( - bus, - machine_name, - pid, - directory, - uuid, - local_ifindex, - service, - &error); - if (r >= 0) - return 0; - if (!sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) - return log_error_errno(r, "Failed to register machine: %s", bus_error_message(&error, r)); - - sd_bus_error_free(&error); - - r = bus_call_method( - bus, - bus_machine_mgr, - "RegisterMachineWithNetwork", - &error, - NULL, - "sayssusai", - machine_name, - SD_BUS_MESSAGE_APPEND_ID128(uuid), - service, - "container", - pidref_is_set(pid) ? (uint32_t) pid->pid : 0, - strempty(directory), - local_ifindex > 0 ? 1 : 0, local_ifindex); - if (r < 0) - return log_error_errno(r, "Failed to register machine: %s", bus_error_message(&error, r)); - - return 0; -} - -int unregister_machine( - sd_bus *bus, - const char *machine_name) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - - r = bus_call_method(bus, bus_machine_mgr, "UnregisterMachine", &error, NULL, "s", machine_name); - if (r < 0) - log_debug("Failed to unregister machine: %s", bus_error_message(&error, r)); - - return 0; -} - int allocate_scope( sd_bus *bus, const char *machine_name, diff --git a/src/nspawn/nspawn-register.h b/src/nspawn/nspawn-register.h index c4b8048606251..d82c780181c6d 100644 --- a/src/nspawn/nspawn-register.h +++ b/src/nspawn/nspawn-register.h @@ -4,16 +4,6 @@ #include "shared-forward.h" #include "nspawn-settings.h" -int register_machine( - sd_bus *bus, - const char *machine_name, - const PidRef *pid, - const char *directory, - sd_id128_t uuid, - int local_ifindex, - const char *service); -int unregister_machine(sd_bus *bus, const char *machine_name); - typedef enum AllocateScopeFlags { ALLOCATE_SCOPE_ALLOW_PIDFD = 1 << 0, } AllocateScopeFlags; diff --git a/src/nspawn/nspawn-seccomp.c b/src/nspawn/nspawn-seccomp.c index d85a30ee9f9cf..beffd5da8a862 100644 --- a/src/nspawn/nspawn-seccomp.c +++ b/src/nspawn/nspawn-seccomp.c @@ -7,6 +7,7 @@ #include "log.h" #include "nspawn-seccomp.h" #include "seccomp-util.h" +#include "set.h" #include "strv.h" #if HAVE_SECCOMP @@ -172,7 +173,13 @@ static int add_syscall_filters( return 0; } -int setup_seccomp(uint64_t cap_list_retain, char **syscall_allow_list, char **syscall_deny_list) { +int setup_seccomp( + uint64_t cap_list_retain, + char **syscall_allow_list, + char **syscall_deny_list, + Set *restrict_address_families, + bool restrict_address_families_is_allowlist) { + uint32_t arch; int r; @@ -241,12 +248,18 @@ int setup_seccomp(uint64_t cap_list_retain, char **syscall_allow_list, char **sy seccomp_arch_to_string(arch)); } + if (restrict_address_families_is_allowlist || !set_isempty(restrict_address_families)) { + r = seccomp_restrict_address_families(restrict_address_families, restrict_address_families_is_allowlist); + if (r < 0) + return log_error_errno(r, "Failed to install address family filter: %m"); + } + return 0; } #else -int setup_seccomp(uint64_t cap_list_retain, char **syscall_allow_list, char **syscall_deny_list) { +int setup_seccomp(uint64_t cap_list_retain, char **syscall_allow_list, char **syscall_deny_list, Set *restrict_address_families, bool restrict_address_families_is_allowlist) { return 0; } diff --git a/src/nspawn/nspawn-seccomp.h b/src/nspawn/nspawn-seccomp.h index 31520a09300d3..52232ad56aebb 100644 --- a/src/nspawn/nspawn-seccomp.h +++ b/src/nspawn/nspawn-seccomp.h @@ -3,4 +3,9 @@ #include "shared-forward.h" -int setup_seccomp(uint64_t cap_list_retain, char **syscall_allow_list, char **syscall_deny_list); +int setup_seccomp( + uint64_t cap_list_retain, + char **syscall_allow_list, + char **syscall_deny_list, + Set *restrict_address_families, + bool restrict_address_families_is_allowlist); diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index c058ab28f71de..30c603394c1fe 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -12,9 +12,11 @@ #include "nspawn-mount.h" #include "nspawn-network.h" #include "nspawn-settings.h" +#include "parse-helpers.h" #include "parse-util.h" #include "process-util.h" #include "rlimit-util.h" +#include "set.h" #include "socket-util.h" #include "string-table.h" #include "string-util.h" @@ -137,6 +139,7 @@ Settings* settings_free(Settings *s) { rlimit_free_all(s->rlimit); free(s->hostname); cpu_set_done(&s->cpu_set); + set_free(s->restrict_address_families); strv_free(s->bind_user); free(s->bind_user_shell); @@ -717,12 +720,12 @@ int config_parse_private_users( r = parse_uid(shift, &sh); if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, "UID/GID shift invalid, ignoring: %s", range); + log_syntax(unit, LOG_WARNING, filename, line, r, "UID/GID shift invalid, ignoring: %s", rvalue); return 0; } if (!userns_shift_range_valid(sh, rn)) { - log_syntax(unit, LOG_WARNING, filename, line, 0, "UID/GID shift and range combination invalid, ignoring: %s", range); + log_syntax(unit, LOG_WARNING, filename, line, 0, "UID/GID shift and range combination invalid, ignoring: %s", rvalue); return 0; } @@ -1054,3 +1057,32 @@ int config_parse_bind_user_shell( return 0; } + +int config_parse_restrict_address_families( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Settings *settings = ASSERT_PTR(data); + int r; + + assert(rvalue); + + r = parse_address_families(rvalue, &settings->restrict_address_families, &settings->restrict_address_families_is_allowlist); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse address family, ignoring: %s", rvalue); + return 0; + } + + return 0; +} diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index 84c342b83c1eb..c2e079f0563c1 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -92,43 +92,44 @@ typedef enum ConsoleMode { } ConsoleMode; typedef enum SettingsMask { - SETTING_START_MODE = UINT64_C(1) << 0, - SETTING_ENVIRONMENT = UINT64_C(1) << 1, - SETTING_USER = UINT64_C(1) << 2, - SETTING_CAPABILITY = UINT64_C(1) << 3, - SETTING_KILL_SIGNAL = UINT64_C(1) << 4, - SETTING_PERSONALITY = UINT64_C(1) << 5, - SETTING_MACHINE_ID = UINT64_C(1) << 6, - SETTING_NETWORK = UINT64_C(1) << 7, - SETTING_EXPOSE_PORTS = UINT64_C(1) << 8, - SETTING_READ_ONLY = UINT64_C(1) << 9, - SETTING_VOLATILE_MODE = UINT64_C(1) << 10, - SETTING_CUSTOM_MOUNTS = UINT64_C(1) << 11, - SETTING_WORKING_DIRECTORY = UINT64_C(1) << 12, - SETTING_USERNS = UINT64_C(1) << 13, - SETTING_NOTIFY_READY = UINT64_C(1) << 14, - SETTING_PIVOT_ROOT = UINT64_C(1) << 15, - SETTING_SYSCALL_FILTER = UINT64_C(1) << 16, - SETTING_HOSTNAME = UINT64_C(1) << 17, - SETTING_NO_NEW_PRIVILEGES = UINT64_C(1) << 18, - SETTING_OOM_SCORE_ADJUST = UINT64_C(1) << 19, - SETTING_CPU_AFFINITY = UINT64_C(1) << 20, - SETTING_RESOLV_CONF = UINT64_C(1) << 21, - SETTING_LINK_JOURNAL = UINT64_C(1) << 22, - SETTING_TIMEZONE = UINT64_C(1) << 23, - SETTING_EPHEMERAL = UINT64_C(1) << 24, - SETTING_SLICE = UINT64_C(1) << 25, - SETTING_DIRECTORY = UINT64_C(1) << 26, - SETTING_USE_CGNS = UINT64_C(1) << 27, - SETTING_CLONE_NS_FLAGS = UINT64_C(1) << 28, - SETTING_CONSOLE_MODE = UINT64_C(1) << 29, - SETTING_CREDENTIALS = UINT64_C(1) << 30, - SETTING_BIND_USER = UINT64_C(1) << 31, - SETTING_BIND_USER_SHELL = UINT64_C(1) << 32, - SETTING_SUPPRESS_SYNC = UINT64_C(1) << 33, - SETTING_RLIMIT_FIRST = UINT64_C(1) << 34, /* we define one bit per resource limit here */ - SETTING_RLIMIT_LAST = UINT64_C(1) << (34 + _RLIMIT_MAX - 1), - _SETTINGS_MASK_ALL = (UINT64_C(1) << (34 + _RLIMIT_MAX)) -1, + SETTING_START_MODE = UINT64_C(1) << 0, + SETTING_ENVIRONMENT = UINT64_C(1) << 1, + SETTING_USER = UINT64_C(1) << 2, + SETTING_CAPABILITY = UINT64_C(1) << 3, + SETTING_KILL_SIGNAL = UINT64_C(1) << 4, + SETTING_PERSONALITY = UINT64_C(1) << 5, + SETTING_MACHINE_ID = UINT64_C(1) << 6, + SETTING_NETWORK = UINT64_C(1) << 7, + SETTING_EXPOSE_PORTS = UINT64_C(1) << 8, + SETTING_READ_ONLY = UINT64_C(1) << 9, + SETTING_VOLATILE_MODE = UINT64_C(1) << 10, + SETTING_CUSTOM_MOUNTS = UINT64_C(1) << 11, + SETTING_WORKING_DIRECTORY = UINT64_C(1) << 12, + SETTING_USERNS = UINT64_C(1) << 13, + SETTING_NOTIFY_READY = UINT64_C(1) << 14, + SETTING_PIVOT_ROOT = UINT64_C(1) << 15, + SETTING_SYSCALL_FILTER = UINT64_C(1) << 16, + SETTING_HOSTNAME = UINT64_C(1) << 17, + SETTING_NO_NEW_PRIVILEGES = UINT64_C(1) << 18, + SETTING_OOM_SCORE_ADJUST = UINT64_C(1) << 19, + SETTING_CPU_AFFINITY = UINT64_C(1) << 20, + SETTING_RESOLV_CONF = UINT64_C(1) << 21, + SETTING_LINK_JOURNAL = UINT64_C(1) << 22, + SETTING_TIMEZONE = UINT64_C(1) << 23, + SETTING_EPHEMERAL = UINT64_C(1) << 24, + SETTING_SLICE = UINT64_C(1) << 25, + SETTING_DIRECTORY = UINT64_C(1) << 26, + SETTING_USE_CGNS = UINT64_C(1) << 27, + SETTING_CLONE_NS_FLAGS = UINT64_C(1) << 28, + SETTING_CONSOLE_MODE = UINT64_C(1) << 29, + SETTING_CREDENTIALS = UINT64_C(1) << 30, + SETTING_BIND_USER = UINT64_C(1) << 31, + SETTING_BIND_USER_SHELL = UINT64_C(1) << 32, + SETTING_SUPPRESS_SYNC = UINT64_C(1) << 33, + SETTING_RESTRICT_ADDRESS_FAMILIES = UINT64_C(1) << 34, + SETTING_RLIMIT_FIRST = UINT64_C(1) << 35, /* we define one bit per resource limit here */ + SETTING_RLIMIT_LAST = UINT64_C(1) << (35 + _RLIMIT_MAX - 1), + _SETTINGS_MASK_ALL = (UINT64_C(1) << (35 + _RLIMIT_MAX)) -1, _SETTING_FORCE_ENUM_WIDTH = UINT64_MAX } SettingsMask; @@ -190,6 +191,8 @@ typedef struct Settings { bool link_journal_try; TimezoneMode timezone; int suppress_sync; + Set *restrict_address_families; + bool restrict_address_families_is_allowlist; /* [Files] */ int read_only; @@ -277,6 +280,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_userns_chown); CONFIG_PARSER_PROTOTYPE(config_parse_userns_ownership); CONFIG_PARSER_PROTOTYPE(config_parse_bind_user); CONFIG_PARSER_PROTOTYPE(config_parse_bind_user_shell); +CONFIG_PARSER_PROTOTYPE(config_parse_restrict_address_families); DECLARE_STRING_TABLE_LOOKUP(resolv_conf_mode, ResolvConfMode); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 722be8bbf7cb6..b1c8defb6c46a 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -50,6 +49,8 @@ #include "fd-util.h" #include "fdset.h" #include "fileio.h" +#include "fork-notify.h" +#include "format-table.h" #include "format-util.h" #include "fs-util.h" #include "gpt.h" @@ -66,6 +67,7 @@ #include "loopback-setup.h" #include "machine-bind-user.h" #include "machine-credential.h" +#include "machine-register.h" #include "main-func.h" #include "mkdir.h" #include "mount-util.h" @@ -88,10 +90,13 @@ #include "nspawn.h" #include "nsresource.h" #include "os-util.h" +#include "parse-helpers.h" #include "osc-context.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" +#include "path-lookup.h" #include "path-util.h" #include "pidref.h" #include "polkit-agent.h" @@ -106,6 +111,7 @@ #include "runtime-scope.h" #include "seccomp-util.h" #include "selinux-util.h" +#include "set.h" #include "shift-uid.h" #include "signal-util.h" #include "siphash24.h" @@ -129,6 +135,7 @@ /* The notify socket inside the container it can use to talk to nspawn using the sd_notify(3) protocol */ #define NSPAWN_NOTIFY_SOCKET_PATH "/run/host/notify" #define NSPAWN_MOUNT_TUNNEL "/run/host/incoming" +#define NSPAWN_JOURNAL_SOCKET_PATH "/run/host/journal/socket" #define EXIT_FORCE_RESTART 133 @@ -153,7 +160,7 @@ static char *arg_hostname = NULL; /* The name the payload sees by default */ static const char *arg_selinux_context = NULL; static const char *arg_selinux_apifs_context = NULL; static char *arg_slice = NULL; -static bool arg_private_network; /* initialized depending on arg_privileged in run() */ +static bool arg_private_network = false; static bool arg_read_only = false; static StartMode arg_start_mode = START_PID1; static bool arg_ephemeral = false; @@ -192,7 +199,7 @@ static CustomMount *arg_custom_mounts = NULL; static size_t arg_n_custom_mounts = 0; static char **arg_setenv = NULL; static bool arg_quiet = false; -static bool arg_register = true; +static int arg_register = -1; static bool arg_keep_unit = false; static char **arg_network_interfaces = NULL; static char **arg_network_macvlan = NULL; @@ -202,7 +209,7 @@ static char **arg_network_veth_extra = NULL; static char *arg_network_bridge = NULL; static char *arg_network_zone = NULL; static char *arg_network_namespace_path = NULL; -struct ether_addr arg_network_provided_mac = {}; +static struct ether_addr arg_network_provided_mac = {}; static PagerFlags arg_pager_flags = 0; static unsigned long arg_personality = PERSONALITY_INVALID; static char *arg_image = NULL; @@ -212,7 +219,7 @@ static VolatileMode arg_volatile_mode = VOLATILE_NO; static ExposePort *arg_expose_ports = NULL; static char **arg_property = NULL; static sd_bus_message *arg_property_message = NULL; -static UserNamespaceMode arg_userns_mode; /* initialized depending on arg_privileged in run() */ +static UserNamespaceMode arg_userns_mode = _USER_NAMESPACE_MODE_INVALID; static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; static unsigned arg_delegate_container_ranges = 0; static UserNamespaceOwnership arg_userns_ownership = _USER_NAMESPACE_OWNERSHIP_INVALID; @@ -249,13 +256,20 @@ static char *arg_bind_user_shell = NULL; static bool arg_bind_user_shell_copy = false; static char **arg_bind_user_groups = NULL; static bool arg_suppress_sync = false; +static Set *arg_restrict_address_families = NULL; +static bool arg_restrict_address_families_is_allowlist = false; static char *arg_settings_filename = NULL; static Architecture arg_architecture = _ARCHITECTURE_INVALID; static ImagePolicy *arg_image_policy = NULL; static char *arg_background = NULL; -static bool arg_privileged = false; +static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID; static bool arg_cleanup = false; static bool arg_ask_password = true; +static char *arg_forward_journal = NULL; +static uint64_t arg_forward_journal_max_use = UINT64_MAX; +static uint64_t arg_forward_journal_keep_free = UINT64_MAX; +static uint64_t arg_forward_journal_max_file_size = UINT64_MAX; +static uint64_t arg_forward_journal_max_files = UINT64_MAX; STATIC_DESTRUCTOR_REGISTER(arg_directory, freep); STATIC_DESTRUCTOR_REGISTER(arg_template, freep); @@ -293,9 +307,11 @@ STATIC_DESTRUCTOR_REGISTER(arg_sysctl, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_shell, freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_groups, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_restrict_address_families, set_freep); STATIC_DESTRUCTOR_REGISTER(arg_settings_filename, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_background, freep); +STATIC_DESTRUCTOR_REGISTER(arg_forward_journal, freep); static int parse_private_users( const char *s, @@ -345,7 +361,7 @@ static int parse_private_users( *ret_uid_shift = 0; *ret_uid_range = UINT32_C(0x10000); - } else if (streq(optarg, "managed")) { + } else if (streq(s, "managed")) { /* managed: User namespace on, and acquire it from systemd-nsresourced */ *ret_userns_mode = USER_NAMESPACE_MANAGED; *ret_uid_shift = UID_INVALID; @@ -353,7 +369,7 @@ static int parse_private_users( } else { /* anything else: User namespacing on, UID range is explicitly configured */ - r = parse_userns_uid_range(optarg, ret_uid_shift, ret_uid_range); + r = parse_userns_uid_range(s, ret_uid_shift, ret_uid_range); if (r < 0) return r; *ret_userns_mode = USER_NAMESPACE_FIXED; @@ -372,163 +388,55 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" - "%5$sSpawn a command or OS in a lightweight container.%6$s\n\n" - " -h --help Show this help\n" - " --version Print version string\n" - " -q --quiet Do not show status information\n" - " --no-pager Do not pipe output into a pager\n" - " --settings=BOOLEAN Load additional settings from .nspawn file\n" - " --cleanup Clean up left-over mounts and underlying mount\n" - " points used by the container\n" - " --no-ask-password Do not prompt for password\n" - "\n%3$sImage:%4$s\n" - " -D --directory=PATH Root directory for the container\n" - " --template=PATH Initialize root directory from template directory,\n" - " if missing\n" - " -x --ephemeral Run container with snapshot of root directory, and\n" - " remove it after exit\n" - " -i --image=PATH Root file system disk image (or device node) for\n" - " the container\n" - " --image-policy=POLICY Specify disk image dissection policy\n" - " --oci-bundle=PATH OCI bundle directory\n" - " --read-only Mount the root directory read-only\n" - " --volatile[=MODE] Run the system in volatile mode\n" - " --root-hash=HASH Specify verity root hash for root disk image\n" - " --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n" - " as a DER encoded PKCS7, either as a path to a file\n" - " or as an ASCII base64 encoded string prefixed by\n" - " 'base64:'\n" - " --verity-data=PATH Specify hash device for verity\n" - " --pivot-root=PATH[:PATH]\n" - " Pivot root to given directory in the container\n" - "\n%3$sExecution:%4$s\n" - " -a --as-pid2 Maintain a stub init as PID1, invoke binary as PID2\n" - " -b --boot Boot up full system (i.e. invoke init)\n" - " --chdir=PATH Set working directory in the container\n" - " -E --setenv=NAME[=VALUE] Pass an environment variable to PID 1\n" - " -u --user=USER Run the command under specified user or UID\n" - " --kill-signal=SIGNAL Select signal to use for shutting down PID 1\n" - " --notify-ready=BOOLEAN Receive notifications from the child init process\n" - " --suppress-sync=BOOLEAN\n" - " Suppress any form of disk data synchronization\n" - "\n%3$sSystem Identity:%4$s\n" - " -M --machine=NAME Set the machine name for the container\n" - " --hostname=NAME Override the hostname for the container\n" - " --uuid=UUID Set a specific machine UUID for the container\n" - "\n%3$sProperties:%4$s\n" - " -S --slice=SLICE Place the container in the specified slice\n" - " --property=NAME=VALUE Set scope unit property\n" - " --register=BOOLEAN Register container as machine\n" - " --keep-unit Do not register a scope for the machine, reuse\n" - " the service unit nspawn is running in\n" - "\n%3$sUser Namespacing:%4$s\n" - " --private-users=no Run without user namespacing\n" - " --private-users=yes|pick|identity|managed\n" - " Run within user namespace, autoselect UID/GID range\n" - " --private-users=UIDBASE[:NUIDS]\n" - " Similar, but with user configured UID/GID range\n" - " --private-users-ownership=MODE\n" - " Adjust ('chown') or map ('map') OS tree ownership\n" - " to private UID/GID range\n" - " --private-users-delegate=N\n" - " Delegate N additional 64K UID/GID ranges for use\n" - " by nested containers (requires managed user\n" - " namespaces)\n" - " -U Equivalent to --private-users=pick and\n" - " --private-users-ownership=auto\n" - "\n%3$sNetworking:%4$s\n" - " --private-network Disable network in container\n" - " --network-interface=HOSTIF[:CONTAINERIF]\n" - " Assign an existing network interface to the\n" - " container\n" - " --network-macvlan=HOSTIF[:CONTAINERIF]\n" - " Create a macvlan network interface based on an\n" - " existing network interface to the container\n" - " --network-ipvlan=HOSTIF[:CONTAINERIF]\n" - " Create an ipvlan network interface based on an\n" - " existing network interface to the container\n" - " -n --network-veth Add a virtual Ethernet connection between host\n" - " and container\n" - " --network-veth-extra=HOSTIF[:CONTAINERIF]\n" - " Add an additional virtual Ethernet link between\n" - " host and container\n" - " --network-bridge=INTERFACE\n" - " Add a virtual Ethernet connection to the container\n" - " and attach it to an existing bridge on the host\n" - " --network-zone=NAME Similar, but attach the new interface to an\n" - " automatically managed bridge interface\n" - " --network-namespace-path=PATH\n" - " Set network namespace to the one represented by\n" - " the specified kernel namespace file node\n" - " -p --port=[PROTOCOL:]HOSTPORT[:CONTAINERPORT]\n" - " Expose a container IP port on the host\n" - "\n%3$sSecurity:%4$s\n" - " --capability=CAP In addition to the default, retain specified\n" - " capability\n" - " --drop-capability=CAP Drop the specified capability from the default set\n" - " --ambient-capability=CAP\n" - " Sets the specified capability for the started\n" - " process. Not useful if booting a machine.\n" - " --no-new-privileges Set PR_SET_NO_NEW_PRIVS flag for container payload\n" - " --system-call-filter=LIST|~LIST\n" - " Permit/prohibit specific system calls\n" - " -Z --selinux-context=SECLABEL\n" - " Set the SELinux security context to be used by\n" - " processes in the container\n" - " -L --selinux-apifs-context=SECLABEL\n" - " Set the SELinux security context to be used by\n" - " API/tmpfs file systems in the container\n" - "\n%3$sResources:%4$s\n" - " --rlimit=NAME=LIMIT Set a resource limit for the payload\n" - " --oom-score-adjust=VALUE\n" - " Adjust the OOM score value for the payload\n" - " --cpu-affinity=CPUS Adjust the CPU affinity of the container\n" - " --personality=ARCH Pick personality for this container\n" - "\n%3$sIntegration:%4$s\n" - " --resolv-conf=MODE Select mode of /etc/resolv.conf initialization\n" - " --timezone=MODE Select mode of /etc/localtime initialization\n" - " --link-journal=MODE Link up guest journal, one of no, auto, guest, \n" - " host, try-guest, try-host\n" - " -j Equivalent to --link-journal=try-guest\n" - "\n%3$sMounts:%4$s\n" - " --bind=PATH[:PATH[:OPTIONS]]\n" - " Bind mount a file or directory from the host into\n" - " the container\n" - " --bind-ro=PATH[:PATH[:OPTIONS]\n" - " Similar, but creates a read-only bind mount\n" - " --inaccessible=PATH Over-mount file node with inaccessible node to mask\n" - " it\n" - " --tmpfs=PATH:[OPTIONS] Mount an empty tmpfs to the specified directory\n" - " --overlay=PATH[:PATH...]:PATH\n" - " Create an overlay mount from the host to \n" - " the container\n" - " --overlay-ro=PATH[:PATH...]:PATH\n" - " Similar, but creates a read-only overlay mount\n" - " --bind-user=NAME Bind user from host to container\n" - " --bind-user-shell=BOOL|PATH\n" - " Configure the shell to use for --bind-user= users\n" - " --bind-user-group=GROUP\n" - " Add an auxiliary group to --bind-user= users\n" - "\n%3$sInput/Output:%4$s\n" - " --console=MODE Select how stdin/stdout/stderr and /dev/console are\n" - " set up for the container.\n" - " -P --pipe Equivalent to --console=pipe\n" - " --background=COLOR Set ANSI color for background\n" - "\n%3$sCredentials:%4$s\n" - " --set-credential=ID:VALUE\n" - " Pass a credential with literal value to container.\n" - " --load-credential=ID:PATH\n" - " Load credential to pass to container from file or\n" - " AF_UNIX stream socket.\n" - "\nSee the %2$s for details.\n", + static const char* const groups[] = { + NULL, + "Image", + "Execution", + "System Identity", + "Properties", + "User Namespacing", + "Networking", + "Security", + "Resources", + "Integration", + "Mounts", + "Input/Output", + "Credentials", + "Other", + }; + + _cleanup_(table_unref_many) Table* tables[ELEMENTSOF(groups) + 1] = {}; + + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + r = option_parser_get_help_table_group(groups[i], &tables[i]); + if (r < 0) + return r; + } + + (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], tables[3], + tables[4], tables[5], tables[6], tables[7], + tables[8], tables[9], tables[10], tables[11], + tables[12], tables[13]); + + printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" + "%sSpawn a command or OS in a lightweight container.%s\n\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + r = table_print_or_warn(tables[0]); + if (r < 0) + return r; + + for (size_t i = 1; i < ELEMENTSOF(groups); i++) { + printf("\n%s%s:%s\n", ansi_underline(), groups[i], ansi_normal()); + + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + + printf("\nSee the %s for details.\n", link); return 0; } @@ -551,6 +459,8 @@ static int parse_capability_spec(const char *spec, uint64_t *ret_mask) { uint64_t mask = 0; int r; + assert(ret_mask); + for (;;) { _cleanup_free_ char *t = NULL; @@ -685,715 +595,526 @@ static int parse_environment(void) { } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_PRIVATE_NETWORK, - ARG_UUID, - ARG_READ_ONLY, - ARG_CAPABILITY, - ARG_AMBIENT_CAPABILITY, - ARG_DROP_CAPABILITY, - ARG_LINK_JOURNAL, - ARG_BIND, - ARG_BIND_RO, - ARG_TMPFS, - ARG_OVERLAY, - ARG_OVERLAY_RO, - ARG_INACCESSIBLE, - ARG_SHARE_SYSTEM, - ARG_REGISTER, - ARG_KEEP_UNIT, - ARG_NETWORK_INTERFACE, - ARG_NETWORK_MACVLAN, - ARG_NETWORK_IPVLAN, - ARG_NETWORK_BRIDGE, - ARG_NETWORK_ZONE, - ARG_NETWORK_VETH_EXTRA, - ARG_NETWORK_NAMESPACE_PATH, - ARG_PERSONALITY, - ARG_VOLATILE, - ARG_TEMPLATE, - ARG_PROPERTY, - ARG_PRIVATE_USERS, - ARG_PRIVATE_USERS_DELEGATE, - ARG_KILL_SIGNAL, - ARG_SETTINGS, - ARG_CHDIR, - ARG_PIVOT_ROOT, - ARG_PRIVATE_USERS_CHOWN, - ARG_PRIVATE_USERS_OWNERSHIP, - ARG_NOTIFY_READY, - ARG_ROOT_HASH, - ARG_ROOT_HASH_SIG, - ARG_VERITY_DATA, - ARG_SYSTEM_CALL_FILTER, - ARG_RLIMIT, - ARG_HOSTNAME, - ARG_NO_NEW_PRIVILEGES, - ARG_OOM_SCORE_ADJUST, - ARG_CPU_AFFINITY, - ARG_RESOLV_CONF, - ARG_TIMEZONE, - ARG_CONSOLE, - ARG_PIPE, - ARG_OCI_BUNDLE, - ARG_NO_PAGER, - ARG_SET_CREDENTIAL, - ARG_LOAD_CREDENTIAL, - ARG_BIND_USER, - ARG_BIND_USER_SHELL, - ARG_BIND_USER_GROUP, - ARG_SUPPRESS_SYNC, - ARG_IMAGE_POLICY, - ARG_BACKGROUND, - ARG_CLEANUP, - ARG_NO_ASK_PASSWORD, - ARG_MSTACK, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "directory", required_argument, NULL, 'D' }, - { "template", required_argument, NULL, ARG_TEMPLATE }, - { "ephemeral", no_argument, NULL, 'x' }, - { "user", required_argument, NULL, 'u' }, - { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK }, - { "as-pid2", no_argument, NULL, 'a' }, - { "boot", no_argument, NULL, 'b' }, - { "uuid", required_argument, NULL, ARG_UUID }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "capability", required_argument, NULL, ARG_CAPABILITY }, - { "ambient-capability", required_argument, NULL, ARG_AMBIENT_CAPABILITY }, - { "drop-capability", required_argument, NULL, ARG_DROP_CAPABILITY }, - { "no-new-privileges", required_argument, NULL, ARG_NO_NEW_PRIVILEGES }, - { "link-journal", required_argument, NULL, ARG_LINK_JOURNAL }, - { "bind", required_argument, NULL, ARG_BIND }, - { "bind-ro", required_argument, NULL, ARG_BIND_RO }, - { "tmpfs", required_argument, NULL, ARG_TMPFS }, - { "overlay", required_argument, NULL, ARG_OVERLAY }, - { "overlay-ro", required_argument, NULL, ARG_OVERLAY_RO }, - { "inaccessible", required_argument, NULL, ARG_INACCESSIBLE }, - { "machine", required_argument, NULL, 'M' }, - { "hostname", required_argument, NULL, ARG_HOSTNAME }, - { "slice", required_argument, NULL, 'S' }, - { "setenv", required_argument, NULL, 'E' }, - { "selinux-context", required_argument, NULL, 'Z' }, - { "selinux-apifs-context", required_argument, NULL, 'L' }, - { "quiet", no_argument, NULL, 'q' }, - { "share-system", no_argument, NULL, ARG_SHARE_SYSTEM }, /* not documented */ - { "register", required_argument, NULL, ARG_REGISTER }, - { "keep-unit", no_argument, NULL, ARG_KEEP_UNIT }, - { "network-interface", required_argument, NULL, ARG_NETWORK_INTERFACE }, - { "network-macvlan", required_argument, NULL, ARG_NETWORK_MACVLAN }, - { "network-ipvlan", required_argument, NULL, ARG_NETWORK_IPVLAN }, - { "network-veth", no_argument, NULL, 'n' }, - { "network-veth-extra", required_argument, NULL, ARG_NETWORK_VETH_EXTRA }, - { "network-bridge", required_argument, NULL, ARG_NETWORK_BRIDGE }, - { "network-zone", required_argument, NULL, ARG_NETWORK_ZONE }, - { "network-namespace-path", required_argument, NULL, ARG_NETWORK_NAMESPACE_PATH }, - { "personality", required_argument, NULL, ARG_PERSONALITY }, - { "image", required_argument, NULL, 'i' }, - { "volatile", optional_argument, NULL, ARG_VOLATILE }, - { "port", required_argument, NULL, 'p' }, - { "property", required_argument, NULL, ARG_PROPERTY }, - { "private-users", optional_argument, NULL, ARG_PRIVATE_USERS }, - { "private-users-chown", optional_argument, NULL, ARG_PRIVATE_USERS_CHOWN }, /* obsolete */ - { "private-users-ownership",required_argument, NULL, ARG_PRIVATE_USERS_OWNERSHIP}, - { "private-users-delegate", required_argument, NULL, ARG_PRIVATE_USERS_DELEGATE }, - { "kill-signal", required_argument, NULL, ARG_KILL_SIGNAL }, - { "settings", required_argument, NULL, ARG_SETTINGS }, - { "chdir", required_argument, NULL, ARG_CHDIR }, - { "pivot-root", required_argument, NULL, ARG_PIVOT_ROOT }, - { "notify-ready", required_argument, NULL, ARG_NOTIFY_READY }, - { "root-hash", required_argument, NULL, ARG_ROOT_HASH }, - { "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG }, - { "verity-data", required_argument, NULL, ARG_VERITY_DATA }, - { "system-call-filter", required_argument, NULL, ARG_SYSTEM_CALL_FILTER }, - { "rlimit", required_argument, NULL, ARG_RLIMIT }, - { "oom-score-adjust", required_argument, NULL, ARG_OOM_SCORE_ADJUST }, - { "cpu-affinity", required_argument, NULL, ARG_CPU_AFFINITY }, - { "resolv-conf", required_argument, NULL, ARG_RESOLV_CONF }, - { "timezone", required_argument, NULL, ARG_TIMEZONE }, - { "console", required_argument, NULL, ARG_CONSOLE }, - { "pipe", no_argument, NULL, ARG_PIPE }, - { "oci-bundle", required_argument, NULL, ARG_OCI_BUNDLE }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL }, - { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL }, - { "bind-user", required_argument, NULL, ARG_BIND_USER }, - { "bind-user-shell", required_argument, NULL, ARG_BIND_USER_SHELL }, - { "bind-user-group", required_argument, NULL, ARG_BIND_USER_GROUP }, - { "suppress-sync", required_argument, NULL, ARG_SUPPRESS_SYNC }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "background", required_argument, NULL, ARG_BACKGROUND }, - { "cleanup", no_argument, NULL, ARG_CLEANUP }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "mstack", required_argument, NULL, ARG_MSTACK }, - {} - }; - - int c, r; uint64_t plus = 0, minus = 0; bool mask_all_settings = false, mask_no_settings = false; + int r; assert(argc >= 0); assert(argv); - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; - while ((c = getopt_long(argc, argv, "+hD:u:abL:M:jS:Z:qi:xp:nUE:P", options, NULL)) >= 0) + OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + const Option *opt; + const char *arg; + + FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) { switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'D': - r = parse_path_argument(optarg, false, &arg_directory); - if (r < 0) - return r; + OPTION('q', "quiet", NULL, "Do not show status information"): + arg_quiet = true; + break; - arg_settings_mask |= SETTING_DIRECTORY; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case ARG_TEMPLATE: - r = parse_path_argument(optarg, false, &arg_template); - if (r < 0) - return r; + OPTION_LONG("settings", "BOOLEAN", "Load additional settings from .nspawn file"): + /* no → do not read files + * yes → read files, do not override cmdline, trust only subset + * override → read files, override cmdline, trust only subset + * trusted → read files, do not override cmdline, trust all + */ - arg_settings_mask |= SETTING_DIRECTORY; + r = parse_boolean(arg); + if (r < 0) { + if (streq(arg, "trusted")) { + mask_all_settings = false; + mask_no_settings = false; + arg_settings_trusted = true; + + } else if (streq(arg, "override")) { + mask_all_settings = false; + mask_no_settings = true; + arg_settings_trusted = -1; + } else + return log_error_errno(r, "Failed to parse --settings= argument: %s", arg); + } else if (r > 0) { + /* yes */ + mask_all_settings = false; + mask_no_settings = false; + arg_settings_trusted = -1; + } else { + /* no */ + mask_all_settings = true; + mask_no_settings = false; + arg_settings_trusted = false; + } break; - case 'i': - r = parse_path_argument(optarg, false, &arg_image); - if (r < 0) - return r; + OPTION_LONG("cleanup", NULL, + "Clean up left-over mounts and underlying mount points used by the container"): + arg_cleanup = true; + break; - arg_settings_mask |= SETTING_DIRECTORY; + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; break; - case ARG_MSTACK: - r = parse_path_argument(optarg, false, &arg_mstack); + OPTION_GROUP("Image"): {} + + OPTION('D', "directory", "PATH", "Root directory for the container"): + r = parse_path_argument(arg, false, &arg_directory); if (r < 0) return r; - arg_settings_mask |= SETTING_DIRECTORY; break; - case ARG_OCI_BUNDLE: - r = parse_path_argument(optarg, false, &arg_oci_bundle); + OPTION_LONG("template", "PATH", + "Initialize root directory from template directory, if missing"): + r = parse_path_argument(arg, false, &arg_template); if (r < 0) return r; - + arg_settings_mask |= SETTING_DIRECTORY; break; - case 'x': + OPTION('x', "ephemeral", NULL, + "Run container with snapshot of root directory, and remove it after exit"): arg_ephemeral = true; arg_settings_mask |= SETTING_EPHEMERAL; break; - case 'u': - r = free_and_strdup(&arg_user, optarg); + OPTION('i', "image", "PATH", + "Root file system disk image (or device node) for the container"): + r = parse_path_argument(arg, false, &arg_image); if (r < 0) - return log_oom(); + return r; + arg_settings_mask |= SETTING_DIRECTORY; + break; - arg_settings_mask |= SETTING_USER; + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); + if (r < 0) + return r; break; - case ARG_NETWORK_ZONE: { - _cleanup_free_ char *j = NULL; + OPTION_LONG("mstack", "PATH", /* help= */ NULL): + r = parse_path_argument(arg, false, &arg_mstack); + if (r < 0) + return r; + arg_settings_mask |= SETTING_DIRECTORY; + break; - j = strjoin("vz-", optarg); - if (!j) - return log_oom(); + OPTION_LONG("oci-bundle", "PATH", "OCI bundle directory"): + r = parse_path_argument(arg, false, &arg_oci_bundle); + if (r < 0) + return r; + break; - if (!ifname_valid(j)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Network zone name not valid: %s", j); + OPTION_LONG("read-only", NULL, "Mount the root directory read-only"): + arg_read_only = true; + arg_settings_mask |= SETTING_READ_ONLY; + break; - free_and_replace(arg_network_zone, j); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "volatile", "MODE", "Run the system in volatile mode"): + if (!arg) + arg_volatile_mode = VOLATILE_YES; + else if (streq(arg, "help")) + return DUMP_STRING_TABLE(volatile_mode, VolatileMode, _VOLATILE_MODE_MAX); + else { + VolatileMode m; - arg_network_veth = true; - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; + m = volatile_mode_from_string(arg); + if (m < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse --volatile= argument: %s", arg); + else + arg_volatile_mode = m; + } + arg_settings_mask |= SETTING_VOLATILE_MODE; break; - } - case ARG_NETWORK_BRIDGE: - - if (!ifname_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Bridge interface name not valid: %s", optarg); + OPTION_LONG("root-hash", "HASH", "Specify verity root hash for root disk image"): { + _cleanup_(iovec_done) struct iovec k = {}; - r = free_and_strdup(&arg_network_bridge, optarg); + r = unhexmem(arg, &k.iov_base, &k.iov_len); if (r < 0) - return log_oom(); + return log_error_errno(r, "Failed to parse root hash: %s", arg); + if (k.iov_len < sizeof(sd_id128_t)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Root hash must be at least 128-bit long: %s", arg); - _fallthrough_; - case 'n': - arg_network_veth = true; - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; + iovec_done(&arg_verity_settings.root_hash); + arg_verity_settings.root_hash = TAKE_STRUCT(k); break; + } - case ARG_NETWORK_VETH_EXTRA: - r = veth_extra_parse(&arg_network_veth_extra, optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --network-veth-extra= parameter: %s", optarg); + OPTION_LONG("root-hash-sig", "SIG", + "Specify pkcs7 signature of root hash for verity"): { + _cleanup_(iovec_done) struct iovec p = {}; + const char *value; - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; - break; + if ((value = startswith(arg, "base64:"))) { + r = unbase64mem(value, &p.iov_base, &p.iov_len); + if (r < 0) + return log_error_errno(r, "Failed to parse root hash signature '%s': %m", arg); - case ARG_NETWORK_INTERFACE: - r = interface_pair_parse(&arg_network_interfaces, optarg); - if (r < 0) - return r; + } else { + r = read_full_file(arg, (char**) &p.iov_base, &p.iov_len); + if (r < 0) + return log_error_errno(r, "Failed to parse root hash signature file '%s': %m", arg); + } - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; + iovec_done(&arg_verity_settings.root_hash_sig); + arg_verity_settings.root_hash_sig = TAKE_STRUCT(p); break; + } - case ARG_NETWORK_MACVLAN: - r = macvlan_pair_parse(&arg_network_macvlan, optarg); + OPTION_LONG("verity-data", "PATH", "Specify hash device for verity"): + r = parse_path_argument(arg, false, &arg_verity_settings.data_path); if (r < 0) return r; - - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; break; - case ARG_NETWORK_IPVLAN: - r = ipvlan_pair_parse(&arg_network_ipvlan, optarg); + OPTION_LONG("pivot-root", "PATH[:PATH]", + "Pivot root to given directory in the container"): + r = pivot_root_parse(&arg_pivot_root_new, &arg_pivot_root_old, arg); if (r < 0) - return r; - - _fallthrough_; - case ARG_PRIVATE_NETWORK: - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; + return log_error_errno(r, "Failed to parse --pivot-root= argument %s: %m", arg); + arg_settings_mask |= SETTING_PIVOT_ROOT; break; - case ARG_NETWORK_NAMESPACE_PATH: - r = parse_path_argument(optarg, false, &arg_network_namespace_path); - if (r < 0) - return r; + OPTION_GROUP("Execution"): {} - arg_settings_mask |= SETTING_NETWORK; - break; - - case 'b': - if (arg_start_mode == START_PID2) + OPTION('a', "as-pid2", NULL, "Maintain a stub init as PID1, invoke binary as PID2"): + if (arg_start_mode == START_BOOT) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--boot and --as-pid2 may not be combined."); - - arg_start_mode = START_BOOT; + arg_start_mode = START_PID2; arg_settings_mask |= SETTING_START_MODE; break; - case 'a': - if (arg_start_mode == START_BOOT) + OPTION('b', "boot", NULL, "Boot up full system (i.e. invoke init)"): + if (arg_start_mode == START_PID2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--boot and --as-pid2 may not be combined."); - - arg_start_mode = START_PID2; + arg_start_mode = START_BOOT; arg_settings_mask |= SETTING_START_MODE; break; - case ARG_UUID: - r = id128_from_string_nonzero(optarg, &arg_uuid); - if (r == -ENXIO) + OPTION_LONG("chdir", "PATH", "Set working directory in the container"): { + _cleanup_free_ char *wd = NULL; + + if (!path_is_absolute(arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Machine UUID may not be all zeroes."); - if (r < 0) - return log_error_errno(r, "Invalid UUID: %s", optarg); + "Working directory %s is not an absolute path.", arg); - arg_settings_mask |= SETTING_MACHINE_ID; - break; + r = path_simplify_alloc(arg, &wd); + if (r < 0) + return log_error_errno(r, "Failed to simplify path %s: %m", arg); - case 'S': { - _cleanup_free_ char *mangled = NULL; + if (!path_is_normalized(wd)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory path is not normalized: %s", wd); - r = unit_name_mangle_with_suffix(optarg, NULL, UNIT_NAME_MANGLE_WARN, ".slice", &mangled); - if (r < 0) - return log_oom(); + if (path_below_api_vfs(wd)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory is below API VFS, refusing: %s", wd); - free_and_replace(arg_slice, mangled); - arg_settings_mask |= SETTING_SLICE; + free_and_replace(arg_chdir, wd); + arg_settings_mask |= SETTING_WORKING_DIRECTORY; break; } - case 'M': - if (!isempty(optarg) && !hostname_is_valid(optarg, /* flags= */ 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid machine name: %s", optarg); - - r = free_and_strdup_warn(&arg_machine, optarg); + OPTION('E', "setenv", "NAME[=VALUE]", "Pass an environment variable to PID 1"): + r = strv_env_replace_strdup_passthrough(&arg_setenv, arg); if (r < 0) - return r; + return log_error_errno(r, "Cannot assign environment variable %s: %m", arg); + arg_settings_mask |= SETTING_ENVIRONMENT; break; - case ARG_HOSTNAME: - if (!isempty(optarg) && !hostname_is_valid(optarg, /* flags= */ 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid hostname: %s", optarg); - - r = free_and_strdup_warn(&arg_hostname, optarg); + OPTION('u', "uid", "USER", "Run the command under specified user or UID"): + r = free_and_strdup(&arg_user, arg); if (r < 0) - return r; - - arg_settings_mask |= SETTING_HOSTNAME; - break; - - case 'Z': - arg_selinux_context = optarg; + return log_oom(); + arg_settings_mask |= SETTING_USER; break; - case 'L': - arg_selinux_apifs_context = optarg; - break; + OPTION_LONG("kill-signal", "SIGNAL", "Select signal to use for shutting down PID 1"): + if (streq(arg, "help")) + return DUMP_STRING_TABLE(signal, int, _NSIG); - case ARG_READ_ONLY: - arg_read_only = true; - arg_settings_mask |= SETTING_READ_ONLY; + arg_kill_signal = signal_from_string(arg); + if (arg_kill_signal < 0) + return log_error_errno(arg_kill_signal, "Cannot parse signal: %s", arg); + arg_settings_mask |= SETTING_KILL_SIGNAL; break; - case ARG_AMBIENT_CAPABILITY: { - uint64_t m; - r = parse_capability_spec(optarg, &m); - if (r <= 0) + OPTION_LONG("notify-ready", "BOOLEAN", "Receive notifications from the child init process"): + r = parse_boolean_argument("--notify-ready=", arg, &arg_notify_ready); + if (r < 0) return r; - arg_caps_ambient |= m; - arg_settings_mask |= SETTING_CAPABILITY; + arg_settings_mask |= SETTING_NOTIFY_READY; break; - } - case ARG_CAPABILITY: - case ARG_DROP_CAPABILITY: { - uint64_t m; - r = parse_capability_spec(optarg, &m); - if (r <= 0) - return r; - if (c == ARG_CAPABILITY) - plus |= m; - else - minus |= m; - arg_settings_mask |= SETTING_CAPABILITY; - break; - } - case ARG_NO_NEW_PRIVILEGES: - r = parse_boolean_argument("--no-new-privileges=", optarg, &arg_no_new_privileges); + OPTION_LONG("suppress-sync", "BOOLEAN", "Suppress any form of disk data synchronization"): + r = parse_boolean_argument("--suppress-sync=", arg, &arg_suppress_sync); if (r < 0) return r; - - arg_settings_mask |= SETTING_NO_NEW_PRIVILEGES; + arg_settings_mask |= SETTING_SUPPRESS_SYNC; break; - case 'j': - arg_link_journal = LINK_GUEST; - arg_link_journal_try = true; - arg_settings_mask |= SETTING_LINK_JOURNAL; - break; + OPTION_GROUP("System Identity"): {} - case ARG_LINK_JOURNAL: - r = parse_link_journal(optarg, &arg_link_journal, &arg_link_journal_try); - if (r < 0) - return log_error_errno(r, "Failed to parse link journal mode %s", optarg); - - arg_settings_mask |= SETTING_LINK_JOURNAL; - break; - - case ARG_BIND: - case ARG_BIND_RO: - r = bind_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg, c == ARG_BIND_RO); + OPTION('M', "machine", "NAME", "Set the machine name for the container"): + if (!isempty(arg) && !hostname_is_valid(arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid machine name: %s", arg); + r = free_and_strdup_warn(&arg_machine, arg); if (r < 0) - return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", optarg); - - arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + return r; break; - case ARG_TMPFS: - r = tmpfs_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg); + OPTION_LONG("hostname", "NAME", "Override the hostname for the container"): + if (!isempty(arg) && !hostname_is_valid(arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid hostname: %s", arg); + r = free_and_strdup_warn(&arg_hostname, arg); if (r < 0) - return log_error_errno(r, "Failed to parse --tmpfs= argument %s: %m", optarg); - - arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + return r; + arg_settings_mask |= SETTING_HOSTNAME; break; - case ARG_OVERLAY: - case ARG_OVERLAY_RO: - r = overlay_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg, c == ARG_OVERLAY_RO); - if (r == -EADDRNOTAVAIL) - return log_error_errno(r, "--overlay(-ro)= needs at least two colon-separated directories specified."); + OPTION_LONG("uuid", "UUID", "Set a specific machine UUID for the container"): + r = id128_from_string_nonzero(arg, &arg_uuid); + if (r == -ENXIO) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Machine UUID may not be all zeroes."); if (r < 0) - return log_error_errno(r, "Failed to parse --overlay(-ro)= argument %s: %m", optarg); - - arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + return log_error_errno(r, "Invalid UUID: %s", arg); + arg_settings_mask |= SETTING_MACHINE_ID; break; - case ARG_INACCESSIBLE: - r = inaccessible_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --inaccessible= argument %s: %m", optarg); + OPTION_GROUP("Properties"): {} - arg_settings_mask |= SETTING_CUSTOM_MOUNTS; - break; + OPTION('S', "slice", "SLICE", "Place the container in the specified slice"): { + _cleanup_free_ char *mangled = NULL; - case 'E': - r = strv_env_replace_strdup_passthrough(&arg_setenv, optarg); + r = unit_name_mangle_with_suffix(arg, NULL, UNIT_NAME_MANGLE_WARN, ".slice", &mangled); if (r < 0) - return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); - - arg_settings_mask |= SETTING_ENVIRONMENT; - break; + return log_oom(); - case 'q': - arg_quiet = true; + free_and_replace(arg_slice, mangled); + arg_settings_mask |= SETTING_SLICE; break; + } - case ARG_SHARE_SYSTEM: - /* We don't officially support this anymore, except for compat reasons. People should use the - * $SYSTEMD_NSPAWN_SHARE_* environment variables instead. */ - log_warning("Please do not use --share-system anymore, use $SYSTEMD_NSPAWN_SHARE_* instead."); - arg_clone_ns_flags = 0; + OPTION_LONG("property", "NAME=VALUE", "Set scope unit property"): + if (strv_extend(&arg_property, arg) < 0) + return log_oom(); break; - case ARG_REGISTER: - r = parse_boolean_argument("--register=", optarg, &arg_register); + OPTION_LONG("register", "BOOLEAN", "Register container as machine"): + r = parse_tristate_argument_with_auto("--register=", arg, &arg_register); if (r < 0) return r; - break; - case ARG_KEEP_UNIT: + OPTION_LONG("keep-unit", NULL, + "Do not register a scope for the machine, reuse the service unit nspawn is running in"): arg_keep_unit = true; break; - case ARG_PERSONALITY: - - arg_personality = personality_from_string(optarg); - if (arg_personality == PERSONALITY_INVALID) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown or unsupported personality '%s'.", optarg); - - arg_settings_mask |= SETTING_PERSONALITY; - break; - - case ARG_VOLATILE: - - if (!optarg) - arg_volatile_mode = VOLATILE_YES; - else if (streq(optarg, "help")) - return DUMP_STRING_TABLE(volatile_mode, VolatileMode, _VOLATILE_MODE_MAX); - else { - VolatileMode m; + OPTION_GROUP("User Namespacing"): {} - m = volatile_mode_from_string(optarg); - if (m < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse --volatile= argument: %s", optarg); - else - arg_volatile_mode = m; - } - - arg_settings_mask |= SETTING_VOLATILE_MODE; + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "private-users", "MODE", + "Run within user namespace, configure UID/GID range"): + r = parse_private_users(arg, &arg_userns_mode, &arg_uid_shift, &arg_uid_range); + if (r < 0) + return r; + arg_settings_mask |= SETTING_USERNS; break; - case 'p': - r = expose_port_parse(&arg_expose_ports, optarg); - if (r == -EEXIST) - return log_error_errno(r, "Duplicate port specification: %s", optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse host port %s: %m", optarg); + OPTION_LONG("private-users-ownership", "MODE", + "Adjust ('chown') or map ('map') OS tree ownership to private UID/GID range"): + if (streq(arg, "help")) + return DUMP_STRING_TABLE(user_namespace_ownership, UserNamespaceOwnership, _USER_NAMESPACE_OWNERSHIP_MAX); - arg_settings_mask |= SETTING_EXPOSE_PORTS; + arg_userns_ownership = user_namespace_ownership_from_string(arg); + if (arg_userns_ownership < 0) + return log_error_errno(arg_userns_ownership, "Cannot parse --private-users-ownership= value: %s", arg); + arg_settings_mask |= SETTING_USERNS; break; - case ARG_PROPERTY: - if (strv_extend(&arg_property, optarg) < 0) - return log_oom(); - + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "private-users-chown", "MODE", /* help= */ NULL): /* obsolete */ + arg_userns_ownership = USER_NAMESPACE_OWNERSHIP_CHOWN; + arg_settings_mask |= SETTING_USERNS; break; - case ARG_PRIVATE_USERS: - r = parse_private_users(optarg, &arg_userns_mode, &arg_uid_shift, &arg_uid_range); + OPTION_LONG("private-users-delegate", "N", + "Delegate N additional 64K UID/GID ranges for use by nested containers"): + r = safe_atou(arg, &arg_delegate_container_ranges); if (r < 0) - return r; - + return log_error_errno(r, "Failed to parse --private-users-delegate= parameter: %s", arg); arg_settings_mask |= SETTING_USERNS; break; - case 'U': + OPTION_SHORT('U', NULL, + "Equivalent to --private-users=pick and --private-users-ownership=auto"): if (userns_supported()) { - /* Note that arg_userns_ownership is implied by USER_NAMESPACE_PICK further down. */ - arg_userns_mode = arg_privileged ? USER_NAMESPACE_PICK : USER_NAMESPACE_MANAGED; + arg_userns_mode = _USER_NAMESPACE_MODE_INVALID; arg_uid_shift = UID_INVALID; arg_uid_range = UINT32_C(0x10000); - arg_settings_mask |= SETTING_USERNS; } - break; - case ARG_PRIVATE_USERS_CHOWN: - arg_userns_ownership = USER_NAMESPACE_OWNERSHIP_CHOWN; + OPTION_GROUP("Networking"): {} - arg_settings_mask |= SETTING_USERNS; + OPTION_LONG("private-network", NULL, "Disable network in container"): + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; break; - case ARG_PRIVATE_USERS_OWNERSHIP: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(user_namespace_ownership, UserNamespaceOwnership, _USER_NAMESPACE_OWNERSHIP_MAX); - - arg_userns_ownership = user_namespace_ownership_from_string(optarg); - if (arg_userns_ownership < 0) - return log_error_errno(arg_userns_ownership, "Cannot parse --private-users-ownership= value: %s", optarg); - - arg_settings_mask |= SETTING_USERNS; + OPTION_LONG("network-interface", "HOSTIF[:CONTAINERIF]", + "Assign an existing network interface to the container"): + r = interface_pair_parse(&arg_network_interfaces, arg); + if (r < 0) + return r; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; break; - case ARG_PRIVATE_USERS_DELEGATE: - r = safe_atou(optarg, &arg_delegate_container_ranges); + OPTION_LONG("network-macvlan", "HOSTIF[:CONTAINERIF]", + "Create a macvlan network interface based on an existing network interface to the container"): + r = macvlan_pair_parse(&arg_network_macvlan, arg); if (r < 0) - return log_error_errno(r, "Failed to parse --private-users-delegate= parameter: %s", optarg); - - arg_settings_mask |= SETTING_USERNS; + return r; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; break; - case ARG_KILL_SIGNAL: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(signal, int, _NSIG); - - arg_kill_signal = signal_from_string(optarg); - if (arg_kill_signal < 0) - return log_error_errno(arg_kill_signal, "Cannot parse signal: %s", optarg); - - arg_settings_mask |= SETTING_KILL_SIGNAL; + OPTION_LONG("network-ipvlan", "HOSTIF[:CONTAINERIF]", + "Create an ipvlan network interface based on an existing network interface to the container"): + r = ipvlan_pair_parse(&arg_network_ipvlan, arg); + if (r < 0) + return r; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; break; - case ARG_SETTINGS: - - /* no → do not read files - * yes → read files, do not override cmdline, trust only subset - * override → read files, override cmdline, trust only subset - * trusted → read files, do not override cmdline, trust all - */ - - r = parse_boolean(optarg); - if (r < 0) { - if (streq(optarg, "trusted")) { - mask_all_settings = false; - mask_no_settings = false; - arg_settings_trusted = true; - - } else if (streq(optarg, "override")) { - mask_all_settings = false; - mask_no_settings = true; - arg_settings_trusted = -1; - } else - return log_error_errno(r, "Failed to parse --settings= argument: %s", optarg); - } else if (r > 0) { - /* yes */ - mask_all_settings = false; - mask_no_settings = false; - arg_settings_trusted = -1; - } else { - /* no */ - mask_all_settings = true; - mask_no_settings = false; - arg_settings_trusted = false; - } - + OPTION('n', "network-veth", NULL, + "Add a virtual Ethernet connection between host and container"): + arg_network_veth = true; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; break; - case ARG_CHDIR: { - _cleanup_free_ char *wd = NULL; + OPTION_LONG("network-veth-extra", "HOSTIF[:CONTAINERIF]", + "Add an additional virtual Ethernet link between host and container"): + r = veth_extra_parse(&arg_network_veth_extra, arg); + if (r < 0) + return log_error_errno(r, "Failed to parse --network-veth-extra= parameter: %s", arg); + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; - if (!path_is_absolute(optarg)) + OPTION_LONG("network-bridge", "INTERFACE", + "Add a virtual Ethernet connection to the container and attach it to an existing bridge on the host"): + if (!ifname_valid(arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Working directory %s is not an absolute path.", optarg); - - r = path_simplify_alloc(optarg, &wd); + "Bridge interface name not valid: %s", arg); + r = free_and_strdup(&arg_network_bridge, arg); if (r < 0) - return log_error_errno(r, "Failed to simplify path %s: %m", optarg); - - if (!path_is_normalized(wd)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory path is not normalized: %s", wd); + return log_oom(); + arg_network_veth = true; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; - if (path_below_api_vfs(wd)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory is below API VFS, refusing: %s", wd); + OPTION_LONG("network-zone", "NAME", + "Similar, but attach the new interface to an automatically managed bridge interface"): { + _cleanup_free_ char *j = NULL; - free_and_replace(arg_chdir, wd); - arg_settings_mask |= SETTING_WORKING_DIRECTORY; - break; - } + j = strjoin("vz-", arg); + if (!j) + return log_oom(); - case ARG_PIVOT_ROOT: - r = pivot_root_parse(&arg_pivot_root_new, &arg_pivot_root_old, optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --pivot-root= argument %s: %m", optarg); + if (!ifname_valid(j)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Network zone name not valid: %s", j); - arg_settings_mask |= SETTING_PIVOT_ROOT; + free_and_replace(arg_network_zone, j); + arg_network_veth = true; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; break; + } - case ARG_NOTIFY_READY: - r = parse_boolean_argument("--notify-ready=", optarg, &arg_notify_ready); + OPTION_LONG("network-namespace-path", "PATH", + "Set network namespace to the one represented by the specified kernel namespace file node"): + r = parse_path_argument(arg, false, &arg_network_namespace_path); if (r < 0) return r; + arg_settings_mask |= SETTING_NETWORK; + break; - arg_settings_mask |= SETTING_NOTIFY_READY; - break; - - case ARG_ROOT_HASH: { - _cleanup_(iovec_done) struct iovec k = {}; - - r = unhexmem(optarg, &k.iov_base, &k.iov_len); + OPTION('p', "port", "[PROTOCOL:]HOSTPORT[:CONTAINERPORT]", + "Expose a container IP port on the host"): + r = expose_port_parse(&arg_expose_ports, arg); + if (r == -EEXIST) + return log_error_errno(r, "Duplicate port specification: %s", arg); if (r < 0) - return log_error_errno(r, "Failed to parse root hash: %s", optarg); - if (k.iov_len < sizeof(sd_id128_t)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Root hash must be at least 128-bit long: %s", optarg); - - iovec_done(&arg_verity_settings.root_hash); - arg_verity_settings.root_hash = TAKE_STRUCT(k); + return log_error_errno(r, "Failed to parse host port %s: %m", arg); + arg_settings_mask |= SETTING_EXPOSE_PORTS; break; - } - case ARG_ROOT_HASH_SIG: { - _cleanup_(iovec_done) struct iovec p = {}; - char *value; + OPTION_GROUP("Security"): {} - if ((value = startswith(optarg, "base64:"))) { - r = unbase64mem(value, &p.iov_base, &p.iov_len); - if (r < 0) - return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg); + OPTION_LONG("capability", "CAP", + "In addition to the default, retain specified capability"): {} + OPTION_LONG("drop-capability", "CAP", + "Drop the specified capability from the default set"): { + uint64_t m; + r = parse_capability_spec(arg, &m); + if (r <= 0) + return r; - } else { - r = read_full_file(optarg, (char**) &p.iov_base, &p.iov_len); - if (r < 0) - return log_error_errno(r, "Failed to parse root hash signature file '%s': %m", optarg); - } + if (streq(opt->long_code, "capability")) + plus |= m; + else + minus |= m; + arg_settings_mask |= SETTING_CAPABILITY; + break; + } - iovec_done(&arg_verity_settings.root_hash_sig); - arg_verity_settings.root_hash_sig = TAKE_STRUCT(p); + OPTION_LONG("ambient-capability", "CAP", + "Sets the specified capability for the started process"): { + uint64_t m; + r = parse_capability_spec(arg, &m); + if (r <= 0) + return r; + arg_caps_ambient |= m; + arg_settings_mask |= SETTING_CAPABILITY; break; } - case ARG_VERITY_DATA: - r = parse_path_argument(optarg, false, &arg_verity_settings.data_path); + OPTION_LONG("no-new-privileges", "BOOL", + "Set PR_SET_NO_NEW_PRIVS flag for container payload"): + r = parse_boolean_argument("--no-new-privileges=", arg, &arg_no_new_privileges); if (r < 0) return r; + arg_settings_mask |= SETTING_NO_NEW_PRIVILEGES; break; - case ARG_SYSTEM_CALL_FILTER: { + OPTION_LONG("system-call-filter", "LIST|~LIST", + "Permit/prohibit specific system calls"): { bool negative; const char *items; - negative = optarg[0] == '~'; - items = negative ? optarg + 1 : optarg; + negative = arg[0] == '~'; + items = negative ? arg + 1 : arg; for (;;) { _cleanup_free_ char *word = NULL; @@ -1413,25 +1134,44 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return log_oom(); } - arg_settings_mask |= SETTING_SYSCALL_FILTER; break; } - case ARG_RLIMIT: { + OPTION_LONG("restrict-address-families", "LIST", "Restrict socket address families to the given allowlist"): + r = parse_address_families(optarg, &arg_restrict_address_families, &arg_restrict_address_families_is_allowlist); + if (r < 0) + return log_error_errno(r, "Failed to parse --restrict-address-families= argument: %s", optarg); + + arg_settings_mask |= SETTING_RESTRICT_ADDRESS_FAMILIES; + break; + + OPTION('Z', "selinux-context", "SECLABEL", + "Set the SELinux security context to be used by processes in the container"): + arg_selinux_context = arg; + break; + + OPTION('L', "selinux-apifs-context", "SECLABEL", + "Set the SELinux security context to be used by API/tmpfs file systems in the container"): + arg_selinux_apifs_context = arg; + break; + + OPTION_GROUP("Resources"): {} + + OPTION_LONG("rlimit", "NAME=LIMIT", "Set a resource limit for the payload"): { const char *eq; _cleanup_free_ char *name = NULL; int rl; - if (streq(optarg, "help")) + if (streq(arg, "help")) return DUMP_STRING_TABLE(rlimit, int, _RLIMIT_MAX); - eq = strchr(optarg, '='); + eq = strchr(arg, '='); if (!eq) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--rlimit= expects an '=' assignment."); - name = strndup(optarg, eq - optarg); + name = strndup(arg, eq - arg); if (!name) return log_oom(); @@ -1453,162 +1193,261 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_OOM_SCORE_ADJUST: - r = parse_oom_score_adjust(optarg, &arg_oom_score_adjust); + OPTION_LONG("oom-score-adjust", "VALUE", "Adjust the OOM score value for the payload"): + r = parse_oom_score_adjust(arg, &arg_oom_score_adjust); if (r < 0) - return log_error_errno(r, "Failed to parse --oom-score-adjust= parameter: %s", optarg); - + return log_error_errno(r, "Failed to parse --oom-score-adjust= parameter: %s", arg); arg_oom_score_adjust_set = true; arg_settings_mask |= SETTING_OOM_SCORE_ADJUST; break; - case ARG_CPU_AFFINITY: { + OPTION_LONG("cpu-affinity", "CPUS", "Adjust the CPU affinity of the container"): { CPUSet cpuset; - r = parse_cpu_set(optarg, &cpuset); + r = parse_cpu_set(arg, &cpuset); if (r < 0) - return log_error_errno(r, "Failed to parse CPU affinity mask %s: %m", optarg); + return log_error_errno(r, "Failed to parse CPU affinity mask %s: %m", arg); cpu_set_done_and_replace(arg_cpu_set, cpuset); arg_settings_mask |= SETTING_CPU_AFFINITY; break; } - case ARG_RESOLV_CONF: - if (streq(optarg, "help")) + OPTION_LONG("personality", "ARCH", "Pick personality for this container"): + arg_personality = personality_from_string(arg); + if (arg_personality == PERSONALITY_INVALID) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown or unsupported personality '%s'.", arg); + arg_settings_mask |= SETTING_PERSONALITY; + break; + + OPTION_GROUP("Integration"): {} + + OPTION_LONG("resolv-conf", "MODE", "Select mode of /etc/resolv.conf initialization"): + if (streq(arg, "help")) return DUMP_STRING_TABLE(resolv_conf_mode, ResolvConfMode, _RESOLV_CONF_MODE_MAX); - arg_resolv_conf = resolv_conf_mode_from_string(optarg); + arg_resolv_conf = resolv_conf_mode_from_string(arg); if (arg_resolv_conf < 0) return log_error_errno(arg_resolv_conf, - "Failed to parse /etc/resolv.conf mode: %s", optarg); - + "Failed to parse /etc/resolv.conf mode: %s", arg); arg_settings_mask |= SETTING_RESOLV_CONF; break; - case ARG_TIMEZONE: - if (streq(optarg, "help")) + OPTION_LONG("timezone", "MODE", "Select mode of /etc/localtime initialization"): + if (streq(arg, "help")) return DUMP_STRING_TABLE(timezone_mode, TimezoneMode, _TIMEZONE_MODE_MAX); - arg_timezone = timezone_mode_from_string(optarg); + arg_timezone = timezone_mode_from_string(arg); if (arg_timezone < 0) return log_error_errno(arg_timezone, - "Failed to parse /etc/localtime mode: %s", optarg); - + "Failed to parse /etc/localtime mode: %s", arg); arg_settings_mask |= SETTING_TIMEZONE; break; - case ARG_CONSOLE: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(console_mode, ConsoleMode, _CONSOLE_MODE_MAX); + OPTION_LONG("link-journal", "MODE", + "Link up guest journal, one of no, auto, guest, host, try-guest, try-host"): + r = parse_link_journal(arg, &arg_link_journal, &arg_link_journal_try); + if (r < 0) + return log_error_errno(r, "Failed to parse link journal mode %s", arg); + arg_settings_mask |= SETTING_LINK_JOURNAL; + break; - arg_console_mode = console_mode_from_string(optarg); - if (arg_console_mode < 0) - return log_error_errno(arg_console_mode, "Unknown console mode: %s", optarg); + OPTION_SHORT('j', NULL, "Equivalent to --link-journal=try-guest"): + arg_link_journal = LINK_GUEST; + arg_link_journal_try = true; + arg_settings_mask |= SETTING_LINK_JOURNAL; + break; - arg_settings_mask |= SETTING_CONSOLE_MODE; + OPTION_LONG("forward-journal", "FILE|DIR", "Forward the container's journal to the host"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_forward_journal); + if (r < 0) + return r; + break; + OPTION_LONG("forward-journal-max-use", "BYTES", "Maximum disk space for forwarded journal"): + r = parse_size(arg, 1024, &arg_forward_journal_max_use); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-max-use= value: %s", optarg); break; - case 'P': - case ARG_PIPE: - arg_console_mode = CONSOLE_PIPE; - arg_settings_mask |= SETTING_CONSOLE_MODE; + OPTION_LONG("forward-journal-keep-free", "BYTES", "Minimum disk space to keep free"): + r = parse_size(arg, 1024, &arg_forward_journal_keep_free); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-keep-free= value: %s", optarg); break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION_LONG("forward-journal-max-file-size", "BYTES", "Maximum size of individual journal files"): + r = parse_size(arg, 1024, &arg_forward_journal_max_file_size); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-max-file-size= value: %s", optarg); break; - case ARG_SET_CREDENTIAL: - r = machine_credential_set(&arg_credentials, optarg); + OPTION_LONG("forward-journal-max-files", "N", "Maximum number of journal files to keep"): + r = safe_atou64(arg, &arg_forward_journal_max_files); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse --forward-journal-max-files= value: %s", optarg); + break; - arg_settings_mask |= SETTING_CREDENTIALS; + OPTION_GROUP("Mounts"): {} + + OPTION_LONG("bind", "PATH[:PATH[:OPTIONS]]", + "Bind mount a file or directory from the host into the container"): {} + OPTION_LONG("bind-ro", "PATH[:PATH[:OPTIONS]]", + "Similar, but creates a read-only bind mount"): + r = bind_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, arg, + streq(opt->long_code, "bind-ro")); + if (r < 0) + return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", arg); + arg_settings_mask |= SETTING_CUSTOM_MOUNTS; break; - case ARG_LOAD_CREDENTIAL: - r = machine_credential_load(&arg_credentials, optarg); + OPTION_LONG("inaccessible", "PATH", + "Over-mount file node with inaccessible node to mask it"): + r = inaccessible_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, arg); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse --inaccessible= argument %s: %m", arg); + arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + break; - arg_settings_mask |= SETTING_CREDENTIALS; + OPTION_LONG("tmpfs", "PATH:[OPTIONS]", + "Mount an empty tmpfs to the specified directory"): + r = tmpfs_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, arg); + if (r < 0) + return log_error_errno(r, "Failed to parse --tmpfs= argument %s: %m", arg); + arg_settings_mask |= SETTING_CUSTOM_MOUNTS; break; - case ARG_BIND_USER: - if (!valid_user_group_name(optarg, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", optarg); + OPTION_LONG("overlay", "PATH[:PATH...]:PATH", + "Create an overlay mount from the host to the container"): {} + OPTION_LONG("overlay-ro", "PATH[:PATH...]:PATH", + "Similar, but creates a read-only overlay mount"): + r = overlay_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, arg, + streq(opt->long_code, "overlay-ro")); + if (r == -EADDRNOTAVAIL) + return log_error_errno(r, "--overlay(-ro)= needs at least two colon-separated directories specified."); + if (r < 0) + return log_error_errno(r, "Failed to parse --overlay(-ro)= argument %s: %m", arg); + arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + break; - if (strv_extend(&arg_bind_user, optarg) < 0) + OPTION_LONG("bind-user", "NAME", "Bind user from host to container"): + if (!valid_user_group_name(arg, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", arg); + if (strv_extend(&arg_bind_user, arg) < 0) return log_oom(); - arg_settings_mask |= SETTING_BIND_USER; break; - case ARG_BIND_USER_SHELL: { + OPTION_LONG("bind-user-shell", "BOOL|PATH", + "Configure the shell to use for --bind-user= users"): { bool copy = false; char *sh = NULL; - r = parse_user_shell(optarg, &sh, ©); + r = parse_user_shell(arg, &sh, ©); if (r == -ENOMEM) return log_oom(); if (r < 0) - return log_error_errno(r, "Invalid user shell to bind: %s", optarg); + return log_error_errno(r, "Invalid user shell to bind: %s", arg); free_and_replace(arg_bind_user_shell, sh); arg_bind_user_shell_copy = copy; - arg_settings_mask |= SETTING_BIND_USER_SHELL; break; } - case ARG_BIND_USER_GROUP: - if (!valid_user_group_name(optarg, /* flags= */ 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", optarg); - - if (strv_extend(&arg_bind_user_groups, optarg) < 0) + OPTION_LONG("bind-user-group", "GROUP", + "Add an auxiliary group to --bind-user= users"): + if (!valid_user_group_name(arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", arg); + if (strv_extend(&arg_bind_user_groups, arg) < 0) return log_oom(); + break; + + OPTION_GROUP("Input/Output"): {} + + OPTION_LONG("console", "MODE", + "Select how stdin/stdout/stderr and /dev/console are set up for the container"): + if (streq(arg, "help")) + return DUMP_STRING_TABLE(console_mode, ConsoleMode, _CONSOLE_MODE_MAX); + + arg_console_mode = console_mode_from_string(arg); + if (arg_console_mode < 0) + return log_error_errno(arg_console_mode, "Unknown console mode: %s", arg); + arg_settings_mask |= SETTING_CONSOLE_MODE; + break; + OPTION('P', "pipe", NULL, "Equivalent to --console=pipe"): + arg_console_mode = CONSOLE_PIPE; + arg_settings_mask |= SETTING_CONSOLE_MODE; break; - case ARG_SUPPRESS_SYNC: - r = parse_boolean_argument("--suppress-sync=", optarg, &arg_suppress_sync); + OPTION_LONG("background", "COLOR", "Set ANSI color for background"): + r = parse_background_argument(arg, &arg_background); if (r < 0) return r; - - arg_settings_mask |= SETTING_SUPPRESS_SYNC; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_GROUP("Credentials"): {} + + OPTION_LONG("set-credential", "ID:VALUE", + "Pass a credential with literal value to container"): + r = machine_credential_set(&arg_credentials, arg); if (r < 0) return r; + arg_settings_mask |= SETTING_CREDENTIALS; break; - case ARG_BACKGROUND: - r = parse_background_argument(optarg, &arg_background); + OPTION_LONG("load-credential", "ID:PATH", + "Load credential to pass to container from file or AF_UNIX stream socket"): + r = machine_credential_load(&arg_credentials, arg); if (r < 0) return r; + arg_settings_mask |= SETTING_CREDENTIALS; break; - case ARG_CLEANUP: - arg_cleanup = true; - break; + OPTION_GROUP("Other"): {} - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION_LONG("share-system", NULL, /* help= */ NULL): /* not documented */ + log_warning("Please do not use --share-system anymore, use $SYSTEMD_NSPAWN_SHARE_* instead."); + arg_clone_ns_flags = 0; break; - case '?': - return -EINVAL; + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "user", "NAME", "Run in the user service manager scope"): + if (arg) + /* --user=NAME is a deprecated alias for --uid=NAME */ + log_warning("--user=NAME is deprecated, use --uid=NAME instead."); + else { + /* --user= used to require an argument (the container user to run as). It has + * been repurposed to optionally set the runtime scope, with --uid= replacing + * the old container user functionality. To maintain backwards compatibility + * with the space-separated form (--user NAME), if the next arg does not look + * like an option, interpret it as a user name. */ + const char *t = option_parser_next_arg(&state); + if (t && t[0] != '-') { + arg = option_parser_consume_next_arg(&state); + log_warning("--user NAME is deprecated, use --uid=NAME instead."); + } + } + + if (arg) { + r = free_and_strdup(&arg_user, arg); + if (r < 0) + return log_oom(); + arg_settings_mask |= SETTING_USER; + } else + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; - default: - assert_not_reached(); + OPTION_LONG("system", NULL, "Run in the system service manager scope"): + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; } + } - if (argc > optind) { + char **args = option_parser_get_args(&state); + if (!strv_isempty(args)) { strv_free(arg_parameters); - arg_parameters = strv_copy(argv + optind); + arg_parameters = strv_copy(args); if (!arg_parameters) return log_oom(); @@ -1622,10 +1461,36 @@ static int parse_argv(int argc, char *argv[]) { * --directory=". */ arg_directory = TAKE_PTR(arg_template); + /* Derive runtime scope from UID if not explicitly set via --user/--system */ + if (arg_runtime_scope < 0) + arg_runtime_scope = getuid() == 0 ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER; + + if (arg_userns_mode == _USER_NAMESPACE_MODE_INVALID) { + /* -U sets arg_userns_mode to _USER_NAMESPACE_MODE_INVALID to defer the PICK vs MANAGED + * resolution to here where arg_runtime_scope has its final value. */ + if (arg_runtime_scope == RUNTIME_SCOPE_USER) + arg_userns_mode = USER_NAMESPACE_MANAGED; + else if (FLAGS_SET(arg_settings_mask, SETTING_USERNS)) + arg_userns_mode = USER_NAMESPACE_PICK; + else + arg_userns_mode = USER_NAMESPACE_NO; + } + + if (!FLAGS_SET(arg_settings_mask, SETTING_NETWORK)) + /* Imply private networking for unprivileged operation, since kernel otherwise + * refuses mounting sysfs. */ + arg_private_network = arg_runtime_scope == RUNTIME_SCOPE_USER; + arg_caps_retain |= plus; arg_caps_retain |= arg_private_network ? UINT64_C(1) << CAP_NET_ADMIN : 0; arg_caps_retain &= ~minus; + if ((arg_forward_journal_max_use != UINT64_MAX || + arg_forward_journal_keep_free != UINT64_MAX || + arg_forward_journal_max_file_size != UINT64_MAX || + arg_forward_journal_max_files != UINT64_MAX) && !arg_forward_journal) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--forward-journal-max-use=/--forward-journal-keep-free=/--forward-journal-max-file-size=/--forward-journal-max-files= require --forward-journal=."); + /* Make sure to parse environment before we reset the settings mask below */ r = parse_environment(); if (r < 0) @@ -1649,7 +1514,7 @@ static int verify_arguments(void) { /* We can mount selinuxfs only if we are privileged and can do so before userns. In managed mode we * have to enter the userns earlier, hence cannot do that. */ - /* SET_FLAG(arg_mount_settings, MOUNT_PRIVILEGED, arg_privileged); */ + /* SET_FLAG(arg_mount_settings, MOUNT_PRIVILEGED, arg_runtime_scope == RUNTIME_SCOPE_SYSTEM); */ SET_FLAG(arg_mount_settings, MOUNT_PRIVILEGED, arg_userns_mode != USER_NAMESPACE_MANAGED); SET_FLAG(arg_mount_settings, MOUNT_USE_USERNS, arg_userns_mode != USER_NAMESPACE_NO); @@ -1657,8 +1522,8 @@ static int verify_arguments(void) { if (arg_private_network) SET_FLAG(arg_mount_settings, MOUNT_APPLY_APIVFS_NETNS, arg_private_network); - if (!arg_privileged && arg_userns_mode != USER_NAMESPACE_MANAGED) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unprivileged operation requires managed user namespaces, as otherwise no UID range can be acquired."); + if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM && arg_userns_mode != USER_NAMESPACE_MANAGED) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User-scoped operation requires managed user namespaces, as otherwise no UID range can be acquired."); if (arg_userns_mode == USER_NAMESPACE_MANAGED && !arg_private_network) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Managed user namespace operation requires private networking, as otherwise /sys/ may not be mounted."); @@ -1668,7 +1533,7 @@ static int verify_arguments(void) { if (!(arg_clone_ns_flags & CLONE_NEWPID) || !(arg_clone_ns_flags & CLONE_NEWUTS)) { - arg_register = false; + arg_register = 0; if (arg_start_mode != START_PID1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--boot cannot be used without namespacing."); } @@ -2123,35 +1988,56 @@ static int setup_resolv_conf(const char *dest) { return 0; } -static int setup_boot_id(void) { - _cleanup_(unlink_and_freep) char *from = NULL; - _cleanup_free_ char *path = NULL; - sd_id128_t rnd = SD_ID128_NULL; - const char *to; +static int setup_boot_id_file(const char *directory) { + _cleanup_free_ char *p = NULL; + sd_id128_t rnd; int r; - /* Generate a new randomized boot ID, so that each boot-up of the container gets a new one */ + assert(directory); - r = tempfn_random_child("/run", "proc-sys-kernel-random-boot-id", &path); - if (r < 0) - return log_error_errno(r, "Failed to generate random boot ID path: %m"); + /* Generate a new randomized boot ID, so that each boot-up of the container gets a new one. We create + * the backing file here in the outer child already, since /run/host/ is mounted read-only by the time + * the inner child runs. We intentionally do not unlink it: bind mounts of unlinked files cannot be + * replicated to other mount namespaces (both the old and new mount APIs fail with ENOENT). Since + * mount_private_apivfs() needs to replicate submounts like boot_id when setting up a fresh /proc + * instance, the backing file must remain on disk. It lives in /run/host/ which is cleaned up on + * shutdown anyway. */ + + p = path_join(directory, "/run/host/proc-sys-kernel-random-boot-id"); + if (!p) + return log_oom(); r = sd_id128_randomize(&rnd); if (r < 0) return log_error_errno(r, "Failed to generate random boot id: %m"); - r = id128_write(path, ID128_FORMAT_UUID, rnd); + r = id128_write(p, ID128_FORMAT_UUID, rnd); if (r < 0) return log_error_errno(r, "Failed to write boot id: %m"); - from = TAKE_PTR(path); - to = "/proc/sys/kernel/random/boot_id"; + return userns_lchown(p, 0, 0); +} + +static int setup_boot_id(void) { + int r; - r = mount_nofollow_verbose(LOG_ERR, from, to, NULL, MS_BIND, NULL); + r = mount_nofollow_verbose( + LOG_ERR, + "/run/host/proc-sys-kernel-random-boot-id", + "/proc/sys/kernel/random/boot_id", + /* fstype= */ NULL, + MS_BIND, + /* options= */ NULL); if (r < 0) return r; - return mount_nofollow_verbose(LOG_ERR, NULL, to, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL); + return mount_nofollow_verbose( + LOG_ERR, + /* what= */ NULL, + "/proc/sys/kernel/random/boot_id", + /* fstype= */ NULL, + MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, + /* options= */ NULL); } static int bind_mount_devnode(const char *from, const char *to) { @@ -2210,8 +2096,9 @@ static int copy_devnode_one(const char *dest, const char *node, bool check) { log_debug_errno(errno, "Device node %s does not exist, ignoring.", from); return 0; } - if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "%s is not a device node.", from); + r = stat_verify_device_node(&st); + if (r < 0) + return log_error_errno(r, "'%s' is not a device node.", from); /* Create the parent directory of the device node. Here, we assume that the path has at most one * subdirectory under /dev/, e.g. /dev/net/tun. */ @@ -2531,38 +2418,44 @@ static int setup_credentials(const char *root) { return mount_nofollow_verbose(LOG_ERR, NULL, q, NULL, MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, "mode=0500"); } -static int setup_kmsg(int fd_inner_socket) { - _cleanup_(unlink_and_freep) char *from = NULL; - _cleanup_free_ char *fifo = NULL; - _cleanup_close_ int fd = -EBADF; - int r; +static int setup_kmsg_fifo(const char *directory) { + _cleanup_free_ char *p = NULL; - assert(fd_inner_socket >= 0); + assert(directory); + + p = path_join(directory, "/run/host/proc-kmsg"); + if (!p) + return log_oom(); BLOCK_WITH_UMASK(0000); - /* We create the kmsg FIFO as a temporary file in /run, but immediately delete it after bind mounting it to - * /proc/kmsg. While FIFOs on the reading side behave very similar to /proc/kmsg, their writing side behaves - * differently from /dev/kmsg in that writing blocks when nothing is reading. In order to avoid any problems - * with containers deadlocking due to this we simply make /dev/kmsg unavailable to the container. */ + if (mkfifo(p, 0600) < 0) + return log_error_errno(errno, "mkfifo() for /run/host/proc-kmsg failed: %m"); - r = tempfn_random_child("/run", "proc-kmsg", &fifo); - if (r < 0) - return log_error_errno(r, "Failed to generate kmsg path: %m"); + return userns_lchown(p, 0, 0); +} + +static int setup_kmsg(int fd_inner_socket) { + _cleanup_close_ int fd = -EBADF; + int r; - if (mkfifo(fifo, 0600) < 0) - return log_error_errno(errno, "mkfifo() for /run/kmsg failed: %m"); + assert(fd_inner_socket >= 0); - from = TAKE_PTR(fifo); + /* We bind mount the kmsg FIFO (created in the outer child) to /proc/kmsg. While FIFOs on the reading + * side behave very similar to /proc/kmsg, their writing side behaves differently from /dev/kmsg in + * that writing blocks when nothing is reading. In order to avoid any problems with containers + * deadlocking due to this we simply make /dev/kmsg unavailable to the container. */ - r = mount_nofollow_verbose(LOG_ERR, from, "/proc/kmsg", NULL, MS_BIND, NULL); + r = mount_nofollow_verbose(LOG_ERR, "/run/host/proc-kmsg", "/proc/kmsg", NULL, MS_BIND, NULL); if (r < 0) return r; - fd = open(from, O_RDWR|O_NONBLOCK|O_CLOEXEC); + fd = open("/run/host/proc-kmsg", O_RDWR|O_NONBLOCK|O_CLOEXEC); if (fd < 0) return log_error_errno(errno, "Failed to open fifo: %m"); + /* NB: We intentionally do not unlink the backing FIFO. See setup_boot_id_file() for details. */ + /* Store away the fd in the socket, so that it stays open as long as we run the child */ r = send_one_fd(fd_inner_socket, fd, 0); if (r < 0) @@ -2981,6 +2874,7 @@ static int wait_for_container(PidRef *pid, ContainerStatus *container) { int r; assert(pidref_is_set(pid)); + assert(container); r = pidref_wait_for_terminate(pid, &status); if (r < 0) @@ -3182,7 +3076,7 @@ static int determine_names(void) { if (arg_machine) { _cleanup_(image_unrefp) Image *i = NULL; - r = image_find(arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, + r = image_find(arg_runtime_scope, IMAGE_MACHINE, arg_machine, NULL, &i); if (r == -ENOENT) return log_error_errno(r, "No image for machine '%s'.", arg_machine); @@ -3623,7 +3517,7 @@ static int inner_child( } else #endif { - r = setup_seccomp(arg_caps_retain, arg_syscall_allow_list, arg_syscall_deny_list); + r = setup_seccomp(arg_caps_retain, arg_syscall_allow_list, arg_syscall_deny_list, arg_restrict_address_families, arg_restrict_address_families_is_allowlist); if (r < 0) return r; } @@ -3859,9 +3753,10 @@ static int setup_notify_child(const void *directory) { return TAKE_FD(fd); } -static int setup_unix_export_dir_outside(char **ret) { +static int setup_unix_export_dir_outside(const char *runtime_dir, char **ret) { int r; + assert(runtime_dir); assert(ret); if (arg_userns_mode == USER_NAMESPACE_MANAGED) { @@ -3870,7 +3765,7 @@ static int setup_unix_export_dir_outside(char **ret) { } _cleanup_free_ char *p = NULL; - p = path_join("/run/systemd/nspawn/unix-export", arg_machine); + p = path_join(runtime_dir, "unix-export"); if (!p) return log_oom(); @@ -4375,6 +4270,14 @@ static int outer_child( (void) make_inaccessible_nodes(p, chown_uid, chown_uid); + r = setup_boot_id_file(directory); + if (r < 0) + return r; + + r = setup_kmsg_fifo(directory); + if (r < 0) + return r; + r = setup_unix_export_host_inside(directory, unix_export_path); if (r < 0) return r; @@ -4723,6 +4626,8 @@ static int nspawn_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t r static int setup_notify_parent(sd_event *event, int fd, PidRef *inner_child_pid, sd_event_source **notify_event_source) { int r; + assert(notify_event_source); + if (fd < 0) return 0; @@ -4783,8 +4688,13 @@ static int merge_settings(Settings *settings, const char *path) { } if ((arg_settings_mask & SETTING_EPHEMERAL) == 0 && - settings->ephemeral >= 0) - arg_ephemeral = settings->ephemeral; + settings->ephemeral >= 0) { + + if (!arg_settings_trusted) + log_warning("Ignoring ephemeral setting, file %s is not trusted.", path); + else + arg_ephemeral = settings->ephemeral; + } if ((arg_settings_mask & SETTING_DIRECTORY) == 0 && settings->root) { @@ -4953,8 +4863,13 @@ static int merge_settings(Settings *settings, const char *path) { } if ((arg_settings_mask & SETTING_BIND_USER) == 0 && - !strv_isempty(settings->bind_user)) - strv_free_and_replace(arg_bind_user, settings->bind_user); + !strv_isempty(settings->bind_user)) { + + if (!arg_settings_trusted) + log_warning("Ignoring bind user setting, file %s is not trusted.", path); + else + strv_free_and_replace(arg_bind_user, settings->bind_user); + } if (!FLAGS_SET(arg_settings_mask, SETTING_BIND_USER_SHELL) && settings->bind_user_shell_set) { @@ -5091,6 +5006,12 @@ static int merge_settings(Settings *settings, const char *path) { settings->suppress_sync >= 0) arg_suppress_sync = settings->suppress_sync; + if (!FLAGS_SET(arg_settings_mask, SETTING_RESTRICT_ADDRESS_FAMILIES) && + (settings->restrict_address_families || settings->restrict_address_families_is_allowlist)) { + set_free_and_replace(arg_restrict_address_families, settings->restrict_address_families); + arg_restrict_address_families_is_allowlist = settings->restrict_address_families_is_allowlist; + } + /* The following properties can only be set through the OCI settings logic, not from the command line, hence we * don't consult arg_settings_mask for them. */ @@ -5134,7 +5055,7 @@ static int load_settings(void) { _SD_PATH_INVALID, }; - const uint64_t *q = arg_privileged ? lookup_dir_system : lookup_dir_user; + const uint64_t *q = arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? lookup_dir_system : lookup_dir_user; for (; *q != _SD_PATH_INVALID; q++) { _cleanup_free_ char *cd = NULL; r = sd_path_lookup(*q, "systemd/nspawn", &cd); @@ -5228,6 +5149,7 @@ static int load_oci_bundle(void) { } static int run_container( + const char *runtime_dir, const char *directory, int mount_fd, DissectedImage *dissected_image, @@ -5266,7 +5188,7 @@ static int run_container( assert_se(sigaddset(&mask_chld, SIGCHLD) == 0); /* Set up the unix export host directory on the host first */ - r = setup_unix_export_dir_outside(&unix_export_host_dir); + r = setup_unix_export_dir_outside(runtime_dir, &unix_export_host_dir); if (r < 0) return r; @@ -5489,7 +5411,7 @@ static int run_container( assert(child_netns_fd < 0); child_netns_fd = receive_one_fd(fd_inner_socket_pair[0], 0); if (child_netns_fd < 0) - return log_error_errno(r, "Failed to receive child network namespace: %m"); + return log_error_errno(child_netns_fd, "Failed to receive child network namespace: %m"); } r = move_network_interfaces(child_netns_fd, arg_network_interfaces); @@ -5565,7 +5487,7 @@ static int run_container( /* Registration always happens on the system bus */ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *system_bus = NULL; - if (arg_register || (arg_privileged && !arg_keep_unit)) { + if (arg_register != 0 || (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM && !arg_keep_unit)) { r = sd_bus_default_system(&system_bus); if (r < 0) return log_error_errno(r, "Failed to open system bus: %m"); @@ -5580,8 +5502,8 @@ static int run_container( _cleanup_(sd_bus_flush_close_unrefp) sd_bus *user_bus = NULL; _cleanup_(sd_bus_unrefp) sd_bus *runtime_bus = NULL; - if (arg_register || !arg_keep_unit) { - if (arg_privileged) + if (arg_register != 0 || !arg_keep_unit) { + if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) runtime_bus = sd_bus_ref(system_bus); else { r = sd_bus_default_user(&user_bus); @@ -5648,41 +5570,28 @@ static int run_container( scope_allocated = true; } - bool registered_system = false, registered_runtime = false; - if (arg_register) { - r = register_machine( - system_bus, - arg_machine, - pid, - arg_directory, - arg_uuid, - ifi, - arg_container_service_name); - if (r < 0) { - if (arg_privileged) /* if privileged the request to register definitely failed */ - return r; - - log_notice_errno(r, "Failed to register machine in system context, will try in user context."); - } else - registered_system = true; - - if (!arg_privileged) { - r = register_machine( - runtime_bus, - arg_machine, - pid, - arg_directory, - arg_uuid, - ifi, - arg_container_service_name); - if (r < 0) { - if (!registered_system) /* neither registration worked: fail */ - return r; + MachineRegistrationContext machine_ctx = { + .scope = arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? RUNTIME_SCOPE_SYSTEM : _RUNTIME_SCOPE_INVALID, + .system_bus = system_bus, + .user_bus = runtime_bus, + }; + if (arg_register != 0) { + const MachineRegistration reg = { + .name = arg_machine, + .id = arg_uuid, + .service = arg_container_service_name, + .class = "container", + .pidref = pid, + .root_directory = arg_directory, + .local_ifindex = ifi, + }; - log_notice_errno(r, "Failed to register machine in user context, but succeeded in system context, will proceed."); - } else - registered_runtime = true; - } + r = register_machine_with_fallback_and_log( + &machine_ctx, + ®, + /* graceful= */ arg_register < 0); + if (r < 0) + return r; } if (arg_keep_unit && (arg_slice || arg_property)) @@ -5894,10 +5803,7 @@ static int run_container( r = wait_for_container(pid, &container_status); /* Tell machined that we are gone. */ - if (registered_system) - (void) unregister_machine(system_bus, arg_machine); - if (registered_runtime) - (void) unregister_machine(runtime_bus, arg_machine); + unregister_machine_with_fallback_and_log(&machine_ctx, arg_machine); if (r < 0) /* We failed to wait for the container, or the container exited abnormally. */ @@ -6048,28 +5954,22 @@ static int cant_be_in_netns(void) { return 0; } -static void initialize_defaults(void) { - arg_privileged = getuid() == 0; - - /* If running unprivileged default to systemd-nsresourced operation */ - arg_userns_mode = arg_privileged ? USER_NAMESPACE_NO : USER_NAMESPACE_MANAGED; - - /* Imply private networking for unprivileged operation, since kernel otherwise refuses mounting sysfs */ - arg_private_network = !arg_privileged; -} - -static void cleanup_propagation_and_export_directories(void) { - const char *p; +static void cleanup_propagation_and_export_directories(const char *runtime_dir) { + _cleanup_free_ char *p = NULL; - if (!arg_machine || !arg_privileged) + if (!runtime_dir || arg_userns_mode == USER_NAMESPACE_MANAGED) return; - p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); - (void) rm_rf(p, REMOVE_ROOT); + p = path_join("/run/systemd/nspawn/propagate", arg_machine); + if (p) + (void) rm_rf(p, REMOVE_ROOT); - p = strjoina("/run/systemd/nspawn/unix-export/", arg_machine); - (void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW); - (void) rmdir(p); + free(p); + p = path_join(runtime_dir, "unix-export"); + if (p) { + (void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW); + (void) rmdir(p); + } } static int do_cleanup(void) { @@ -6082,7 +5982,16 @@ static int do_cleanup(void) { if (r < 0) return r; - cleanup_propagation_and_export_directories(); + _cleanup_free_ char *subdir = path_join("systemd/nspawn", arg_machine); + if (!subdir) + return log_oom(); + + _cleanup_free_ char *runtime_dir = NULL; + r = runtime_directory(arg_runtime_scope, subdir, &runtime_dir); + if (r < 0) + return r; + + cleanup_propagation_and_export_directories(runtime_dir); return 0; } @@ -6102,11 +6011,12 @@ static int run(int argc, char *argv[]) { _cleanup_(sd_netlink_unrefp) sd_netlink *nfnl = NULL; _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *nsresource_link = NULL, *mountfsd_link = NULL; + _cleanup_free_ char *runtime_dir = NULL, *subdir = NULL; + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir_destroy = NULL; + _cleanup_(fork_notify_terminate) PidRef journal_remote_pidref = PIDREF_NULL; log_setup(); - initialize_defaults(); - r = parse_argv(argc, argv); if (r <= 0) goto finish; @@ -6114,9 +6024,9 @@ static int run(int argc, char *argv[]) { if (arg_cleanup) return do_cleanup(); - (void) dlopen_libmount(); - (void) dlopen_libseccomp(); - (void) dlopen_libselinux(); + (void) dlopen_libmount(LOG_DEBUG); + (void) dlopen_libseccomp(LOG_DEBUG); + (void) dlopen_libselinux(LOG_DEBUG); r = cg_has_legacy(); if (r < 0) @@ -6151,6 +6061,12 @@ static int run(int argc, char *argv[]) { if (r < 0) goto finish; + if (!FLAGS_SET(arg_settings_mask, SETTING_RESTRICT_ADDRESS_FAMILIES) && !arg_restrict_address_families) + log_notice("Note: in a future version of systemd-nspawn the default set of permitted socket address" + " families will be restricted to AF_INET, AF_INET6 and AF_UNIX." + " Use --restrict-address-families= to configure the set of permitted socket address" + " families, or set RestrictAddressFamilies= in a .nspawn file."); + /* If we're not unsharing the network namespace and are unsharing the user namespace, we won't have * permissions to bind ports in the container, so let's drop the CAP_NET_BIND_SERVICE capability to * indicate that. */ @@ -6212,7 +6128,7 @@ static int run(int argc, char *argv[]) { r = mountfsd_connect(&mountfsd_link); if (r < 0) { - log_error_errno(r, "Failed to connect to mountsd: %m"); + log_error_errno(r, "Failed to connect to mountfsd: %m"); goto finish; } @@ -6261,7 +6177,7 @@ static int run(int argc, char *argv[]) { r = create_ephemeral_snapshot( arg_directory, - arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, + arg_runtime_scope, arg_read_only, &tree_global_lock, &tree_local_lock, @@ -6282,10 +6198,10 @@ static int run(int argc, char *argv[]) { goto finish; r = image_path_lock( - arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, + arg_runtime_scope, arg_directory, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, - arg_privileged ? &tree_global_lock : NULL, + arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? &tree_global_lock : NULL, &tree_local_lock); if (r == -EBUSY) { log_error_errno(r, "Directory tree %s is currently busy.", arg_directory); @@ -6413,10 +6329,10 @@ static int run(int argc, char *argv[]) { /* Always take an exclusive lock on our own ephemeral copy. */ r = image_path_lock( - arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, + arg_runtime_scope, np, LOCK_EX|LOCK_NB, - arg_privileged ? &tree_global_lock : NULL, + arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? &tree_global_lock : NULL, &tree_local_lock); if (r < 0) { log_error_errno(r, "Failed to create image lock: %m"); @@ -6441,10 +6357,10 @@ static int run(int argc, char *argv[]) { remove_image = true; } else { r = image_path_lock( - arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, + arg_runtime_scope, arg_image, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, - arg_privileged ? &tree_global_lock : NULL, + arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? &tree_global_lock : NULL, &tree_local_lock); if (r == -EBUSY) { log_error_errno(r, "Disk image %s is currently busy.", arg_image); @@ -6470,7 +6386,7 @@ static int run(int argc, char *argv[]) { if (arg_userns_mode != USER_NAMESPACE_MANAGED) { r = loop_device_make_by_path( arg_image, - arg_read_only ? O_RDONLY : O_RDWR, + arg_read_only ? O_RDONLY : -1, /* sector_size= */ UINT32_MAX, FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN, LOCK_SH, @@ -6587,6 +6503,22 @@ static int run(int argc, char *argv[]) { } else assert_not_reached(); + subdir = path_join("systemd/nspawn", arg_machine); + if (!subdir) { + r = log_oom(); + goto finish; + } + + r = runtime_directory_make( + arg_runtime_scope, + subdir, + &runtime_dir, + &runtime_dir_destroy); + if (r < 0) { + log_error_errno(r, "Failed to create runtime directory: %m"); + goto finish; + } + /* Create a temporary place to mount stuff. */ r = mkdtemp_malloc("/tmp/nspawn-root-XXXXXX", &rootdir); if (r < 0) { @@ -6633,8 +6565,61 @@ static int run(int argc, char *argv[]) { expose_args.nfnl = nfnl; } + if (arg_forward_journal) { + _cleanup_free_ char *socket_path = path_join(runtime_dir, "journal-remote-socket"); + if (!socket_path) { + r = log_oom(); + goto finish; + } + + union sockaddr_union sa; + r = sockaddr_un_set_path(&sa.un, socket_path); + if (r < 0) { + log_error_errno(r, "Failed to set AF_UNIX path to '%s': %m", socket_path); + goto finish; + } + + (void) sockaddr_un_unlink(&sa.un); + + r = fork_journal_remote( + socket_path, + arg_forward_journal, + arg_forward_journal_max_use, + arg_forward_journal_keep_free, + arg_forward_journal_max_file_size, + arg_forward_journal_max_files, + &journal_remote_pidref); + if (r < 0) + goto finish; + + CustomMount *cm = custom_mount_add(&arg_custom_mounts, &arg_n_custom_mounts, CUSTOM_MOUNT_BIND); + if (!cm) { + r = log_oom(); + goto finish; + } + + cm->source = TAKE_PTR(socket_path); + cm->read_only = true; + cm->destination = strdup(NSPAWN_JOURNAL_SOCKET_PATH); + if (!cm->destination) { + r = log_oom(); + goto finish; + } + + r = machine_credential_add(&arg_credentials, "journal.forward_to_socket", NSPAWN_JOURNAL_SOCKET_PATH, SIZE_MAX); + if (r == -EEXIST) { + log_error_errno(r, "Credential 'journal.forward_to_socket' already set via --set-credential=, refusing --forward-journal=."); + goto finish; + } + if (r < 0) { + log_error_errno(r, "Failed to add 'journal.forward_to_socket' credential: %m"); + goto finish; + } + } + for (;;) { r = run_container( + runtime_dir, rootdir, mount_fd, dissected_image, @@ -6675,18 +6660,7 @@ static int run(int argc, char *argv[]) { log_warning_errno(errno, "Can't remove image file '%s', ignoring: %m", arg_image); } - if (arg_machine && arg_userns_mode != USER_NAMESPACE_MANAGED) { - const char *p; - - p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); - (void) rm_rf(p, REMOVE_ROOT); - - p = strjoina("/run/systemd/nspawn/unix-export/", arg_machine); - (void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW); - (void) rmdir(p); - } - - cleanup_propagation_and_export_directories(); + cleanup_propagation_and_export_directories(runtime_dir); expose_port_flush(nfnl, arg_expose_ports, AF_INET, &expose_args.address4); expose_port_flush(nfnl, arg_expose_ports, AF_INET6, &expose_args.address6); diff --git a/src/nsresourced/bpf/userns-restrict/meson.build b/src/nsresourced/bpf/userns-restrict/meson.build deleted file mode 100644 index e6bcc51add313..0000000000000 --- a/src/nsresourced/bpf/userns-restrict/meson.build +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -if conf.get('HAVE_VMLINUX_H') != 1 - subdir_done() -endif - -userns_restrict_bpf_o_unstripped = custom_target( - input : 'userns-restrict.bpf.c', - output : 'userns-restrict.bpf.unstripped.o', - command : bpf_o_unstripped_cmd, - depends : vmlinux_h_dependency) - -userns_restrict_bpf_o = custom_target( - input : userns_restrict_bpf_o_unstripped, - output : 'userns-restrict.bpf.o', - command : bpf_o_cmd) - -userns_restrict_skel_h = custom_target( - input : userns_restrict_bpf_o, - output : 'userns-restrict.skel.h', - command : skel_h_cmd, - capture : true) - -generated_sources += userns_restrict_skel_h diff --git a/src/nsresourced/meson.build b/src/nsresourced/meson.build index 6b6ae1558c0f7..881fd911e418a 100644 --- a/src/nsresourced/meson.build +++ b/src/nsresourced/meson.build @@ -4,8 +4,6 @@ if conf.get('ENABLE_NSRESOURCED') != 1 subdir_done() endif -subdir('bpf/userns-restrict') - systemd_nsresourced_sources = files( 'nsresourced-manager.c', 'nsresourced.c', @@ -21,29 +19,26 @@ test_userns_restrict_sources = files( 'test-userns-restrict.c' ) -if conf.get('HAVE_VMLINUX_H') == 1 - systemd_nsresourced_sources += userns_restrict_skel_h - systemd_nsresourcework_sources += userns_restrict_skel_h - test_userns_restrict_sources += userns_restrict_skel_h -endif - executables += [ libexec_template + { 'name' : 'systemd-nsresourced', 'sources' : systemd_nsresourced_sources, 'extract' : systemd_nsresourced_extract_sources, 'dependencies' : threads, + 'bpf_programs': ['userns-restrict'], }, libexec_template + { 'name' : 'systemd-nsresourcework', 'sources' : systemd_nsresourcework_sources, 'dependencies' : threads, 'objects' : ['systemd-nsresourced'], + 'bpf_programs': ['userns-restrict'], }, test_template + { 'sources' : test_userns_restrict_sources, 'conditions' : ['HAVE_VMLINUX_H'], 'objects' : ['systemd-nsresourced'], + 'bpf_programs': ['userns-restrict'], }, ] diff --git a/src/nsresourced/nsresourced-manager.c b/src/nsresourced/nsresourced-manager.c index 664cb2d1a2ad1..cceaa9c378e74 100644 --- a/src/nsresourced/nsresourced-manager.c +++ b/src/nsresourced/nsresourced-manager.c @@ -8,7 +8,7 @@ #include "bpf-dlopen.h" #if HAVE_VMLINUX_H #include "bpf-link.h" -#include "bpf/userns-restrict/userns-restrict-skel.h" +#include "userns-restrict-skel.h" #endif #include "build-path.h" #include "common-signal.h" @@ -89,6 +89,8 @@ int manager_new(Manager **ret) { _cleanup_(manager_freep) Manager *m = NULL; int r; + assert(ret); + m = new(Manager, 1); if (!m) return -ENOMEM; diff --git a/src/nsresourced/userns-registry.c b/src/nsresourced/userns-registry.c index 371a35086f424..50efcd1153a5d 100644 --- a/src/nsresourced/userns-registry.c +++ b/src/nsresourced/userns-registry.c @@ -65,14 +65,7 @@ void delegated_userns_info_done(DelegatedUserNamespaceInfo *info) { info->n_ancestor_userns = 0; } -void delegated_userns_info_done_many(DelegatedUserNamespaceInfo infos[], size_t n) { - assert(infos || n == 0); - - FOREACH_ARRAY(info, infos, n) - delegated_userns_info_done(info); - - free(infos); -} +static DEFINE_ARRAY_FREE_FUNC(delegated_userns_info_free_array, DelegatedUserNamespaceInfo, delegated_userns_info_done); UserNamespaceInfo* userns_info_new(void) { UserNamespaceInfo *info = new(UserNamespaceInfo, 1); @@ -97,7 +90,7 @@ UserNamespaceInfo *userns_info_free(UserNamespaceInfo *userns) { free(userns->cgroups); free(userns->name); - delegated_userns_info_done_many(userns->delegates, userns->n_delegates); + delegated_userns_info_free_array(userns->delegates, userns->n_delegates); strv_free(userns->netifs); @@ -154,12 +147,10 @@ static int dispatch_delegates_array(const char *name, sd_json_variant *variant, size_t n = 0; int r; - CLEANUP_ARRAY(delegates, n, delegated_userns_info_done_many); + CLEANUP_ARRAY(delegates, n, delegated_userns_info_free_array); if (sd_json_variant_is_null(variant)) { - delegated_userns_info_done_many(info->delegates, info->n_delegates); - info->delegates = NULL; - info->n_delegates = 0; + CLEANUP_ARRAY(info->delegates, info->n_delegates, delegated_userns_info_free_array); return 0; } @@ -199,7 +190,7 @@ static int dispatch_delegates_array(const char *name, sd_json_variant *variant, n++; } - delegated_userns_info_done_many(info->delegates, info->n_delegates); + delegated_userns_info_free_array(info->delegates, info->n_delegates); info->delegates = TAKE_PTR(delegates); info->n_delegates = n; diff --git a/src/nsresourced/userns-registry.h b/src/nsresourced/userns-registry.h index f08b238861ae4..77ff2d6d20760 100644 --- a/src/nsresourced/userns-registry.h +++ b/src/nsresourced/userns-registry.h @@ -24,7 +24,6 @@ typedef struct DelegatedUserNamespaceInfo { } void delegated_userns_info_done(DelegatedUserNamespaceInfo *info); -void delegated_userns_info_done_many(DelegatedUserNamespaceInfo infos[], size_t n); typedef struct UserNamespaceInfo { uid_t owner; diff --git a/src/nsresourced/userns-restrict.c b/src/nsresourced/userns-restrict.c index c0d7f8a82daec..11bb2e7d8fd36 100644 --- a/src/nsresourced/userns-restrict.c +++ b/src/nsresourced/userns-restrict.c @@ -3,7 +3,7 @@ #include #if HAVE_VMLINUX_H -#include "bpf/userns-restrict/userns-restrict-skel.h" +#include "userns-restrict-skel.h" #endif #include "bpf-dlopen.h" @@ -68,7 +68,7 @@ int userns_restrict_install( if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-lsm not supported, can't lock down user namespace."); - r = dlopen_bpf(); + r = dlopen_bpf(LOG_DEBUG); if (r < 0) return r; @@ -151,7 +151,7 @@ int userns_restrict_install( link = NULL; } else { linked = true; - log_info("userns-restrict BPF-LSM program %s already attached.", ps->name); + log_debug("userns-restrict BPF-LSM program %s already attached.", ps->name); } } diff --git a/src/nss-myhostname/nss-myhostname.c b/src/nss-myhostname/nss-myhostname.c index ed470ed298cc4..83d968ff0b58c 100644 --- a/src/nss-myhostname/nss-myhostname.c +++ b/src/nss-myhostname/nss-myhostname.c @@ -39,7 +39,7 @@ enum nss_status _nss_myhostname_gethostbyname4_r( _cleanup_free_ char *hn = NULL; const char *canonical = NULL; int n_addresses = 0; - uint32_t local_address_ipv4; + uint32_t local_address_ipv4 = 0; size_t l, idx, ms; char *r_name; @@ -230,7 +230,7 @@ static enum nss_status fill_in_hostent( if (additional) { r_alias = buffer + idx; memcpy(r_alias, additional, l_additional+1); - idx += ALIGN(l_additional+1); + assert_se(INC_SAFE(&idx, ALIGN(l_additional+1))); } /* Second, create aliases array */ @@ -238,10 +238,10 @@ static enum nss_status fill_in_hostent( if (additional) { ((char**) r_aliases)[0] = r_alias; ((char**) r_aliases)[1] = NULL; - idx += 2*sizeof(char*); + assert_se(INC_SAFE(&idx, 2 * sizeof(char*))); } else { ((char**) r_aliases)[0] = NULL; - idx += sizeof(char*); + assert_se(INC_SAFE(&idx, sizeof(char*))); } /* Third, add addresses */ @@ -258,14 +258,14 @@ static enum nss_status fill_in_hostent( } assert(i == c); - idx += c*ALIGN(alen); + assert_se(INC_SAFE(&idx, c*ALIGN(alen))); } else if (af == AF_INET) { *(uint32_t*) r_addr = local_address_ipv4; - idx += ALIGN(alen); + assert_se(INC_SAFE(&idx, ALIGN(alen))); } else if (socket_ipv6_is_enabled()) { memcpy(r_addr, LOCALADDRESS_IPV6, FAMILY_ADDRESS_SIZE(AF_INET6)); - idx += ALIGN(alen); + assert_se(INC_SAFE(&idx, ALIGN(alen))); } /* Fourth, add address pointer array */ @@ -277,15 +277,15 @@ static enum nss_status fill_in_hostent( ((char**) r_addr_list)[i] = r_addr + i*ALIGN(alen); ((char**) r_addr_list)[i] = NULL; - idx += (c+1) * sizeof(char*); + assert_se(INC_SAFE(&idx, (c+1) * sizeof(char*))); } else if (af == AF_INET || socket_ipv6_is_enabled()) { ((char**) r_addr_list)[0] = r_addr; ((char**) r_addr_list)[1] = NULL; - idx += 2 * sizeof(char*); + assert_se(INC_SAFE(&idx, 2 * sizeof(char*))); } else { ((char**) r_addr_list)[0] = NULL; - idx += sizeof(char*); + assert_se(INC_SAFE(&idx, sizeof(char*))); } /* Verify the size matches */ diff --git a/src/nss-resolve/nss-resolve.c b/src/nss-resolve/nss-resolve.c index ea60727e906d9..2bcfca71e5bd8 100644 --- a/src/nss-resolve/nss-resolve.c +++ b/src/nss-resolve/nss-resolve.c @@ -50,6 +50,8 @@ static int connect_to_resolved(sd_varlink **ret) { _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; int r; + assert(ret); + r = sd_varlink_connect_address(&link, "/run/systemd/resolve/io.systemd.Resolve"); if (r < 0) return r; diff --git a/src/nss-systemd/nss-systemd.c b/src/nss-systemd/nss-systemd.c index 6ed97f31a68f9..0689175a53c11 100644 --- a/src/nss-systemd/nss-systemd.c +++ b/src/nss-systemd/nss-systemd.c @@ -149,6 +149,7 @@ static enum nss_status copy_synthesized_passwd( assert(dest); assert(src); + assert(errnop); assert(src->pw_name); assert(src->pw_passwd); assert(src->pw_gecos); @@ -191,6 +192,7 @@ static enum nss_status copy_synthesized_spwd( assert(dest); assert(src); + assert(errnop); assert(src->sp_namp); assert(src->sp_pwdp); @@ -223,6 +225,7 @@ static enum nss_status copy_synthesized_group( assert(dest); assert(src); + assert(errnop); assert(src->gr_name); assert(src->gr_passwd); assert(src->gr_mem); @@ -259,6 +262,7 @@ static enum nss_status copy_synthesized_sgrp( assert(dest); assert(src); + assert(errnop); assert(src->sg_namp); assert(src->sg_passwd); assert(src->sg_adm); diff --git a/src/nss-systemd/userdb-glue.c b/src/nss-systemd/userdb-glue.c index 1d5e311ce8653..5bc89d5f9bb69 100644 --- a/src/nss-systemd/userdb-glue.c +++ b/src/nss-systemd/userdb-glue.c @@ -415,6 +415,9 @@ enum nss_status userdb_getgrgid( * string vector strv and stores amount of pointers in n and total * length of all contained strings including NUL bytes in len. */ static void nss_count_strv(char * const *strv, size_t *n, size_t *len) { + assert(n); + assert(len); + STRV_FOREACH(str, strv) { (*len) += sizeof(char*); /* space for array entry */ (*len) += strlen(*str) + 1; @@ -472,7 +475,9 @@ int nss_pack_group_record_shadow( assert(buffer); - p = buffer + sizeof(void*) * (n + 1); /* place member strings right after the ptr array */ + /* n already includes trailing NULL pointers from nss_count_strv(), unlike the + * non-shadow nss_pack_group_record() where n does not include them. */ + p = buffer + sizeof(void*) * n; array = (char**) buffer; /* place ptr array at beginning of buffer, under assumption buffer is aligned */ sgrp->sg_mem = array; diff --git a/src/oom/oomctl.c b/src/oom/oomctl.c index d9098de0ebf1e..51dfe1f1a40a3 100644 --- a/src/oom/oomctl.c +++ b/src/oom/oomctl.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-bus.h" #include "alloc-util.h" @@ -9,16 +7,19 @@ #include "bus-error.h" #include "bus-locator.h" #include "bus-message-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "pretty-print.h" #include "verbs.h" static PagerFlags arg_pager_flags = 0; -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; pager_open(arg_pager_flags); @@ -27,26 +28,46 @@ static int help(int argc, char *argv[], void *userdata) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%2$sManage or inspect the userspace OOM killer.%3$s\n" - "\n%4$sCommands:%5$s\n" - " dump Output the current state of systemd-oomd\n" - "\n%4$sOptions:%5$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - "\nSee the %6$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sManage or inspect the userspace OOM killer.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, ansi_highlight(), ansi_normal(), ansi_underline(), - ansi_normal(), - link); + ansi_normal()); + + r = table_print_or_warn(verbs); + if (r < 0) + return r; + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int dump_state(int argc, char *argv[], void *userdata) { +VERB_COMMON_HELP_HIDDEN(help); + +VERB(verb_dump_state, "dump", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Output the current state of systemd-oomd"); +static int verb_dump_state(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -65,64 +86,43 @@ static int dump_state(int argc, char *argv[], void *userdata) { return bus_message_dump_fd(reply); } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(0, NULL, NULL); + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: - return version(); + OPTION_COMMON_VERSION: + return version(); - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; } + *ret_args = option_parser_get_args(&state); return 1; } static int run(int argc, char* argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "dump", VERB_ANY, 1, VERB_DEFAULT, dump_state }, - {} - }; - int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/oom/oomd-manager.c b/src/oom/oomd-manager.c index b2142dd43cf33..382a246c2dddb 100644 --- a/src/oom/oomd-manager.c +++ b/src/oom/oomd-manager.c @@ -422,7 +422,7 @@ static int monitor_swap_contexts_handler(sd_event_source *s, uint64_t usec, void if (r < 0) log_error_errno(r, "Failed to select any cgroups based on swap: %m"); else { - if (selected && r > 0) { + if (selected && r > 0) log_notice("Marked %s for killing due to memory used (%"PRIu64") / total (%"PRIu64") and " "swap used (%"PRIu64") / total (%"PRIu64") being more than " PERMYRIAD_AS_PERCENT_FORMAT_STR, @@ -430,7 +430,6 @@ static int monitor_swap_contexts_handler(sd_event_source *s, uint64_t usec, void m->system_context.mem_used, m->system_context.mem_total, m->system_context.swap_used, m->system_context.swap_total, PERMYRIAD_AS_PERCENT_FORMAT_VAL(m->swap_used_limit_permyriad)); - } return 0; } } @@ -439,6 +438,8 @@ static int monitor_swap_contexts_handler(sd_event_source *s, uint64_t usec, void } static void clear_candidate_hashmapp(Manager **m) { + assert(m); + if (*m) hashmap_clear((*m)->monitored_mem_pressure_cgroup_contexts_candidates); } diff --git a/src/oom/oomd-util.c b/src/oom/oomd-util.c index 55e17df46f08c..c0e04041a7e6a 100644 --- a/src/oom/oomd-util.c +++ b/src/oom/oomd-util.c @@ -624,7 +624,7 @@ int oomd_select_by_swap_usage(Hashmap *h, uint64_t threshold_usage, OomdCGroupCo int oomd_cgroup_context_acquire(const char *path, OomdCGroupContext **ret) { _cleanup_(oomd_cgroup_context_unrefp) OomdCGroupContext *ctx = NULL; - _cleanup_free_ char *p = NULL, *val = NULL; + _cleanup_free_ char *p = NULL; bool is_root; int r; @@ -678,13 +678,9 @@ int oomd_cgroup_context_acquire(const char *path, OomdCGroupContext **ret) { else if (r < 0) return log_debug_errno(r, "Error getting memory.swap.current from %s: %m", path); - r = cg_get_keyed_attribute(path, "memory.stat", STRV_MAKE("pgscan"), &val); + r = cg_get_keyed_attribute_uint64(path, "memory.stat", "pgscan", &ctx->pgscan); if (r < 0) return log_debug_errno(r, "Error getting pgscan from memory.stat under %s: %m", path); - - r = safe_atou64(val, &ctx->pgscan); - if (r < 0) - return log_debug_errno(r, "Error converting pgscan value to uint64_t: %m"); } *ret = TAKE_PTR(ctx); diff --git a/src/oom/oomd.c b/src/oom/oomd.c index b1d3efb8f5733..54a3bb7a1d562 100644 --- a/src/oom/oomd.c +++ b/src/oom/oomd.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-event.h" #include "alloc-util.h" @@ -11,11 +9,13 @@ #include "cgroup-util.h" #include "daemon-util.h" #include "fileio.h" +#include "format-table.h" #include "log.h" #include "main-func.h" #include "oomd-conf.h" #include "oomd-manager.h" #include "oomd-manager-bus.h" +#include "options.h" #include "parse-util.h" #include "pretty-print.h" #include "psi-util.h" @@ -24,74 +24,60 @@ static bool arg_dry_run = false; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-oomd", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...]\n\n" - "Run the userspace out-of-memory (OOM) killer.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --dry-run Only print destructive actions instead of doing them\n" - " --bus-introspect=PATH Write D-Bus XML introspection data\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + "Run the userspace out-of-memory (OOM) killer.\n\n", + program_invocation_short_name); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_DRY_RUN, - ARG_BUS_INTROSPECT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "dry-run", no_argument, NULL, ARG_DRY_RUN }, - { "bus-introspect", required_argument, NULL, ARG_BUS_INTROSPECT }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_DRY_RUN: + OPTION_LONG("dry-run", NULL, + "Only print destructive actions instead of doing them"): arg_dry_run = true; break; - case ARG_BUS_INTROSPECT: + OPTION_LONG("bus-introspect", "PATH", + "Write D-Bus XML introspection data"): return bus_introspect_implementations( stdout, - optarg, + arg, BUS_IMPLEMENTATIONS(&manager_object, &log_control_object)); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); diff --git a/src/path/path-tool.c b/src/path/path-tool.c index 62eade3b05dfa..1920ff8d60028 100644 --- a/src/path/path-tool.c +++ b/src/path/path-tool.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-path.h" @@ -8,12 +7,15 @@ #include "alloc-util.h" #include "build.h" #include "errno-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "pretty-print.h" #include "sort-util.h" #include "string-util.h" +#include "strv.h" static const char *arg_suffix = NULL; static PagerFlags arg_pager_flags = 0; @@ -59,6 +61,7 @@ static const char* const path_table[_SD_PATH_MAX] = { [SD_PATH_USER_PUBLIC] = "user-public", [SD_PATH_USER_TEMPLATES] = "user-templates", [SD_PATH_USER_DESKTOP] = "user-desktop", + [SD_PATH_USER_PROJECTS] = "user-projects", [SD_PATH_SEARCH_BINARIES] = "search-binaries", [SD_PATH_SEARCH_BINARIES_DEFAULT] = "search-binaries-default", @@ -172,72 +175,57 @@ static int print_path(const char *n) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-path", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] [NAME...]\n" - "\n%sShow system and user paths.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --suffix=SUFFIX Suffix to append to paths\n" - " --no-pager Do not pipe output into a pager\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] [NAME...]\n\n" + "%sShow system and user paths.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_SUFFIX, - ARG_NO_PAGER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "suffix", required_argument, NULL, ARG_SUFFIX }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_SUFFIX: - arg_suffix = optarg; + OPTION_LONG("suffix", "SUFFIX", "Suffix to append to paths"): + arg_suffix = arg; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } @@ -246,15 +234,16 @@ static int run(int argc, char* argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - if (optind >= argc) + if (strv_isempty(args)) return list_paths(); - for (int i = optind; i < argc; i++) - RET_GATHER(r, print_path(argv[i])); + STRV_FOREACH(i, args) + RET_GATHER(r, print_path(*i)); return r; } diff --git a/src/pcrextend/meson.build b/src/pcrextend/meson.build index 3a8824eaa8444..f2f5f3b46e3e8 100644 --- a/src/pcrextend/meson.build +++ b/src/pcrextend/meson.build @@ -11,7 +11,7 @@ executables += [ ], 'sources' : files('pcrextend.c'), 'dependencies' : [ - libopenssl, + libopenssl_cflags, tpm2, ], }, diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index c80bf376fdce1..c92d3f981124a 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -1,17 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-json.h" #include "sd-messages.h" #include "sd-varlink.h" #include "alloc-util.h" #include "build.h" +#include "crypto-util.h" #include "efi-loader.h" #include "escape.h" +#include "format-table.h" #include "json-util.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pcrextend-util.h" #include "pretty-print.h" @@ -29,7 +30,7 @@ static char **arg_banks = NULL; static char *arg_file_system = NULL; static bool arg_machine_id = false; static bool arg_product_id = false; -static unsigned arg_pcr_index = UINT_MAX; +static uint32_t arg_pcr_mask = 0; static char *arg_nvpcr_name = NULL; static bool arg_varlink = false; static bool arg_early = false; @@ -42,127 +43,104 @@ STATIC_DESTRUCTOR_REGISTER(arg_nvpcr_name, freep); #define EXTENSION_STRING_SAFE_LIMIT 1024 -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-pcrextend", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%1$s [OPTIONS...] WORD\n" "%1$s [OPTIONS...] --file-system=PATH\n" "%1$s [OPTIONS...] --machine-id\n" "%1$s [OPTIONS...] --product-id\n" - "\n%5$sExtend a TPM2 PCR with boot phase, machine ID, or file system ID.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --bank=DIGEST Select TPM PCR bank (SHA1, SHA256)\n" - " --pcr=INDEX Select TPM PCR index (0…23)\n" - " --nvpcr=NAME Select TPM PCR mode nvindex name\n" - " --tpm2-device=PATH Use specified TPM2 device\n" - " --graceful Exit gracefully if no TPM2 device is found\n" - " --file-system=PATH Measure UUID/labels of file system into PCR 15\n" - " --machine-id Measure machine ID into PCR 15\n" - " --product-id Measure SMBIOS product ID into NvPCR 'hardware'\n" - " --early Run in early boot mode, without access to /var/\n" - " --event-type=TYPE Event type to include in the event log\n" - "\nSee the %2$s for details.\n", + "\n%2$sExtend a TPM2 PCR with boot phase, machine ID, or file system ID.%3$s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_BANK, - ARG_PCR, - ARG_NVPCR, - ARG_TPM2_DEVICE, - ARG_GRACEFUL, - ARG_FILE_SYSTEM, - ARG_MACHINE_ID, - ARG_PRODUCT_ID, - ARG_EARLY, - ARG_EVENT_TYPE, - }; + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "bank", required_argument, NULL, ARG_BANK }, - { "pcr", required_argument, NULL, ARG_PCR }, - { "nvpcr", required_argument, NULL, ARG_NVPCR }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "graceful", no_argument, NULL, ARG_GRACEFUL }, - { "file-system", required_argument, NULL, ARG_FILE_SYSTEM }, - { "machine-id", no_argument, NULL, ARG_MACHINE_ID }, - { "product-id", no_argument, NULL, ARG_PRODUCT_ID }, - { "early", no_argument, NULL, ARG_EARLY }, - { "event-type", required_argument, NULL, ARG_EVENT_TYPE }, - {} - }; + r = table_print_or_warn(options); + if (r < 0) + return r; - int c, r; + printf("\nSee the %s for details.\n", link); + return 0; +} +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - help(0, NULL, NULL); - return 0; + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_BANK: { + OPTION_LONG("bank", "DIGEST", "Select TPM PCR bank (SHA1, SHA256)"): { const EVP_MD *implementation; - implementation = EVP_get_digestbyname(optarg); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + + implementation = sym_EVP_get_digestbyname(arg); if (!implementation) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", arg); - if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0) + if (strv_extend(&arg_banks, sym_EVP_MD_get0_name(implementation)) < 0) return log_oom(); break; } - case ARG_PCR: - r = tpm2_pcr_index_from_string(optarg); + OPTION_LONG("pcr", "INDEX", "Select TPM PCR index (0…23)"): + if (isempty(arg)) { + arg_pcr_mask = 0; + break; + } + + r = tpm2_pcr_index_from_string(arg); if (r < 0) - return log_error_errno(r, "Failed to parse PCR index: %s", optarg); + return log_error_errno(r, "Failed to parse PCR index: %s", arg); - arg_pcr_index = r; + arg_pcr_mask |= INDEX_TO_MASK(uint32_t, r); break; - case ARG_NVPCR: - if (!tpm2_nvpcr_name_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NvPCR name is not valid: %s", optarg); + OPTION_LONG("nvpcr", "NAME", "Select TPM PCR mode nvindex name"): + if (!tpm2_nvpcr_name_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NvPCR name is not valid: %s", arg); - r = free_and_strdup_warn(&arg_nvpcr_name, optarg); + r = free_and_strdup_warn(&arg_nvpcr_name, arg); if (r < 0) return r; break; - case ARG_TPM2_DEVICE: { + OPTION_LONG("tpm2-device", "PATH", "Use specified TPM2 device"): { _cleanup_free_ char *device = NULL; - if (streq(optarg, "list")) + if (streq(arg, "list")) return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(arg, "auto")) { + device = strdup(arg); if (!device) return log_oom(); } @@ -171,49 +149,47 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_GRACEFUL: + OPTION_LONG("graceful", NULL, + "Exit gracefully if no TPM2 device is found"): arg_graceful = true; break; - case ARG_FILE_SYSTEM: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_file_system); + OPTION_LONG("file-system", "PATH", + "Measure UUID/labels of file system into PCR 15"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_file_system); if (r < 0) return r; - break; - case ARG_MACHINE_ID: + OPTION_LONG("machine-id", NULL, "Measure machine ID into PCR 15"): arg_machine_id = true; break; - case ARG_PRODUCT_ID: + OPTION_LONG("product-id", NULL, + "Measure SMBIOS product ID into NvPCR 'hardware'"): arg_product_id = true; break; - case ARG_EARLY: + OPTION_LONG("early", NULL, + "Run in early boot mode, without access to /var/"): arg_early = true; break; - case ARG_EVENT_TYPE: - if (streq(optarg, "help")) + OPTION_LONG("event-type", "TYPE", + "Event type to include in the event log"): + if (streq(arg, "help")) return DUMP_STRING_TABLE(tpm2_userspace_event_type, Tpm2UserspaceEventType, _TPM2_USERSPACE_EVENT_TYPE_MAX); - arg_event_type = tpm2_userspace_event_type_from_string(optarg); + arg_event_type = tpm2_userspace_event_type_from_string(arg); if (arg_event_type < 0) - return log_error_errno(arg_event_type, "Failed to parse --event-type= argument: %s", optarg); + return log_error_errno(arg_event_type, "Failed to parse --event-type= argument: %s", arg); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!!arg_file_system + arg_machine_id + arg_product_id > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system=, --machine-id, --product-id may not be combined."); - if (arg_pcr_index != UINT_MAX && arg_nvpcr_name) + if (arg_pcr_mask != 0 && arg_nvpcr_name) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--pcr= and --nvpcr= may not be combined."); r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); @@ -221,21 +197,22 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); if (r > 0) arg_varlink = true; - else if (arg_pcr_index == UINT_MAX && !arg_nvpcr_name) { - arg_pcr_index = - (arg_file_system || arg_machine_id) ? TPM2_PCR_SYSTEM_IDENTITY : /* → PCR 15 */ - !arg_product_id ? TPM2_PCR_KERNEL_BOOT : /* → PCR 11 */ - UINT_MAX; + else if (arg_pcr_mask == 0 && !arg_nvpcr_name) { + arg_pcr_mask = + (arg_file_system || arg_machine_id) ? INDEX_TO_MASK(uint32_t, TPM2_PCR_SYSTEM_IDENTITY) : /* → PCR 15 */ + !arg_product_id ? INDEX_TO_MASK(uint32_t, TPM2_PCR_KERNEL_BOOT) : /* → PCR 11 */ + 0; r = free_and_strdup_warn(&arg_nvpcr_name, arg_product_id ? "hardware" : NULL); if (r < 0) return r; } + *ret_args = option_parser_get_args(&state); return 1; } -static int determine_banks(Tpm2Context *c, unsigned target_pcr_nr) { +static int determine_banks(Tpm2Context *c, uint32_t target_pcr_mask) { _cleanup_strv_free_ char **l = NULL; int r; @@ -244,7 +221,7 @@ static int determine_banks(Tpm2Context *c, unsigned target_pcr_nr) { if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */ return 0; - r = tpm2_get_good_pcr_banks_strv(c, UINT32_C(1) << target_pcr_nr, &l); + r = tpm2_get_good_pcr_banks_strv(c, target_pcr_mask, &l); if (r < 0) return log_error_errno(r, "Could not verify pcr banks: %m"); @@ -256,6 +233,7 @@ static int escape_and_truncate_data(const void *data, size_t size, char **ret) { _cleanup_free_ char *safe = NULL; assert(data || size == 0); + assert(ret); if (size > EXTENSION_STRING_SAFE_LIMIT) { safe = cescape_length(data, EXTENSION_STRING_SAFE_LIMIT); @@ -275,7 +253,7 @@ static int escape_and_truncate_data(const void *data, size_t size, char **ret) { } static int extend_pcr_now( - unsigned pcr, + uint32_t pcr_mask, const void *data, size_t size, Tpm2UserspaceEventType event) { @@ -283,11 +261,13 @@ static int extend_pcr_now( _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; int r; + assert(pcr_mask != 0); + r = tpm2_context_new_or_warn(arg_tpm2_device, &c); if (r < 0) return r; - r = determine_banks(c, pcr); + r = determine_banks(c, pcr_mask); if (r < 0) return r; if (strv_isempty(arg_banks)) /* Still none? */ @@ -302,18 +282,20 @@ static int extend_pcr_now( if (escape_and_truncate_data(data, size, &safe) < 0) return log_oom(); - log_debug("Measuring '%s' into PCR index %u, banks %s.", safe, pcr, joined_banks); - - r = tpm2_pcr_extend_bytes(c, arg_banks, pcr, &IOVEC_MAKE(data, size), /* secret= */ NULL, event, safe); - if (r < 0) - return log_error_errno(r, "Could not extend PCR: %m"); + BIT_FOREACH(pcr, pcr_mask) { + log_debug("Measuring '%s' into PCR index %i, banks %s.", safe, pcr, joined_banks); - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_TPM_PCR_EXTEND_STR), - LOG_MESSAGE("Extended PCR index %u with '%s' (banks %s).", pcr, safe, joined_banks), - LOG_ITEM("MEASURING=%s", safe), - LOG_ITEM("PCR=%u", pcr), - LOG_ITEM("BANKS=%s", joined_banks)); + r = tpm2_pcr_extend_bytes(c, arg_banks, pcr, &IOVEC_MAKE(data, size), /* secret= */ NULL, event, safe); + if (r < 0) + return log_error_errno(r, "Could not extend PCR: %m"); + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_TPM_PCR_EXTEND_STR), + LOG_MESSAGE("Extended PCR index %i with '%s' (banks %s).", pcr, safe, joined_banks), + LOG_ITEM("MEASURING=%s", safe), + LOG_ITEM("PCR=%i", pcr), + LOG_ITEM("BANKS=%s", joined_banks)); + } return 0; } @@ -327,6 +309,8 @@ static int extend_nvpcr_now( _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; int r; + assert(name); + r = tpm2_context_new_or_warn(arg_tpm2_device, &c); if (r < 0) return r; @@ -433,7 +417,7 @@ static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_va if (r == -ENOENT) return sd_varlink_error(link, "io.systemd.PCRExtend.NoSuchNvPCR", NULL); } else - r = extend_pcr_now(p.pcr, extend_iovec->iov_base, extend_iovec->iov_len, p.event_type); + r = extend_pcr_now(INDEX_TO_MASK(uint32_t, p.pcr), extend_iovec->iov_base, extend_iovec->iov_len, p.event_type); if (r < 0) return r; @@ -470,15 +454,18 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; + size_t n_args = strv_length(args); + if (arg_varlink) return vl_server(); /* Invocation as Varlink service */ if (arg_file_system) { - if (optind != argc) + if (n_args != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument."); r = pcrextend_file_system_word(arg_file_system, &word, NULL); @@ -489,7 +476,7 @@ static int run(int argc, char *argv[]) { } else if (arg_machine_id) { - if (optind != argc) + if (n_args != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument."); r = pcrextend_machine_id_word(&word); @@ -500,7 +487,7 @@ static int run(int argc, char *argv[]) { } else if (arg_product_id) { - if (optind != argc) + if (n_args != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument."); r = pcrextend_product_id_word(&word); @@ -509,10 +496,10 @@ static int run(int argc, char *argv[]) { event = TPM2_EVENT_PRODUCT_ID; } else { - if (optind+1 != argc) + if (n_args != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument."); - word = strdup(argv[optind]); + word = strdup(args[0]); if (!word) return log_oom(); @@ -529,24 +516,24 @@ static int run(int argc, char *argv[]) { if (arg_event_type >= 0) event = arg_event_type; - if (arg_graceful && !tpm2_is_fully_supported()) { + if (arg_graceful && !tpm2_is_mostly_supported()) { log_notice("No complete TPM2 support detected, exiting gracefully."); return EXIT_SUCCESS; } - /* Skip logic if sd-stub is not used, after all PCR 11 might have a very different purpose then. */ - r = efi_measured_uki(LOG_ERR); + /* Skip logic if measured OS functionality is not enabled. */ + r = efi_measured_os(LOG_ERR); if (r < 0) return r; if (r == 0) { - log_info("Kernel stub did not measure kernel image into PCR %i, skipping userspace measurement, too.", TPM2_PCR_KERNEL_BOOT); + log_info("OS measurements not explicitly requested and kernel stub did not measure kernel image into PCR %i, skipping userspace measurement, too.", TPM2_PCR_KERNEL_BOOT); return EXIT_SUCCESS; } if (arg_nvpcr_name) r = extend_nvpcr_now(arg_nvpcr_name, word, strlen(word), event); else - r = extend_pcr_now(arg_pcr_index, word, strlen(word), event); + r = extend_pcr_now(arg_pcr_mask, word, strlen(word), event); if (r < 0) return r; diff --git a/src/pcrlock/meson.build b/src/pcrlock/meson.build index 6533ef3ab8f17..a80cd31947977 100644 --- a/src/pcrlock/meson.build +++ b/src/pcrlock/meson.build @@ -13,9 +13,10 @@ executables += [ ), 'dependencies' : [ libm, - libopenssl, + libopenssl_cflags, tpm2, ], + 'public' : true, }, ] @@ -27,6 +28,8 @@ install_data('pcrlock.d/500-separator.pcrlock.d/600-0xffffffff.pcrlock', install install_data('pcrlock.d/700-action-efi-exit-boot-services.pcrlock.d/300-present.pcrlock', install_dir : pcrlockdir / '700-action-efi-exit-boot-services.pcrlock.d') install_data('pcrlock.d/700-action-efi-exit-boot-services.pcrlock.d/600-absent.pcrlock', install_dir : pcrlockdir / '700-action-efi-exit-boot-services.pcrlock.d') install_data('pcrlock.d/750-enter-initrd.pcrlock', install_dir : pcrlockdir) +install_data('pcrlock.d/750-os-separator.pcrlock', install_dir : pcrlockdir) +install_data('pcrlock.d/770-nvpcr-separator.pcrlock', install_dir : pcrlockdir) install_data('pcrlock.d/800-leave-initrd.pcrlock', install_dir : pcrlockdir) install_data('pcrlock.d/850-sysinit.pcrlock', install_dir : pcrlockdir) install_data('pcrlock.d/900-ready.pcrlock', install_dir : pcrlockdir) diff --git a/src/pcrlock/pcrlock-firmware.c b/src/pcrlock/pcrlock-firmware.c index 81481dc168968..5abf66077e7e4 100644 --- a/src/pcrlock/pcrlock-firmware.c +++ b/src/pcrlock/pcrlock-firmware.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - +#include "crypto-util.h" #include "log.h" #include "memory-util.h" #include "pcrlock-firmware.h" @@ -149,13 +148,13 @@ int validate_firmware_header( continue; } - implementation = EVP_get_digestbyname(a); + implementation = sym_EVP_get_digestbyname(a); if (!implementation) { log_notice("Event log advertises hash algorithm '%s' we don't implement, can't validate.", a); continue; } - if (EVP_MD_size(implementation) != id->digestSizes[i].digestSize) + if (sym_EVP_MD_get_size(implementation) != id->digestSizes[i].digestSize) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Advertised digest size for '%s' is wrong, refusing.", a); } diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 138841f31cf56..ecf7b18c351bf 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include -#include #include #include @@ -19,6 +17,7 @@ #include "color-util.h" #include "conf-files.h" #include "creds-util.h" +#include "crypto-util.h" #include "efi-api.h" #include "efivars.h" #include "env-util.h" @@ -38,6 +37,7 @@ #include "list.h" #include "main-func.h" #include "mkdir-label.h" +#include "options.h" #include "ordered-set.h" #include "parse-argument.h" #include "parse-util.h" @@ -543,7 +543,7 @@ static int event_log_record_parse_variable_data( if (!p) return log_oom_debug(); - if (!string_is_safe(p)) + if (!string_is_safe(p, STRING_ALLOW_GLOBS)) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Unsafe EFI variable string in record."); *ret_variable_uuid = efi_guid_to_id128(vdata->variableName); @@ -627,7 +627,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { if (r < 0) return log_error_errno(r, "Failed to make C string from EFI action string: %m"); - if (!string_is_safe(d)) { + if (!string_is_safe(d, STRING_ALLOW_GLOBS|STRING_ALLOW_EMPTY|STRING_ALLOW_BACKSLASHES)) { log_warning("Unsafe EFI action string in record, ignoring."); goto invalid; } @@ -899,6 +899,10 @@ static int event_log_load_firmware(EventLog *el) { path = tpm2_firmware_log_path(); r = read_full_file(path, (char**) &buf, &bufsize); + if (r == -ENOENT) { + log_notice("No '%s' file, assuming TPM without firmware support.", path); + return 0; + } if (r < 0) return log_error_errno(r, "Failed to open TPM2 event log '%s': %m", path); @@ -949,6 +953,11 @@ static int event_log_load_firmware(EventLog *el) { continue; } + if (event->pcrIndex >= TPM2_PCRS_MAX) { + log_debug("Skipping event on PCR %" PRIu32 " (out of range).", event->pcrIndex); + continue; + } + r = event_log_add_record(el, &record); if (r < 0) return log_error_errno(r, "Failed to add record to event log: %m"); @@ -1337,7 +1346,7 @@ static int event_log_calculate_pcrs(EventLog *el) { const char *a; assert_se(a = tpm2_hash_alg_to_string(el->algorithms[i])); - assert_se(md = EVP_get_digestbyname(a)); + assert_se(md = sym_EVP_get_digestbyname(a)); el->mds[i] = md; } @@ -1345,7 +1354,7 @@ static int event_log_calculate_pcrs(EventLog *el) { for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) for (size_t i = 0; i < el->n_algorithms; i++) { EventLogRegisterBank *b = el->registers[pcr].banks + i; - event_log_initial_pcr_state(el, pcr, EVP_MD_size(el->mds[i]), &b->calculated); + event_log_initial_pcr_state(el, pcr, sym_EVP_MD_get_size(el->mds[i]), &b->calculated); } FOREACH_ARRAY(rr, el->records, el->n_records) { @@ -1370,20 +1379,20 @@ static int event_log_calculate_pcrs(EventLog *el) { reg_b = reg->banks + i; - mc = EVP_MD_CTX_new(); + mc = sym_EVP_MD_CTX_new(); if (!mc) return log_oom(); - if (EVP_DigestInit_ex(mc, el->mds[i], NULL) != 1) + if (sym_EVP_DigestInit_ex(mc, el->mds[i], NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize %s message digest context.", n); - if (EVP_DigestUpdate(mc, reg_b->calculated.buffer, reg_b->calculated.size) != 1) + if (sym_EVP_DigestUpdate(mc, reg_b->calculated.buffer, reg_b->calculated.size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); - if (EVP_DigestUpdate(mc, rec_b->hash.buffer, rec_b->hash.size) != 1) + if (sym_EVP_DigestUpdate(mc, rec_b->hash.buffer, rec_b->hash.size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); - if (EVP_DigestFinal_ex(mc, reg_b->calculated.buffer, &sz) != 1) + if (sym_EVP_DigestFinal_ex(mc, reg_b->calculated.buffer, &sz) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context."); assert(sz == reg_b->calculated.size); @@ -1472,7 +1481,7 @@ static int event_log_record_validate_hash_firmware( strict = false; } - int mdsz = EVP_MD_size(md); + int mdsz = sym_EVP_MD_get_size(md); assert(mdsz > 0); assert((size_t) mdsz <= sizeof_field(TPM2B_DIGEST, buffer)); @@ -1482,13 +1491,13 @@ static int event_log_record_validate_hash_firmware( unsigned dsz = mdsz; - if (EVP_Digest(hdata, hsz, payload_hash.buffer, &dsz, md, NULL) != 1) + if (sym_EVP_Digest(hdata, hsz, payload_hash.buffer, &dsz, md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to calculate event payload hash."); assert(dsz == (unsigned) mdsz); /* If this didn't match then let's try the alternative format here, if we have one, and check things then. */ if (memcmp_nn(bank->hash.buffer, bank->hash.size, payload_hash.buffer, payload_hash.size) != 0 && hdata_alternative) { - if (EVP_Digest(hdata_alternative, hsz_alternative, payload_hash.buffer, &dsz, md, NULL) != 1) + if (sym_EVP_Digest(hdata_alternative, hsz_alternative, payload_hash.buffer, &dsz, md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to calculate event payload hash."); assert(dsz == (unsigned) mdsz); } @@ -1532,7 +1541,7 @@ static int event_log_record_validate_hash_userspace( assert(sd_json_variant_is_string(js)); s = sd_json_variant_string(js); - mdsz = EVP_MD_size(md); + mdsz = sym_EVP_MD_get_size(md); assert(mdsz > 0); payload_hash_size = mdsz; @@ -1540,7 +1549,7 @@ static int event_log_record_validate_hash_userspace( if (!payload_hash) return log_oom(); - if (EVP_Digest(s, strlen(s), payload_hash, &payload_hash_size, md, NULL) != 1) + if (sym_EVP_Digest(s, strlen(s), payload_hash, &payload_hash_size, md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to calculate event payload hash."); assert((int) payload_hash_size == mdsz); @@ -1566,7 +1575,7 @@ static int event_log_validate_record_hashes(EventLog *el) { const char *a; assert_se(a = tpm2_hash_alg_to_string(bank->algorithm)); - assert_se(md = EVP_get_digestbyname(a)); + assert_se(md = sym_EVP_get_digestbyname(a)); r = event_log_record_validate_hash_firmware(*rr, bank, md); if (r < 0) @@ -1713,7 +1722,7 @@ static int event_log_add_component_file(EventLog *el, EventLogComponent *compone } if (!sd_json_variant_is_object(j)) { - log_warning_errno(r, "Component file %s does not contain JSON object, ignoring.", path); + log_warning("Component file %s does not contain JSON object, ignoring.", path); return 0; } @@ -2447,6 +2456,8 @@ static int event_log_load_and_process(EventLog **ret) { _cleanup_(event_log_freep) EventLog *el = NULL; int r; + assert(ret); + el = event_log_new(); if (!el) return log_oom(); @@ -2489,7 +2500,9 @@ static int event_log_load_and_process(EventLog **ret) { return 0; } -static int verb_show_log(int argc, char *argv[], void *userdata) { +VERB(verb_show_log, "log", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Show measurement log"); +static int verb_show_log(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *log_table = NULL, *pcr_table = NULL; _cleanup_(event_log_freep) EventLog *el = NULL; bool want_json = sd_json_format_enabled(arg_json_format_flags); @@ -2602,7 +2615,9 @@ static int event_log_record_to_cel(EventLogRecord *record, uint64_t *recnum, sd_ return 0; } -static int verb_show_cel(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_show_cel, "cel", + "Show measurement log in TCG CEL-JSON format"); +static int verb_show_cel(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; _cleanup_(event_log_freep) EventLog *el = NULL; uint64_t recnum = 0; @@ -2637,7 +2652,9 @@ static int verb_show_cel(int argc, char *argv[], void *userdata) { return 0; } -static int verb_list_components(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_list_components, "list-components", + "List defined .pcrlock components"); +static int verb_list_components(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(event_log_freep) EventLog *el = NULL; _cleanup_(table_unrefp) Table *table = NULL; enum { @@ -2771,8 +2788,8 @@ static int make_pcrlock_record( const char *a; assert_se(a = tpm2_hash_alg_to_string(*pa)); - assert_se(md = EVP_get_digestbyname(a)); - hash_ssize = EVP_MD_size(md); + assert_se(md = sym_EVP_get_digestbyname(a)); + hash_ssize = sym_EVP_MD_get_size(md); assert(hash_ssize > 0); hash_usize = hash_ssize; @@ -2780,20 +2797,20 @@ static int make_pcrlock_record( if (!hash) return log_oom(); - if (EVP_Digest(data, data_size, hash, &hash_usize, md, NULL) != 1) + if (sym_EVP_Digest(data, data_size, hash, &hash_usize, md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data with algorithm '%s'.", a); r = sd_json_variant_append_arraybo( &digests, - SD_JSON_BUILD_PAIR("hashAlg", SD_JSON_BUILD_STRING(a)), + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_usize))); if (r < 0) return log_error_errno(r, "Failed to build JSON digest object: %m"); } r = sd_json_buildo(ret_record, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(pcr)), - SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(digests))); + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", pcr), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); if (r < 0) return log_error_errno(r, "Failed to build record object: %m"); @@ -2804,7 +2821,7 @@ static void evp_md_ctx_free_all(EVP_MD_CTX *(*md)[TPM2_N_HASH_ALGORITHMS]) { assert(md); FOREACH_ARRAY(alg, *md, TPM2_N_HASH_ALGORITHMS) if (*alg) - EVP_MD_CTX_free(*alg); + sym_EVP_MD_CTX_free(*alg); } static int make_pcrlock_record_from_stream( @@ -2824,13 +2841,13 @@ static int make_pcrlock_record_from_stream( const EVP_MD *md; assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); - assert_se(md = EVP_get_digestbyname(a)); + assert_se(md = sym_EVP_get_digestbyname(a)); - mdctx[i] = EVP_MD_CTX_new(); + mdctx[i] = sym_EVP_MD_CTX_new(); if (!mdctx[i]) return log_oom(); - if (EVP_DigestInit_ex(mdctx[i], md, NULL) != 1) + if (sym_EVP_DigestInit_ex(mdctx[i], md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize message digest for %s.", a); } @@ -2846,7 +2863,7 @@ static int make_pcrlock_record_from_stream( break; for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) - if (EVP_DigestUpdate(mdctx[i], buffer, n) != 1) + if (sym_EVP_DigestUpdate(mdctx[i], buffer, n) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data."); } @@ -2856,18 +2873,18 @@ static int make_pcrlock_record_from_stream( unsigned hash_usize; assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); - hash_ssize = EVP_MD_CTX_size(mdctx[i]); + hash_ssize = sym_EVP_MD_CTX_get_size(mdctx[i]); assert(hash_ssize > 0 && hash_ssize <= EVP_MAX_MD_SIZE); hash_usize = hash_ssize; unsigned char hash[hash_usize]; - if (EVP_DigestFinal_ex(mdctx[i], hash, &hash_usize) != 1) + if (sym_EVP_DigestFinal_ex(mdctx[i], hash, &hash_usize) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash context for algorithn '%s'.", a); r = sd_json_variant_append_arraybo( &digests, - SD_JSON_BUILD_PAIR("hashAlg", SD_JSON_BUILD_STRING(a)), + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_usize))); if (r < 0) return log_error_errno(r, "Failed to build JSON digest object: %m"); @@ -2881,8 +2898,8 @@ static int make_pcrlock_record_from_stream( r = sd_json_buildo( &record, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(i)), - SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(digests))); + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", i), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); if (r < 0) return log_error_errno(r, "Failed to build record object: %m"); @@ -2914,7 +2931,7 @@ static int write_pcrlock(sd_json_variant *array, const char *default_pcrlock_pat r = sd_json_buildo( &v, - SD_JSON_BUILD_PAIR("records", SD_JSON_BUILD_VARIANT(array))); + SD_JSON_BUILD_PAIR_VARIANT("records", array)); if (r < 0) return log_error_errno(r, "Failed to build JSON object: %m"); @@ -2957,2269 +2974,2267 @@ static int unlink_pcrlock(const char *default_pcrlock_path) { return 0; } -static int verb_lock_raw(int argc, char *argv[], void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - if (arg_pcr_mask == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No PCR specified, refusing."); +static int event_log_reduce_to_safe_pcrs(EventLog *el, uint32_t *pcrs) { + _cleanup_free_ char *dropped = NULL, *kept = NULL; - if (argc >= 2) { - f = fopen(argv[1], "re"); - if (!f) - return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); - } + assert(el); + assert(pcrs); - r = make_pcrlock_record_from_stream(arg_pcr_mask, f ?: stdin, &records); - if (r < 0) - return r; + /* When we compile a new PCR policy we don't want to bind to PCRs which are fishy for one of three + * reasons: + * + * 1. The PCR value doesn't match the event log + * 2. The event log for the PCR contains measurements we don't know responsible components for + * 3. The event log for the PCR does not contain measurements for components we know + * + * This function checks for the three conditions and drops the PCR from the mask. + */ - return write_pcrlock(records, NULL); -} + for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { -static int verb_unlock_simple(int argc, char *argv[], void *userdata) { - return unlink_pcrlock(NULL); -} + if (!BIT_SET(*pcrs, pcr)) + continue; -static int verb_lock_secureboot_policy(int argc, char *argv[], void *userdata) { - static const struct { - sd_id128_t id; - const char *name; - int synthesize_empty; /* 0 → fail, > 0 → synthesize empty db, < 0 → skip */ - } variables[] = { - { EFI_VENDOR_GLOBAL, "SecureBoot", 0 }, - { EFI_VENDOR_GLOBAL, "PK", 1 }, - { EFI_VENDOR_GLOBAL, "KEK", 1 }, - { EFI_VENDOR_DATABASE, "db", 1 }, - { EFI_VENDOR_DATABASE, "dbx", 1 }, - { EFI_VENDOR_DATABASE, "dbt", -1 }, - { EFI_VENDOR_DATABASE, "dbr", -1 }, - }; + if (!event_log_pcr_checks_out(el, el->registers + pcr)) { + log_notice("PCR %" PRIu32 " (%s) value does not match event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); + goto drop; + } - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; - int r; + if (!el->registers[pcr].fully_recognized) { + log_notice("PCR %" PRIu32 " (%s) event log contains unrecognized measurements. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); + goto drop; + } - /* Generates expected records from the current SecureBoot state, as readable in the EFI variables - * right now. */ + if (BIT_SET(el->missing_component_pcrs, pcr)) { + log_notice("PCR %" PRIu32 " (%s) is touched by component we can't find in event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); + goto drop; + } - FOREACH_ELEMENT(vv, variables) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL; + log_info("PCR %" PRIu32 " (%s) matches event log and fully consists of recognized measurements. Including in set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); - _cleanup_free_ char *name = NULL; - if (asprintf(&name, "%s-" SD_ID128_UUID_FORMAT_STR, vv->name, SD_ID128_FORMAT_VAL(vv->id)) < 0) + if (strextendf_with_separator(&kept, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0) return log_oom(); - _cleanup_free_ void *data = NULL; - size_t data_size; - r = efi_get_variable(name, NULL, &data, &data_size); - if (r < 0) { - if (r != -ENOENT || vv->synthesize_empty == 0) - return log_error_errno(r, "Failed to read EFI variable '%s': %m", name); - if (vv->synthesize_empty < 0) - continue; - - /* If the main database variables are not set we don't consider this an error, but - * measure an empty database instead. */ - log_debug("EFI variable %s is not set, synthesizing empty variable for measurement.", name); - data_size = 0; - } + continue; - _cleanup_free_ char16_t* name16 = utf8_to_utf16(vv->name, SIZE_MAX); - if (!name16) - return log_oom(); - size_t name16_bytes = char16_strlen(name16) * 2; + drop: + *pcrs &= ~(UINT32_C(1) << pcr); - size_t vdata_size = offsetof(UEFI_VARIABLE_DATA, unicodeName) + name16_bytes + data_size; - _cleanup_free_ UEFI_VARIABLE_DATA *vdata = malloc(vdata_size); - if (!vdata) + if (strextendf_with_separator(&dropped, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0) return log_oom(); + } - *vdata = (UEFI_VARIABLE_DATA) { - .unicodeNameLength = name16_bytes / 2, - .variableDataLength = data_size, - }; - - efi_id128_to_guid(vv->id, vdata->variableName); - memcpy(mempcpy(vdata->unicodeName, name16, name16_bytes), data, data_size); - - r = make_pcrlock_record(TPM2_PCR_SECURE_BOOT_POLICY /* =7 */, vdata, vdata_size, &record); - if (r < 0) - return r; + if (dropped) + log_notice("PCRs dropped from protection mask: %s", dropped); + else + log_debug("No PCRs dropped from protection mask."); - r = sd_json_variant_append_array(&array, record); - if (r < 0) - return log_error_errno(r, "Failed to append to JSON array: %m"); - } + if (kept) + log_notice("PCRs in protection mask: %s", kept); + else + log_notice("No PCRs kept in protection mask."); - return write_pcrlock(array, PCRLOCK_SECUREBOOT_POLICY_PATH); + return 0; } -static int verb_unlock_secureboot_policy(int argc, char *argv[], void *userdata) { - return unlink_pcrlock(PCRLOCK_SECUREBOOT_POLICY_PATH); -} +static int pcr_prediction_add_result( + Tpm2PCRPrediction *context, + Tpm2PCRPredictionResult *result, + uint32_t pcr, + const char *path) { -static int event_log_record_is_secureboot_variable(EventLogRecord *rec, sd_id128_t uuid, const char *name) { - _cleanup_free_ char *found_name = NULL; - sd_id128_t found_uuid; + _cleanup_free_ Tpm2PCRPredictionResult *copy = NULL; int r; - assert(rec); - assert(name); - - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - return false; - - if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY) - return false; - - if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) - return false; + assert(context); + assert(result); - if (rec->firmware_event_type != EV_EFI_VARIABLE_DRIVER_CONFIG) - return false; + copy = newdup(Tpm2PCRPredictionResult, result, 1); + if (!copy) + return log_oom(); - r = event_log_record_parse_variable_data(rec, &found_uuid, &found_name); - if (r == -EBADMSG) - return false; + r = ordered_set_ensure_put(context->results + pcr, &tpm2_pcr_prediction_result_hash_ops, copy); + if (r == -EEXIST) /* Multiple identical results for the same PCR are totally expected */ + return 0; if (r < 0) - return r; + return log_error_errno(r, "Failed to insert result into set: %m"); - if (!sd_id128_equal(found_uuid, uuid)) - return false; + log_debug("Added prediction result %u for PCR %" PRIu32 " (path: %s)", ordered_set_size(context->results[pcr]), pcr, strempty(path)); - return streq(found_name, name); + TAKE_PTR(copy); + return 0; } -static bool event_log_record_is_secureboot_authority(EventLogRecord *rec) { - assert(rec); - - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - return false; - - if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY) - return false; +static const EVP_MD* evp_from_tpm2_alg(uint16_t alg) { + const char *name; - if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) - return false; + name = tpm2_hash_alg_to_string(alg); + if (!name) + return NULL; - return rec->firmware_event_type == EV_EFI_VARIABLE_AUTHORITY; + return sym_EVP_get_digestbyname(name); } -static int event_log_ensure_secureboot_consistency(EventLog *el) { - static const struct { - sd_id128_t id; - const char *name; - bool required; - } table[] = { - { EFI_VENDOR_GLOBAL, "SecureBoot", true }, - { EFI_VENDOR_GLOBAL, "PK", true }, - { EFI_VENDOR_GLOBAL, "KEK", true }, - { EFI_VENDOR_DATABASE, "db", true }, - { EFI_VENDOR_DATABASE, "dbx", true }, - { EFI_VENDOR_DATABASE, "dbt", false }, - { EFI_VENDOR_DATABASE, "dbr", false }, - // FIXME: ensure we also find the separator here - }; - - EventLogRecord *records[ELEMENTSOF(table)] = {}; - EventLogRecord *first_authority = NULL; - - assert(el); +static int event_log_component_variant_calculate( + Tpm2PCRPredictionResult *result, + EventLogComponentVariant *variant, + uint32_t pcr) { - /* Ensures that the PCR 7 records are complete and in order. Before we lock down PCR 7 we want to - * ensure its state is actually consistent. */ + assert(result); + assert(variant); - FOREACH_ARRAY(rr, el->records, el->n_records) { + FOREACH_ARRAY(rr, variant->records, variant->n_records) { EventLogRecord *rec = *rr; - size_t found = SIZE_MAX; - if (event_log_record_is_secureboot_authority(rec)) { - if (first_authority) - continue; - - first_authority = rec; - // FIXME: also check that each authority record's data is also listed in 'db' + if (!EVENT_LOG_RECORD_IS_PCR(rec)) continue; - } - for (size_t i = 0; i < ELEMENTSOF(table); i++) - if (event_log_record_is_secureboot_variable(rec, table[i].id, table[i].name)) { - found = i; - break; - } - if (found == SIZE_MAX) + if (rec->pcr != pcr) continue; - /* Require the authority records always come *after* database measurements */ - if (first_authority) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "SecureBoot authority before variable, refusing."); + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL; + EventLogRecordBank *b; - /* Check for duplicates */ - if (records[found]) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate '%s' record, refusing.", rec->description); + if (result->hash[i].size <= 0) /* already invalidated */ + continue; - /* Check for order */ - for (size_t j = found + 1; j < ELEMENTSOF(table); j++) - if (records[j]) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'%s' record before '%s' record, refusing.", records[j]->description, rec->description); + b = event_log_record_find_bank(rec, tpm2_hash_algorithms[i]); + if (!b) { + /* Can't calculate, hence invalidate */ + result->hash[i] = (TPM2B_DIGEST) {}; + continue; + } - records[found] = rec; - } + md_ctx = sym_EVP_MD_CTX_new(); + if (!md_ctx) + return log_oom(); - /* Check for existence */ - for (size_t i = 0; i < ELEMENTSOF(table); i++) - if (table[i].required && !records[i]) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Required record '%s' not found, refusing.", table[i].name); - - /* At this point we know that all required variables have been measured, in the right order. */ - return 0; -} - -static int verb_lock_secureboot_authority(int argc, char *argv[], void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; - _cleanup_(event_log_freep) EventLog *el = NULL; - int r; + const EVP_MD *md = ASSERT_PTR(evp_from_tpm2_alg(tpm2_hash_algorithms[i])); - /* Lock down the EV_EFI_VARIABLE_AUTHORITY records from the existing log. Note that there's not too - * much value in locking this down too much, since it stores only the result of the primary database - * checks, and that's what we should bind policy to. Moreover it's hard to predict, since extension - * card firmware validation will result in additional records here. */ + int sz = sym_EVP_MD_get_size(md); + assert(sz > 0); + assert((size_t) sz <= sizeof_field(TPM2B_DIGEST, buffer)); - if (!is_efi_secure_boot()) { - log_info("SecureBoot disabled, not generating authority .pcrlock file."); - return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH); - } + assert(sz == tpm2_hash_alg_to_size(tpm2_hash_algorithms[i])); - el = event_log_new(); - if (!el) - return log_oom(); + assert(result->hash[i].size == (size_t) sz); + assert(b->hash.size == (size_t) sz); - r = event_log_add_algorithms_from_environment(el); - if (r < 0) - return r; + if (sym_EVP_DigestInit_ex(md_ctx, md, NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize message digest."); - r = event_log_load(el); - if (r < 0) - return r; + if (sym_EVP_DigestUpdate(md_ctx, result->hash[i].buffer, sz) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash bank value."); - r = event_log_read_pcrs(el); - if (r < 0) - return r; + if (sym_EVP_DigestUpdate(md_ctx, b->hash.buffer, sz) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data value."); - r = event_log_calculate_pcrs(el); - if (r < 0) - return r; + unsigned l = (unsigned) sz; + if (sym_EVP_DigestFinal_ex(md_ctx, result->hash[i].buffer, &l) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize message digest."); - /* Before we base anything on the event log records, let's check that the event log state checks - * out. */ + assert(l == (unsigned) sz); + } + } - r = event_log_pcr_mask_checks_out(el, UINT32_C(1) << TPM2_PCR_SECURE_BOOT_POLICY); - if (r < 0) - return r; + return 0; +} - r = event_log_validate_record_hashes(el); - if (r < 0) - return r; +static int event_log_predict_pcrs( + EventLog *el, + Tpm2PCRPrediction *context, + Tpm2PCRPredictionResult *parent_result, + size_t component_index, + uint32_t pcr, + const char *path) { - r = event_log_ensure_secureboot_consistency(el); - if (r < 0) - return r; + EventLogComponent *component; + int count = 0, r; - FOREACH_ARRAY(rr, el->records, el->n_records) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; - EventLogRecord *rec = *rr; + assert(el); + assert(context); + assert(parent_result); - if (!event_log_record_is_secureboot_authority(rec)) - continue; + /* Check if we reached the end of the components, generate a result, and backtrack */ + if (component_index >= el->n_components || + (arg_location_end && strcmp(el->components[component_index]->id, arg_location_end) > 0)) { + r = pcr_prediction_add_result(context, parent_result, pcr, path); + if (r < 0) + return r; - log_debug("Locking down authority '%s'.", strna(rec->description)); + return 1; + } - LIST_FOREACH(banks, bank, rec->banks) { - r = sd_json_variant_append_arraybo( - &digests, - SD_JSON_BUILD_PAIR("hashAlg", SD_JSON_BUILD_STRING(tpm2_hash_alg_to_string(bank->algorithm))), - SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size))); - if (r < 0) - return log_error_errno(r, "Failed to build digests array: %m"); - } + component = ASSERT_PTR(el->components[component_index]); - r = sd_json_variant_append_arraybo( - &array, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(rec->pcr)), - SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(digests))); + /* Check if we are just about to process a component after start, if so record a result and continue. */ + if (arg_location_start && strcmp(component->id, arg_location_start) > 0) { + r = pcr_prediction_add_result(context, parent_result, pcr, path); if (r < 0) - return log_error_errno(r, "Failed to build record array: %m"); + return r; } - return write_pcrlock(array, PCRLOCK_SECUREBOOT_AUTHORITY_PATH); -} - -static int verb_unlock_secureboot_authority(int argc, char *argv[], void *userdata) { - return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH); -} - -static int verb_lock_gpt(int argc, char *argv[], void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *record = NULL; - _cleanup_(sd_device_unrefp) sd_device *d = NULL; - uint8_t h[2 * 4096]; /* space for at least two 4K sectors. GPT header should definitely be in here */ - uint64_t start, n_members, member_size; - _cleanup_close_ int fd = -EBADF; - const GptHeader *p; - size_t found = 0; - ssize_t n; - int r; + FOREACH_ARRAY(ii, component->variants, component->n_variants) { + _cleanup_free_ Tpm2PCRPredictionResult *result = NULL; + EventLogComponentVariant *variant = *ii; + _cleanup_free_ char *subpath = NULL; - r = block_device_new_from_path( - argc >= 2 ? argv[1] : "/", - BLOCK_DEVICE_LOOKUP_WHOLE_DISK|BLOCK_DEVICE_LOOKUP_BACKING|BLOCK_DEVICE_LOOKUP_ORIGINATING, - &d); - if (r < 0) - return log_error_errno(r, "Failed to determine root block device: %m"); + /* Operate on a copy of the result */ - fd = sd_device_open(d, O_CLOEXEC|O_RDONLY|O_NOCTTY); - if (fd < 0) - return log_error_errno(fd, "Failed to open root block device: %m"); + if (path) + subpath = strjoin(path, ":", component->id); + else + subpath = strdup(component->id); + if (!subpath) + return log_oom(); - n = pread(fd, &h, sizeof(h), 0); - if (n < 0) - return log_error_errno(errno, "Failed to read GPT header of block device: %m"); - if ((size_t) n != sizeof(h)) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read trying to read GPT header."); + if (!streq(component->id, variant->id)) + if (!strextend(&subpath, "@", variant->id)) + return log_oom(); - /* Try a couple of sector sizes */ - for (size_t sz = 512; sz <= 4096; sz <<= 1) { - assert(sizeof(h) >= sz * 2); - p = (const GptHeader*) (h + sz); /* 2nd sector */ + result = newdup(Tpm2PCRPredictionResult, parent_result, 1); + if (!result) + return log_oom(); - if (!gpt_header_has_signature(p)) - continue; + r = event_log_component_variant_calculate( + result, + variant, + pcr); + if (r < 0) + return r; - if (found != 0) - return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), - "Disk has partition table for multiple sector sizes, refusing."); + r = event_log_predict_pcrs( + el, + context, + result, + component_index + 1, /* Next component */ + pcr, + subpath); + if (r < 0) + return r; - found = sz; + count += r; } - if (found == 0) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "Disk does not have GPT partition table, refusing."); + return count; +} - p = (const GptHeader*) (h + found); +static ssize_t event_log_calculate_component_combinations(EventLog *el) { + ssize_t count = 1; + assert(el); - if (le32toh(p->header_size) > found) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "GPT header size over long (%" PRIu32 "), refusing.", le32toh(p->header_size)); + FOREACH_ARRAY(cc, el->components, el->n_components) { + EventLogComponent *c = *cc; - start = le64toh(p->partition_entry_lba); - if (start > UINT64_MAX / found) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "Partition table start offset overflow, refusing."); + assert(c->n_variants > 0); - member_size = le32toh(p->size_of_partition_entry); - if (member_size < sizeof(GptPartitionEntry)) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "Partition entry size too short, refusing."); + /* Overflow check */ + if (c->n_variants > (size_t) (SSIZE_MAX/count)) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many component combinations."); + count *= c->n_variants; + } - n_members = le32toh(p->number_of_partition_entries); - uint64_t member_bufsz = n_members * member_size; - if (member_bufsz > 1U*1024U*1024U) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "Partition table size too large, refusing."); + return count; +} - member_bufsz = ROUND_UP(member_bufsz, found); +static int event_log_show_predictions(Tpm2PCRPrediction *context, uint16_t alg) { + int r; - _cleanup_free_ void *members = malloc(member_bufsz); - if (!members) - return log_oom(); + assert(context); - n = pread(fd, members, member_bufsz, start * found); - if (n < 0) - return log_error_errno(errno, "Failed to read GPT partition table entries: %m"); - if ((size_t) n != member_bufsz) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading GPT partition table entries."); + pager_open(arg_pager_flags); - size_t vdata_size = le32toh(p->header_size) + sizeof(le64_t) + member_size * n_members; - _cleanup_free_ void *vdata = malloc0(vdata_size); - if (!vdata) - return log_oom(); + if (sd_json_format_enabled(arg_json_format_flags)) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; - void *n_measured_entries = mempcpy(vdata, p, sizeof(GptHeader)); /* n_measured_entries is a 64bit value */ + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL; - void *qq = (uint8_t*) n_measured_entries + sizeof(le64_t); + r = tpm2_pcr_prediction_to_json( + context, + tpm2_hash_algorithms[i], + &aj); + if (r < 0) + return r; - for (uint64_t i = 0; i < n_members; i++) { - const GptPartitionEntry *entry = (const GptPartitionEntry*) ((const uint8_t*) members + (member_size * i)); + if (sd_json_variant_elements(aj) == 0) + continue; - if (memeqzero(entry->partition_type_guid, sizeof(entry->partition_type_guid))) - continue; + r = sd_json_variant_set_field( + &j, + tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]), + aj); + if (r < 0) + return log_error_errno(r, "Failed to add prediction bank to object: %m"); + } - qq = mempcpy(qq, entry, member_size); - unaligned_write_le64(n_measured_entries, unaligned_read_le64(n_measured_entries) + 1); - } + if (!j) { + r = sd_json_variant_new_object(&j, NULL, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocated empty object: %m"); + } - vdata_size = (uint8_t*) qq - (uint8_t*) vdata; - - r = make_pcrlock_record(TPM2_PCR_BOOT_LOADER_CONFIG /* =5 */, vdata, vdata_size, &record); - if (r < 0) - return r; - - r = sd_json_variant_new_array(&array, &record, 1); - if (r < 0) - return log_error_errno(r, "Failed to append to JSON array: %m"); - - return write_pcrlock(array, PCRLOCK_GPT_PATH); -} - -static int verb_unlock_gpt(int argc, char *argv[], void *userdata) { - return unlink_pcrlock(PCRLOCK_GPT_PATH); -} - -static bool event_log_record_is_separator(const EventLogRecord *rec) { - assert(rec); - - /* Recognizes EV_SEPARATOR events */ - - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - return false; + sd_json_variant_dump(j, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL); + return 0; + } - if (rec->firmware_event_type != EV_SEPARATOR) - return false; + for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { + Tpm2PCRPredictionResult *result; + if (!BIT_SET(context->pcrs, pcr)) + continue; - return rec->event_payload_valid == EVENT_PAYLOAD_VALID_YES; /* Insist the record is consistent */ -} + if (ordered_set_isempty(context->results[pcr])) { + printf("No results for PCR %u (%s).\n", pcr, tpm2_pcr_index_to_string(pcr)); + continue; + } -static int event_log_record_is_action_calling_efi_app(const EventLogRecord *rec) { - _cleanup_free_ char *d = NULL; - int r; + printf("%sResults for PCR %u (%s):%s\n", ansi_underline(), pcr, tpm2_pcr_index_to_string(pcr), ansi_normal()); - assert(rec); + ORDERED_SET_FOREACH(result, context->results[pcr]) { - /* Recognizes the special EV_EFI_ACTION that is issues when the firmware passes control to the boot loader. */ + _cleanup_free_ char *aa = NULL, *h = NULL; + const char *a; - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - return false; + TPM2B_DIGEST *hash = tpm2_pcr_prediction_result_get_hash(result, alg); + if (!hash) + continue; - if (rec->pcr != TPM2_PCR_BOOT_LOADER_CODE) - return false; + a = ASSERT_PTR(tpm2_hash_alg_to_string(alg)); + aa = strdup(a); + if (!aa) + return log_oom(); - if (rec->firmware_event_type != EV_EFI_ACTION) - return false; + ascii_strlower(aa); - if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) /* Insist the record is consistent */ - return false; + h = hexmem(hash->buffer, hash->size); + if (!h) + return log_oom(); - r = make_cstring(rec->firmware_payload, rec->firmware_payload_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, &d); - if (r < 0) - return r; + printf(" %s%-6s:%s %s\n", ansi_grey(), aa, ansi_normal(), h); + } + } - return streq(d, "Calling EFI Application from Boot Option"); + return 0; } -static void enable_json_sse(void) { - /* We shall write this to a single output stream? We have to output two files, hence try to be smart - * and enable JSON SSE */ - - if (!arg_pcrlock_path && arg_pcrlock_auto) - return; - - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_SSE)) - return; - - log_notice("Enabling JSON_SEQ mode, since writing two .pcrlock files to single output."); - arg_json_format_flags |= SD_JSON_FORMAT_SSE; -} +static int tpm2_pcr_prediction_run( + EventLog *el, + Tpm2PCRPrediction *context) { -static int verb_lock_firmware(int argc, char *argv[], void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array_early = NULL, *array_late = NULL; - _cleanup_(event_log_freep) EventLog *el = NULL; - uint32_t always_mask, separator_mask, separator_seen_mask = 0, action_seen_mask = 0; - const char *default_pcrlock_early_path, *default_pcrlock_late_path; int r; - enable_json_sse(); + assert(el); + assert(context); - /* The PCRs we intend to cover. Note that we measure firmware, external *and* boot loader code/config - * here – but the latter only until the "separator" events are seen, which tell us where transition - * into OS boot loader happens. This reflects the fact that on some systems the firmware already - * measures some firmware-supplied apps into PCR 4. (e.g. Thinkpad X1 Gen9) */ - if (endswith(argv[0], "firmware-code")) { - always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CODE) | /* → 0 */ - (UINT32_C(1) << TPM2_PCR_EXTERNAL_CODE); /* → 2 */ + for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { + _cleanup_free_ Tpm2PCRPredictionResult *result = NULL; - separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; /* → 4 */ + if (!BIT_SET(context->pcrs, pcr)) + continue; - default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH; - default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH; - } else { - assert(endswith(argv[0], "firmware-config")); - always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CONFIG) | /* → 1 */ - (UINT32_C(1) << TPM2_PCR_EXTERNAL_CONFIG); /* → 3 */ + result = new0(Tpm2PCRPredictionResult, 1); + if (!result) + return log_oom(); - separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CONFIG; /* → 5 */ + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) + event_log_initial_pcr_state(el, pcr, tpm2_hash_alg_to_size(tpm2_hash_algorithms[i]), result->hash + i); - default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH; - default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH; + r = event_log_predict_pcrs( + el, + context, + result, + /* component_index= */ 0, + pcr, + /* path= */ NULL); + if (r < 0) + return r; } - el = event_log_new(); - if (!el) - return log_oom(); + return 0; +} - r = event_log_add_algorithms_from_environment(el); - if (r < 0) - return r; +VERB_NOARG(verb_predict, "predict", + "Predict PCR values"); +static int verb_predict(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction context = { + arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK, + }; + _cleanup_(event_log_freep) EventLog *el = NULL; + ssize_t count; + int r; - r = event_log_load(el); + r = event_log_load_and_process(&el); if (r < 0) return r; - r = event_log_read_pcrs(el); - if (r < 0) - return r; + count = event_log_calculate_component_combinations(el); + if (count < 0) + return count; - r = event_log_calculate_pcrs(el); - if (r < 0) - return r; + log_info("%zi combinations of components.", count); - r = event_log_validate_record_hashes(el); + r = event_log_reduce_to_safe_pcrs(el, &context.pcrs); if (r < 0) return r; - /* Before we base anything on the event log records for any of the selected PCRs, let's check that - * the event log state checks out for them. */ - - r = event_log_pcr_mask_checks_out(el, always_mask|separator_mask); + r = tpm2_pcr_prediction_run(el, &context); if (r < 0) return r; - // FIXME: before doing this, validate ahead-of-time that EV_SEPARATOR records exist for all entries, - // and exactly once - - FOREACH_ARRAY(rr, el->records, el->n_records) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; - EventLogRecord *rec = *rr; - uint32_t bit = UINT32_C(1) << rec->pcr; - - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - continue; - - if (!FLAGS_SET(always_mask, bit) && - !(FLAGS_SET(separator_mask, bit) && !FLAGS_SET(separator_seen_mask|action_seen_mask, bit))) - continue; - - /* If we hit the separator record, we stop processing the PCRs listed in `separator_mask` */ - if (event_log_record_is_separator(rec)) { - separator_seen_mask |= bit; - continue; - } + return event_log_show_predictions(&context, el->primary_algorithm); +} - /* If we hit the special "Calling EFI Application from Boot Option" action we treat this the - * same as a separator here, as that's where firmware passes control to boot loader. Note - * that some EFI implementations forget to generate one of them. */ - r = event_log_record_is_action_calling_efi_app(rec); - if (r < 0) - return log_error_errno(r, "Failed to check if event is 'Calling EFI Application from Boot Option' action: %m"); - if (r > 0) { - action_seen_mask |= bit; - continue; - } +static int remove_policy_file(const char *path) { + assert(path); - LIST_FOREACH(banks, bank, rec->banks) { - r = sd_json_variant_append_arraybo( - &digests, - SD_JSON_BUILD_PAIR("hashAlg", SD_JSON_BUILD_STRING(tpm2_hash_alg_to_string(bank->algorithm))), - SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size))); - if (r < 0) - return log_error_errno(r, "Failed to build digests array: %m"); - } + if (unlink(path) < 0) { + if (errno == ENOENT) + return 0; - r = sd_json_variant_append_arraybo( - FLAGS_SET(separator_seen_mask, bit) ? &array_late : &array_early, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(rec->pcr)), - SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(digests))); - if (r < 0) - return log_error_errno(r, "Failed to build record array: %m"); + return log_error_errno(errno, "Failed to remove policy file '%s': %m", path); } - r = write_pcrlock(array_early, default_pcrlock_early_path); - if (r < 0) - return r; - - return write_pcrlock(array_late, default_pcrlock_late_path); + log_info("Removed policy file '%s'.", path); + return 1; } -static int verb_unlock_firmware(int argc, char *argv[], void *userdata) { - const char *default_pcrlock_early_path, *default_pcrlock_late_path; +static int determine_boot_policy_file(char **ret_path, char **ret_credential_name) { int r; - if (endswith(argv[0], "firmware-code")) { - default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH; - default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH; - } else { - default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH; - default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH; - } - - r = unlink_pcrlock(default_pcrlock_early_path); + _cleanup_free_ char *path = NULL; + r = get_global_boot_credentials_path(&path); if (r < 0) return r; + if (r == 0) { + if (ret_path) + *ret_path = NULL; + if (ret_credential_name) + *ret_credential_name = NULL; + return 0; /* not found! */ + } - if (arg_pcrlock_path) /* if the path is specified don't delete the same thing twice */ - return 0; + sd_id128_t machine_id; + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return log_error_errno(r, "Failed to read machine ID: %m"); - r = unlink_pcrlock(default_pcrlock_late_path); + r = boot_entry_token_ensure( + /* root= */ NULL, + /* conf_root= */ NULL, + machine_id, + /* machine_id_is_random= */ false, + &arg_entry_token_type, + &arg_entry_token); if (r < 0) return r; - return 0; -} + _cleanup_free_ char *fn = strjoin("pcrlock.", arg_entry_token, ".cred"); + if (!fn) + return log_oom(); -static int verb_lock_machine_id(int argc, char *argv[], void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; - _cleanup_free_ char *word = NULL; - int r; + if (!filename_is_valid(fn)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name '%s' would not be a valid file name, refusing.", fn); - r = pcrextend_machine_id_word(&word); - if (r < 0) - return r; + _cleanup_free_ char *joined = NULL; + if (ret_path) { + joined = path_join(path, fn); + if (!joined) + return log_oom(); + } - r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record); - if (r < 0) - return r; + _cleanup_free_ char *cn = NULL; + if (ret_credential_name) { + /* The .cred suffix of the file is stripped when PID 1 imports the credential, hence exclude it from + * the embedded credential name. */ + cn = strjoin("pcrlock.", arg_entry_token); + if (!cn) + return log_oom(); - r = sd_json_variant_new_array(&array, &record, 1); - if (r < 0) - return log_error_errno(r, "Failed to create record array: %m"); + ascii_strlower(cn); /* lowercase this file, no matter what, since stored on VFAT, and we don't want + * to run into case change incompatibilities */ + } - return write_pcrlock(array, PCRLOCK_MACHINE_ID_PATH); -} + if (ret_path) + *ret_path = TAKE_PTR(joined); -static int verb_unlock_machine_id(int argc, char *argv[], void *userdata) { - return unlink_pcrlock(PCRLOCK_MACHINE_ID_PATH); -} + if (ret_credential_name) + *ret_credential_name = TAKE_PTR(cn); -static int pcrlock_file_system_path(const char *normalized_path, char **ret) { - _cleanup_free_ char *s = NULL; + return 1; /* found! */ +} - assert(normalized_path); +static int write_boot_policy_file(const char *json_text) { + _cleanup_free_ char *boot_policy_file = NULL, *credential_name = NULL; + int r; - if (path_equal(normalized_path, "/")) - s = strdup(PCRLOCK_ROOT_FILE_SYSTEM_PATH); - else { - /* We reuse the escaping we use for turning paths into unit names */ - _cleanup_free_ char *escaped = NULL; + assert(json_text); - assert(normalized_path[0] == '/'); - assert(normalized_path[1] != '/'); + r = determine_boot_policy_file(&boot_policy_file, &credential_name); + if (r < 0) + return r; + if (r == 0) { + log_info("Did not find XBOOTLDR/ESP partition, not writing boot policy file."); + return 0; + } - escaped = unit_name_escape(normalized_path + 1); - if (!escaped) - return log_oom(); + _cleanup_(iovec_done) struct iovec encoded = {}; + r = encrypt_credential_and_warn( + CRED_AES256_GCM_BY_NULL, + credential_name, + now(CLOCK_REALTIME), + /* not_after= */ USEC_INFINITY, + /* tpm2_device= */ NULL, + /* tpm2_hash_pcr_mask= */ 0, + /* tpm2_pubkey_path= */ NULL, + /* tpm2_pubkey_pcr_mask= */ 0, + UID_INVALID, + &IOVEC_MAKE_STRING(json_text), + CREDENTIAL_ALLOW_NULL, + &encoded); + if (r < 0) + return log_error_errno(r, "Failed to encode policy as credential: %m"); - s = strjoin(PCRLOCK_FILE_SYSTEM_PATH_PREFIX, escaped, ".pcrlock"); - } - if (!s) - return log_oom(); + r = write_base64_file_at( + AT_FDCWD, + boot_policy_file, + &encoded, + WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL); + if (r < 0) + return log_error_errno(r, "Failed to write boot policy file to '%s': %m", boot_policy_file); - *ret = TAKE_PTR(s); - return 0; + log_info("Written new boot policy to '%s'.", boot_policy_file); + return 1; } -static int verb_lock_file_system(int argc, char *argv[], void *userdata) { - const char* paths[3] = {}; +static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) { int r; - if (argc > 1) - paths[0] = argv[1]; - else { - dev_t a, b; - paths[0] = "/"; + /* Here's how this all works: after predicting all possible PCR values for next boot (with + * alternatives) we'll calculate a policy from it as a combination of PolicyPCR + PolicyOR + * expressions. This is then stored in an NV index. When a component of the boot process is changed a + * new prediction is made and the NV index updated (which automatically invalidates any older + * policies). + * + * Whenever we want to lock an encrypted object (for example FDE) against this policy, we'll use a + * PolicyAuthorizeNV expression that pins the NV index in the policy, and permits access to any + * policies matching the current NV index contents. + * + * We grant world-readable read access to the NV index. Write access is controlled by a PIN (which we + * either generate locally or which the user can provide us with) which can also be used for + * recovery. This PIN is sealed to the TPM and is locked via PolicyAuthorizeNV to the NV index it + * protects (i.e. we dogfood 🌭 🐶 hard here). This means in order to update such a policy we need + * the policy to pass. + * + * Information about the used NV Index, the SRK of the TPM, the sealed PIN and the current PCR + * prediction data are stored in a JSON file in /var/lib/. In order to be able to unlock root disks + * this data must be also copied to the ESP so that it is available to the initrd. The data is not + * sensitive, as SRK and NV index are pinned by it, and the prediction data must match the NV index + * to be useful. */ - r = get_block_device("/", &a); - if (r < 0) - return log_error_errno(r, "Failed to get device of root file system: %m"); + usec_t start_usec = now(CLOCK_MONOTONIC); - r = get_block_device("/var", &b); - if (r < 0) - return log_error_errno(r, "Failed to get device of /var/ file system: %m"); + _cleanup_(event_log_freep) EventLog *el = NULL; + r = event_log_load_and_process(&el); + if (r < 0) + return r; - /* if backing device is distinct, then measure /var/ too */ - if (a != b) - paths[1] = "/var"; + _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction new_prediction = { + arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK, + }; + r = event_log_reduce_to_safe_pcrs(el, &new_prediction.pcrs); + if (r < 0) + return r; - enable_json_sse(); - } + if (!force && new_prediction.pcrs == 0) + log_notice("Set of PCRs to use for policy is empty. Generated policy will not provide any protection in its current form. Proceeding."); - STRV_FOREACH(p, paths) { - _cleanup_free_ char *word = NULL, *normalized_path = NULL, *pcrlock_file = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; + usec_t predict_start_usec = now(CLOCK_MONOTONIC); - r = pcrextend_file_system_word(*p, &word, &normalized_path); - if (r < 0) - return r; + r = tpm2_pcr_prediction_run(el, &new_prediction); + if (r < 0) + return r; - r = pcrlock_file_system_path(normalized_path, &pcrlock_file); - if (r < 0) - return r; + log_info("Predicted future PCRs in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), predict_start_usec), 1)); - r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record); - if (r < 0) - return r; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_prediction_json = NULL; + r = tpm2_pcr_prediction_to_json(&new_prediction, el->primary_algorithm, &new_prediction_json); + if (r < 0) + return r; - r = sd_json_variant_new_array(&array, &record, 1); - if (r < 0) - return log_error_errno(r, "Failed to create record array: %m"); + if (DEBUG_LOGGING) + (void) sd_json_variant_dump(new_prediction_json, SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO, stderr, NULL); - r = write_pcrlock(array, pcrlock_file); - if (r < 0) - return r; + /* v257 and older mistakenly used --pcrlock= for the path. To keep backward compatibility, let's fallback to it when + * --policy= is unspecified but --pcrlock is specified. */ + if (!arg_policy_path && arg_pcrlock_path) { + log_notice("Specified --pcrlock= option for make-policy command. Please use --policy= instead."); + + arg_policy_path = strdup(arg_pcrlock_path); + if (!arg_policy_path) + return log_oom(); } - return 0; -} + _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy old_policy = {}; + r = tpm2_pcrlock_policy_load(arg_policy_path, &old_policy); + if (r < 0) + return r; -static int verb_unlock_file_system(int argc, char *argv[], void *userdata) { - const char* paths[3] = {}; - int r; + bool have_old_policy = r > 0; - if (argc > 1) - paths[0] = argv[1]; - else { - paths[0] = "/"; - paths[1] = "/var"; + /* When we update the policy the old serializations for NV, SRK, PIN remain the same */ + _cleanup_(iovec_done) struct iovec + nv_blob = TAKE_STRUCT(old_policy.nv_handle), + nv_public_blob = TAKE_STRUCT(old_policy.nv_public), + srk_blob = TAKE_STRUCT(old_policy.srk_handle), + pin_public = TAKE_STRUCT(old_policy.pin_public), + pin_private = TAKE_STRUCT(old_policy.pin_private); + + if (have_old_policy) { + if (arg_nv_index != 0 && old_policy.nv_index != arg_nv_index) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Stored policy references different NV index (0x%x) than specified (0x%x), refusing.", old_policy.nv_index, arg_nv_index); + + if (!force && + old_policy.algorithm == el->primary_algorithm && + tpm2_pcr_prediction_equal(&old_policy.prediction, &new_prediction, el->primary_algorithm)) { + log_info("Prediction is identical to current policy, skipping update."); + return 0; /* NOP */ + } } - STRV_FOREACH(p, paths) { - _cleanup_free_ char *normalized_path = NULL, *pcrlock_file = NULL; + _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; + r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); + if (r < 0) + return r; - r = chase(*p, NULL, 0, &normalized_path, NULL); - if (r < 0) - return log_error_errno(r, "Failed to normal path '%s': %m", argv[1]); + if (!tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support PolicyAuthorizeNV command, refusing."); + if (!tpm2_supports_alg(tc, TPM2_ALG_SHA256)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support SHA-256 hash algorithm, refusing."); - r = pcrlock_file_system_path(normalized_path, &pcrlock_file); - if (r < 0) - return r; + _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL; - r = unlink_pcrlock(pcrlock_file); + r = tpm2_deserialize( + tc, + &srk_blob, + &srk_handle); + if (r < 0) + return log_error_errno(r, "Failed to deserialize SRK TR: %m"); + if (r == 0) { + r = tpm2_get_or_create_srk( + tc, + /* session= */ NULL, + /* ret_public= */ NULL, + /* ret_name= */ NULL, + /* ret_qname= */ NULL, + &srk_handle); if (r < 0) - return r; + return log_error_errno(r, "Failed to install SRK: %m"); } - return 0; -} - -static int verb_lock_pe(int argc, char *argv[], void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; - _cleanup_close_ int fd = -EBADF; - int r; + _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; + r = tpm2_make_encryption_session( + tc, + srk_handle, + /* bind_key= */ &TPM2_HANDLE_NONE, + &encryption_session); + if (r < 0) + return log_error_errno(r, "Failed to allocate encryption session: %m"); - // FIXME: Maybe also generate a matching EV_EFI_VARIABLE_AUTHORITY records here for each signature that - // covers this PE plus its hash, as alternatives under the same component name + /* Acquire a recovery PIN, either from the user, or create a randomized one */ + _cleanup_(erase_and_freep) char *pin = NULL; + if (recovery_pin_mode == RECOVERY_PIN_QUERY) { + r = getenv_steal_erase("PIN", &pin); + if (r < 0) + return log_error_errno(r, "Failed to acquire PIN from environment: %m"); + if (r == 0) { + _cleanup_strv_free_erase_ char **l = NULL; - if (argc >= 2) { - fd = open(argv[1], O_RDONLY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); + AskPasswordRequest req = { + .tty_fd = -EBADF, + .message = "Recovery PIN", + .id = "pcrlock-recovery-pin", + .credential = "pcrlock.recovery-pin", + .until = USEC_INFINITY, + .hup_fd = -EBADF, + }; + + r = ask_password_auto( + &req, + /* flags= */ 0, + &l); + if (r < 0) + return log_error_errno(r, "Failed to query for recovery PIN: %m"); + + if (strv_length(l) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single PIN only."); + + pin = TAKE_PTR(l[0]); + l = mfree(l); + } + + } else if (!have_old_policy) { + r = make_recovery_key(&pin); + if (r < 0) + return log_error_errno(r, "Failed to generate a randomized recovery PIN: %m"); + + if (recovery_pin_mode == RECOVERY_PIN_SHOW) + printf("%s Selected recovery PIN is: %s%s%s\n", + glyph(GLYPH_LOCK_AND_KEY), + ansi_highlight_cyan(), + pin, + ansi_normal()); } - if (arg_pcr_mask == 0) - arg_pcr_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; + _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; + TPM2_HANDLE nv_index = 0; - for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; + r = tpm2_deserialize(tc, &nv_blob, &nv_handle); + if (r < 0) + return log_error_errno(r, "Failed to deserialize NV index TR: %m"); + if (r > 0) + nv_index = old_policy.nv_index; - if (!BIT_SET(arg_pcr_mask, i)) - continue; + TPM2B_AUTH auth = {}; + CLEANUP_ERASE(auth); - FOREACH_ARRAY(pa, tpm2_hash_algorithms, TPM2_N_HASH_ALGORITHMS) { - _cleanup_free_ void *hash = NULL; - size_t hash_size; - const EVP_MD *md; - const char *a; + if (pin) { + r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth); + if (r < 0) + return log_error_errno(r, "Failed to hash PIN: %m"); + } else { + assert(iovec_is_set(&pin_public)); + assert(iovec_is_set(&pin_private)); - assert_se(a = tpm2_hash_alg_to_string(*pa)); - assert_se(md = EVP_get_digestbyname(a)); + log_debug("Retrieving PIN from sealed data."); - r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &hash, &hash_size); - if (r < 0) - return log_error_errno(r, "Failed to hash PE binary: %m"); + usec_t pin_start_usec = now(CLOCK_MONOTONIC); - r = sd_json_variant_append_arraybo( - &digests, - SD_JSON_BUILD_PAIR("hashAlg", SD_JSON_BUILD_STRING(a)), - SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_size))); + _cleanup_(iovec_done_erase) struct iovec secret = {}; + for (unsigned attempt = 0;; attempt++) { + _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; + + r = tpm2_make_policy_session( + tc, + srk_handle, + encryption_session, + &policy_session); if (r < 0) - return log_error_errno(r, "Failed to build JSON digest object: %m"); - } + return log_error_errno(r, "Failed to allocate policy session: %m"); - r = sd_json_variant_append_arraybo( - &array, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(i)), - SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(digests))); - if (r < 0) - return log_error_errno(r, "Failed to append record object: %m"); - } + r = tpm2_policy_super_pcr( + tc, + policy_session, + &old_policy.prediction, + old_policy.algorithm); + if (r < 0) + return r; - return write_pcrlock(array, NULL); -} + r = tpm2_policy_authorize_nv( + tc, + policy_session, + nv_handle, + NULL); + if (r < 0) + return log_error_errno(r, "Failed to submit AuthorizeNV policy: %m"); -typedef void* SectionHashArray[_UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS]; + r = tpm2_unseal_data( + tc, + &pin_public, + &pin_private, + srk_handle, + policy_session, + encryption_session, + &secret); + if (r < 0 && (r != -ESTALE || attempt >= 16)) + return log_error_errno(r, "Failed to unseal PIN: %m"); + if (r == 0) + break; -static void section_hashes_array_done(SectionHashArray *array) { - assert(array); + log_debug("Trying again (attempt %u), as PCR values changed during unlock attempt.", attempt+1); + } - for (size_t i = 0; i < _UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS; i++) - free((*array)[i]); -} + if (secret.iov_len > sizeof_field(TPM2B_AUTH, buffer)) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Decrypted PIN too large."); -static int verb_lock_uki(int argc, char *argv[], void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *pe_digests = NULL; - _cleanup_(section_hashes_array_done) SectionHashArray section_hashes = {}; - size_t hash_sizes[TPM2_N_HASH_ALGORITHMS]; - _cleanup_close_ int fd = -EBADF; - int r; + auth = (TPM2B_AUTH) { + .size = secret.iov_len, + }; - if (arg_pcr_mask != 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PCR not configurable for UKI lock down."); + memcpy_safe(auth.buffer, secret.iov_base, secret.iov_len); - if (argc >= 2) { - fd = open(argv[1], O_RDONLY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); + log_info("Retrieved PIN from TPM2 in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_start_usec), 1)); } - for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { - _cleanup_free_ void *peh = NULL; - const EVP_MD *md; - const char *a; + /* Now convert the PIN into an HMAC-SHA256 key that we can use in PolicySigned to protect access to the nvindex with */ + _cleanup_(tpm2_handle_freep) Tpm2Handle *pin_handle = NULL; + r = tpm2_hmac_key_from_pin(tc, encryption_session, &auth, &pin_handle); + if (r < 0) + return r; - assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); - assert_se(md = EVP_get_digestbyname(a)); + TPM2B_NV_PUBLIC nv_public = {}; + usec_t nv_index_start_usec = now(CLOCK_MONOTONIC); - r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &peh, hash_sizes + i); + if (!iovec_is_set(&nv_blob)) { + _cleanup_(Esys_Freep) TPM2B_NAME *pin_name = NULL; + r = tpm2_get_name( + tc, + pin_handle, + &pin_name); if (r < 0) - return log_error_errno(r, "Failed to hash PE binary: %m"); + return log_error_errno(r, "Failed to get name of PIN from TPM2: %m"); - r = sd_json_variant_append_arraybo( - &pe_digests, - SD_JSON_BUILD_PAIR("hashAlg", SD_JSON_BUILD_STRING(a)), - SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(peh, hash_sizes[i]))); + TPM2B_DIGEST recovery_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + r = tpm2_calculate_policy_signed(&recovery_policy_digest, pin_name); if (r < 0) - return log_error_errno(r, "Failed to build JSON digest object: %m"); + return log_error_errno(r, "Failed to calculate PolicySigned policy: %m"); - r = uki_hash(fd < 0 ? STDIN_FILENO : fd, md, section_hashes + (i * _UNIFIED_SECTION_MAX), hash_sizes + i); + log_debug("Allocating NV index to write PCR policy to..."); + r = tpm2_define_policy_nv_index( + tc, + encryption_session, + arg_nv_index, + &recovery_policy_digest, + &nv_index, + &nv_handle, + &nv_public); + if (r == -EEXIST) + return log_error_errno(r, "NV index 0x%" PRIx32 " already allocated.", arg_nv_index); if (r < 0) - return log_error_errno(r, "Failed to UKI hash PE binary: %m"); + return log_error_errno(r, "Failed to allocate NV index: %m"); } - r = sd_json_variant_append_arraybo( - &array, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(TPM2_PCR_BOOT_LOADER_CODE)), - SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(pe_digests))); + _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; + r = tpm2_make_policy_session( + tc, + srk_handle, + encryption_session, + &policy_session); if (r < 0) - return log_error_errno(r, "Failed to append record object: %m"); - - for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *section_digests = NULL, *record = NULL; + return log_error_errno(r, "Failed to allocate policy session: %m"); - if (!unified_section_measure(section)) - continue; + r = tpm2_policy_signed_hmac_sha256( + tc, + policy_session, + pin_handle, + &IOVEC_MAKE(auth.buffer, auth.size), + /* ret_policy_digest= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to submit authentication value policy: %m"); - for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { - const char *a; - void *hash; + log_debug("Calculating new PCR policy to write..."); + TPM2B_DIGEST new_super_pcr_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); - hash = section_hashes[i * _UNIFIED_SECTION_MAX + section]; - if (!hash) - continue; + usec_t pcr_policy_start_usec = now(CLOCK_MONOTONIC); - assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); + r = tpm2_calculate_policy_super_pcr( + &new_prediction, + el->primary_algorithm, + &new_super_pcr_policy_digest); + if (r < 0) + return log_error_errno(r, "Failed to calculate super PCR policy: %m"); - r = sd_json_variant_append_arraybo( - §ion_digests, - SD_JSON_BUILD_PAIR("hashAlg", SD_JSON_BUILD_STRING(a)), - SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_sizes[i]))); - if (r < 0) - return log_error_errno(r, "Failed to build JSON digest object: %m"); - } + log_info("Calculated new PCR policy in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pcr_policy_start_usec), 1)); - if (!section_digests) - continue; + log_debug("Writing new PCR policy to NV index..."); + r = tpm2_write_policy_nv_index( + tc, + policy_session, + nv_index, + nv_handle, + &new_super_pcr_policy_digest); + if (r < 0) + return log_error_errno(r, "Failed to write to NV index: %m"); - /* So we have digests for this section, hence generate a record for the section name first. */ - r = make_pcrlock_record(TPM2_PCR_KERNEL_BOOT /* =11 */, unified_sections[section], strlen(unified_sections[section]) + 1, &record); + log_info("Updated NV index in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), nv_index_start_usec), 1)); + + assert(iovec_is_set(&pin_public) == iovec_is_set(&pin_private)); + if (!iovec_is_set(&pin_public)) { + TPM2B_DIGEST authnv_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + + r = tpm2_calculate_policy_authorize_nv(&nv_public, &authnv_policy_digest); if (r < 0) - return r; + return log_error_errno(r, "Failed to calculate AuthorizeNV policy: %m"); - r = sd_json_variant_append_array(&array, record); + struct iovec data = { + .iov_base = auth.buffer, + .iov_len = auth.size, + }; + + usec_t pin_seal_start_usec = now(CLOCK_MONOTONIC); + + log_debug("Sealing PIN to NV index policy..."); + r = tpm2_seal_data( + tc, + &data, + srk_handle, + encryption_session, + &authnv_policy_digest, + &pin_public, + &pin_private); if (r < 0) - return log_error_errno(r, "Failed to append JSON record array: %m"); + return log_error_errno(r, "Failed to seal PIN to NV auth policy: %m"); - /* And then append a record for the section contents digests as well */ - r = sd_json_variant_append_arraybo( - &array, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(TPM2_PCR_KERNEL_BOOT /* =11 */)), - SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(section_digests))); + log_info("Sealed PIN in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_seal_start_usec), 1)); + } + + if (!iovec_is_set(&nv_blob)) { + r = tpm2_serialize(tc, nv_handle, &nv_blob); if (r < 0) - return log_error_errno(r, "Failed to append record object: %m"); + return log_error_errno(r, "Failed to serialize NV index TR: %m"); } - return write_pcrlock(array, NULL); -} + if (!iovec_is_set(&srk_blob)) { + r = tpm2_serialize(tc, srk_handle, &srk_blob); + if (r < 0) + return log_error_errno(r, "Failed to serialize SRK index TR: %m"); + } -static int verb_lock_kernel_cmdline(int argc, char *argv[], void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; - _cleanup_free_ char *cmdline = NULL; - int r; + if (!iovec_is_set(&nv_public_blob)) { + r = tpm2_marshal_nv_public(&nv_public, &nv_public_blob.iov_base, &nv_public_blob.iov_len); + if (r < 0) + return log_error_errno(r, "Failed to marshal NV public area: %m"); + } - if (argc > 1) { - if (empty_or_dash(argv[1])) - r = read_full_stream(stdin, &cmdline, NULL); - else - r = read_full_file(argv[1], &cmdline, NULL); - } else - r = proc_cmdline(&cmdline); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_configuration_json = NULL; + r = sd_json_buildo( + &new_configuration_json, + SD_JSON_BUILD_PAIR_STRING("pcrBank", tpm2_hash_alg_to_string(el->primary_algorithm)), + SD_JSON_BUILD_PAIR_VARIANT("pcrValues", new_prediction_json), + SD_JSON_BUILD_PAIR_INTEGER("nvIndex", nv_index), + JSON_BUILD_PAIR_IOVEC_BASE64("nvHandle", &nv_blob), + JSON_BUILD_PAIR_IOVEC_BASE64("nvPublic", &nv_public_blob), + JSON_BUILD_PAIR_IOVEC_BASE64("srkHandle", &srk_blob), + JSON_BUILD_PAIR_IOVEC_BASE64("pinPublic", &pin_public), + JSON_BUILD_PAIR_IOVEC_BASE64("pinPrivate", &pin_private)); if (r < 0) - return log_error_errno(r, "Failed to read cmdline: %m"); - - delete_trailing_chars(cmdline, "\n"); - - _cleanup_free_ char16_t *u = NULL; - u = utf8_to_utf16(cmdline, SIZE_MAX); - if (!u) - return log_oom(); + return log_error_errno(r, "Failed to generate JSON: %m"); - r = make_pcrlock_record(TPM2_PCR_KERNEL_INITRD /* = 9 */, u, char16_strlen(u)*2+2, &record); + _cleanup_free_ char *text = NULL; + r = sd_json_variant_format(new_configuration_json, 0, &text); if (r < 0) - return r; + return log_error_errno(r, "Failed to format new configuration to JSON: %m"); - r = sd_json_variant_new_array(&array, &record, 1); + const char *path = arg_policy_path ?: (in_initrd() ? "/run/systemd/pcrlock.json" : "/var/lib/systemd/pcrlock.json"); + r = write_string_file(path, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL); if (r < 0) - return log_error_errno(r, "Failed to create record array: %m"); + return log_error_errno(r, "Failed to write new configuration to '%s': %m", path); - r = write_pcrlock(array, PCRLOCK_KERNEL_CMDLINE_PATH); + if (!arg_policy_path && !in_initrd()) { + r = remove_policy_file("/run/systemd/pcrlock.json"); + if (r < 0) + return r; + } + + log_info("Written new policy to '%s' and digest to TPM2 NV index 0x%x.", path, nv_index); + + (void) write_boot_policy_file(text); + + log_info("Overall time spent: %s", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), start_usec), 1)); + + return 1; /* installed new policy */ +} + +VERB_NOARG(verb_make_policy, "make-policy", + "Predict PCR values and generate TPM2 policy from it"); +static int verb_make_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + r = make_policy(arg_force, arg_recovery_pin); if (r < 0) return r; return 0; } -static int verb_unlock_kernel_cmdline(int argc, char *argv[], void *userdata) { - return unlink_pcrlock(PCRLOCK_KERNEL_CMDLINE_PATH); -} - -static int verb_lock_kernel_initrd(int argc, char *argv[], void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; - _cleanup_fclose_ FILE *f = NULL; - uint32_t pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_INITRD; +static int undefine_policy_nv_index( + uint32_t nv_index, + const struct iovec *nv_blob, + const struct iovec *srk_blob) { int r; - if (argc >= 2) { - f = fopen(argv[1], "re"); - if (!f) - return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); - } + assert(nv_blob); + assert(srk_blob); - r = make_pcrlock_record_from_stream(pcr_mask, f ?: stdin, &records); + _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; + r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); if (r < 0) return r; - r = write_pcrlock(records, PCRLOCK_KERNEL_INITRD_PATH); + _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL; + r = tpm2_deserialize( + tc, + srk_blob, + &srk_handle); + if (r < 0) + return log_error_errno(r, "Failed to deserialize SRK TR: %m"); + + _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; + r = tpm2_deserialize( + tc, + nv_blob, + &nv_handle); + if (r < 0) + return log_error_errno(r, "Failed to deserialize NV TR: %m"); + + _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; + r = tpm2_make_encryption_session( + tc, + srk_handle, + /* bind_key= */ &TPM2_HANDLE_NONE, + &encryption_session); + if (r < 0) + return r; + + r = tpm2_undefine_nv_index( + tc, + encryption_session, + nv_index, + nv_handle); if (r < 0) return r; + log_info("Removed NV index 0x%x", nv_index); return 0; } -static int verb_unlock_kernel_initrd(int argc, char *argv[], void *userdata) { - return unlink_pcrlock(PCRLOCK_KERNEL_INITRD_PATH); -} +static int remove_policy(void) { + int ret = 0, r; -static int event_log_reduce_to_safe_pcrs(EventLog *el, uint32_t *pcrs) { - _cleanup_free_ char *dropped = NULL, *kept = NULL; + _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {}; + r = tpm2_pcrlock_policy_load(arg_policy_path, &policy); + if (r == 0) { + log_info("No policy found."); + return 0; + } - assert(el); - assert(pcrs); + if (r < 0) + log_notice("Failed to load old policy file, assuming it is corrupted, removing."); + else { + r = undefine_policy_nv_index(policy.nv_index, &policy.nv_handle, &policy.srk_handle); + if (r < 0) + log_notice("Failed to remove NV index, assuming data out of date, removing policy file."); - /* When we compile a new PCR policy we don't want to bind to PCRs which are fishy for one of three - * reasons: - * - * 1. The PCR value doesn't match the event log - * 2. The event log for the PCR contains measurements we don't know responsible components for - * 3. The event log for the PCR does not contain measurements for components we know - * - * This function checks for the three conditions and drops the PCR from the mask. - */ + RET_GATHER(ret, r); + } - for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { + if (arg_policy_path) + RET_GATHER(ret, remove_policy_file(arg_policy_path)); + else { + RET_GATHER(ret, remove_policy_file("/var/lib/systemd/pcrlock.json")); + RET_GATHER(ret, remove_policy_file("/run/systemd/pcrlock.json")); + } - if (!BIT_SET(*pcrs, pcr)) - continue; + _cleanup_free_ char *boot_policy_file = NULL; + r = determine_boot_policy_file(&boot_policy_file, /* ret_credential_name= */ NULL); + if (r == 0) + log_info("Did not find XBOOTLDR/ESP partition, not removing boot policy file."); + else if (r > 0) { + RET_GATHER(ret, remove_policy_file(boot_policy_file)); + } else + RET_GATHER(ret, r); - if (!event_log_pcr_checks_out(el, el->registers + pcr)) { - log_notice("PCR %" PRIu32 " (%s) value does not match event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); - goto drop; - } + return ret; +} - if (!el->registers[pcr].fully_recognized) { - log_notice("PCR %" PRIu32 " (%s) event log contains unrecognized measurements. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); - goto drop; - } +VERB_NOARG(verb_remove_policy, "remove-policy", + "Remove TPM2 policy"); +static int verb_remove_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { + return remove_policy(); +} - if (BIT_SET(el->missing_component_pcrs, pcr)) { - log_notice("PCR %" PRIu32 " (%s) is touched by component we can't find in event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); - goto drop; - } +static int test_tpm2_support_pcrlock(Tpm2Support *ret) { + int r; - log_info("PCR %" PRIu32 " (%s) matches event log and fully consists of recognized measurements. Including in set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); + assert(ret); - if (strextendf_with_separator(&kept, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0) - return log_oom(); + /* First check basic support */ + Tpm2Support s = tpm2_support(); - continue; + /* If basic support is available, let's also check the things we need for systemd-pcrlock */ + if (s == TPM2_SUPPORT_FULL) { + _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; + r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); + if (r < 0) + return r; - drop: - *pcrs &= ~(UINT32_C(1) << pcr); + /* We strictly need TPM2_CC_PolicyAuthorizeNV for systemd-pcrlock to work */ + SET_FLAG(s, TPM2_SUPPORT_AUTHORIZE_NV, tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV)); - if (strextendf_with_separator(&dropped, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0) - return log_oom(); - } + log_debug("PolicyAuthorizeNV supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_AUTHORIZE_NV))); - if (dropped) - log_notice("PCRs dropped from protection mask: %s", dropped); - else - log_debug("No PCRs dropped from protection mask."); + /* We also strictly need SHA-256 to work */ + SET_FLAG(s, TPM2_SUPPORT_SHA256, tpm2_supports_alg(tc, TPM2_ALG_SHA256)); - if (kept) - log_notice("PCRs in protection mask: %s", kept); - else - log_notice("No PCRs kept in protection mask."); + log_debug("SHA-256 supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_SHA256))); + } + *ret = s; return 0; } -static int pcr_prediction_add_result( - Tpm2PCRPrediction *context, - Tpm2PCRPredictionResult *result, - uint32_t pcr, - const char *path, - size_t offset) { - - _cleanup_free_ Tpm2PCRPredictionResult *copy = NULL; +VERB_NOARG(verb_is_supported, "is-supported", + "Tests if TPM2 supports necessary features"); +static int verb_is_supported(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; - assert(context); - assert(result); - - copy = newdup(Tpm2PCRPredictionResult, result, 1); - if (!copy) - return log_oom(); - - r = ordered_set_ensure_put(context->results + pcr, &tpm2_pcr_prediction_result_hash_ops, copy); - if (r == -EEXIST) /* Multiple identical results for the same PCR are totally expected */ - return 0; + Tpm2Support s; + r = test_tpm2_support_pcrlock(&s); if (r < 0) - return log_error_errno(r, "Failed to insert result into set: %m"); - - log_debug("Added prediction result %u for PCR %" PRIu32 " (path: %s)", ordered_set_size(context->results[pcr]), pcr, strempty(path)); - - TAKE_PTR(copy); - return 0; -} + return r; -static const EVP_MD* evp_from_tpm2_alg(uint16_t alg) { - const char *name; + if (!arg_quiet) { + if (s == (TPM2_SUPPORT_FULL|TPM2_SUPPORT_API_PCRLOCK)) + printf("%syes%s\n", ansi_green(), ansi_normal()); + else if (FLAGS_SET(s, TPM2_SUPPORT_FULL)) + printf("%sobsolete%s\n", ansi_red(), ansi_normal()); + else if (s == TPM2_SUPPORT_NONE) + printf("%sno%s\n", ansi_red(), ansi_normal()); + else + printf("%spartial%s\n", ansi_yellow(), ansi_normal()); + } - name = tpm2_hash_alg_to_string(alg); - if (!name) - return NULL; + assert_cc((TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK) <= 255); /* make sure this is safe to use as process exit status */ - return EVP_get_digestbyname(name); + return ~s & (TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK); } -static int event_log_component_variant_calculate( - Tpm2PCRPrediction *context, - Tpm2PCRPredictionResult *result, - EventLogComponent *component, - EventLogComponentVariant *variant, - uint32_t pcr, - const char *path) { - +static int event_log_record_is_action_calling_efi_app(const EventLogRecord *rec) { + _cleanup_free_ char *d = NULL; int r; - assert(context); - assert(result); - assert(component); - assert(variant); - - FOREACH_ARRAY(rr, variant->records, variant->n_records) { - EventLogRecord *rec = *rr; - - if (!EVENT_LOG_RECORD_IS_PCR(rec)) - continue; + assert(rec); - if (rec->pcr != pcr) - continue; + /* Recognizes the special EV_EFI_ACTION that is issues when the firmware passes control to the boot loader. */ - for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { - _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL; - EventLogRecordBank *b; + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + return false; - if (result->hash[i].size <= 0) /* already invalidated */ - continue; + if (rec->pcr != TPM2_PCR_BOOT_LOADER_CODE) + return false; - b = event_log_record_find_bank(rec, tpm2_hash_algorithms[i]); - if (!b) { - /* Can't calculate, hence invalidate */ - result->hash[i] = (TPM2B_DIGEST) {}; - continue; - } + if (rec->firmware_event_type != EV_EFI_ACTION) + return false; - md_ctx = EVP_MD_CTX_new(); - if (!md_ctx) - return log_oom(); + if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) /* Insist the record is consistent */ + return false; - const EVP_MD *md = ASSERT_PTR(evp_from_tpm2_alg(tpm2_hash_algorithms[i])); + r = make_cstring(rec->firmware_payload, rec->firmware_payload_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, &d); + if (r < 0) + return r; - int sz = EVP_MD_size(md); - assert(sz > 0); - assert((size_t) sz <= sizeof_field(TPM2B_DIGEST, buffer)); + return streq(d, "Calling EFI Application from Boot Option"); +} - assert(sz == tpm2_hash_alg_to_size(tpm2_hash_algorithms[i])); +static void enable_json_sse(void) { + /* We shall write this to a single output stream? We have to output two files, hence try to be smart + * and enable JSON SSE */ - assert(result->hash[i].size == (size_t) sz); - assert(b->hash.size == (size_t) sz); + if (!arg_pcrlock_path && arg_pcrlock_auto) + return; - if (EVP_DigestInit_ex(md_ctx, md, NULL) != 1) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize message digest."); + if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_SSE)) + return; - if (EVP_DigestUpdate(md_ctx, result->hash[i].buffer, sz) != 1) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash bank value."); + log_notice("Enabling JSON_SEQ mode, since writing two .pcrlock files to single output."); + arg_json_format_flags |= SD_JSON_FORMAT_SSE; +} - if (EVP_DigestUpdate(md_ctx, b->hash.buffer, sz) != 1) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data value."); +static bool event_log_record_is_separator(const EventLogRecord *rec) { + assert(rec); - unsigned l = (unsigned) sz; - if (EVP_DigestFinal_ex(md_ctx, result->hash[i].buffer, &l) != 1) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize message digest."); + /* Recognizes EV_SEPARATOR events */ - assert(l == (unsigned) sz); - } + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + return false; - /* This is a valid result once we hit the start location */ - if (arg_location_start && strcmp(component->id, arg_location_start) >= 0) { - r = pcr_prediction_add_result(context, result, pcr, path, rr - variant->records); - if (r < 0) - return r; - } - } + if (rec->firmware_event_type != EV_SEPARATOR) + return false; - return 0; + return rec->event_payload_valid == EVENT_PAYLOAD_VALID_YES; /* Insist the record is consistent */ } -static int event_log_predict_pcrs( - EventLog *el, - Tpm2PCRPrediction *context, - Tpm2PCRPredictionResult *parent_result, - size_t component_index, - uint32_t pcr, - const char *path) { +VERB_GROUP("Protections"); - EventLogComponent *component; - int count = 0, r; +VERB(verb_lock_firmware, "lock-firmware-code", NULL, VERB_ANY, 2, 0, + "Generate a .pcrlock file from current firmware code"); +static int verb_lock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array_early = NULL, *array_late = NULL; + _cleanup_(event_log_freep) EventLog *el = NULL; + uint32_t always_mask, separator_mask, separator_seen_mask = 0, action_seen_mask = 0; + const char *default_pcrlock_early_path, *default_pcrlock_late_path; + int r; - assert(el); - assert(context); - assert(parent_result); + enable_json_sse(); - /* Check if we reached the end of the components, generate a result, and backtrack */ - if (component_index >= el->n_components || - (arg_location_end && strcmp(el->components[component_index]->id, arg_location_end) > 0)) { - r = pcr_prediction_add_result(context, parent_result, pcr, path, /* offset= */ 0); - if (r < 0) - return r; + /* The PCRs we intend to cover. Note that we measure firmware, external *and* boot loader code/config + * here – but the latter only until the "separator" events are seen, which tell us where transition + * into OS boot loader happens. This reflects the fact that on some systems the firmware already + * measures some firmware-supplied apps into PCR 4. (e.g. Thinkpad X1 Gen9) */ + if (endswith(argv[0], "firmware-code")) { + always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CODE) | /* → 0 */ + (UINT32_C(1) << TPM2_PCR_EXTERNAL_CODE); /* → 2 */ - return 1; - } + separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; /* → 4 */ - component = ASSERT_PTR(el->components[component_index]); + default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH; + default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH; + } else { + assert(endswith(argv[0], "firmware-config")); + always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CONFIG) | /* → 1 */ + (UINT32_C(1) << TPM2_PCR_EXTERNAL_CONFIG); /* → 3 */ - FOREACH_ARRAY(ii, component->variants, component->n_variants) { - _cleanup_free_ Tpm2PCRPredictionResult *result = NULL; - EventLogComponentVariant *variant = *ii; - _cleanup_free_ char *subpath = NULL; + separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CONFIG; /* → 5 */ - /* Operate on a copy of the result */ + default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH; + default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH; + } - if (path) - subpath = strjoin(path, ":", component->id); - else - subpath = strdup(component->id); - if (!subpath) - return log_oom(); + el = event_log_new(); + if (!el) + return log_oom(); - if (!streq(component->id, variant->id)) - if (!strextend(&subpath, "@", variant->id)) - return log_oom(); + r = event_log_add_algorithms_from_environment(el); + if (r < 0) + return r; - result = newdup(Tpm2PCRPredictionResult, parent_result, 1); - if (!result) - return log_oom(); + r = event_log_load(el); + if (r < 0) + return r; - r = event_log_component_variant_calculate( - context, - result, - component, - variant, - pcr, - subpath); - if (r < 0) - return r; + r = event_log_read_pcrs(el); + if (r < 0) + return r; - r = event_log_predict_pcrs( - el, - context, - result, - component_index + 1, /* Next component */ - pcr, - subpath); - if (r < 0) - return r; + r = event_log_calculate_pcrs(el); + if (r < 0) + return r; - count += r; - } + r = event_log_validate_record_hashes(el); + if (r < 0) + return r; - return count; -} + /* Before we base anything on the event log records for any of the selected PCRs, let's check that + * the event log state checks out for them. */ -static ssize_t event_log_calculate_component_combinations(EventLog *el) { - ssize_t count = 1; - assert(el); + r = event_log_pcr_mask_checks_out(el, always_mask|separator_mask); + if (r < 0) + return r; - FOREACH_ARRAY(cc, el->components, el->n_components) { - EventLogComponent *c = *cc; + // FIXME: before doing this, validate ahead-of-time that EV_SEPARATOR records exist for all entries, + // and exactly once - assert(c->n_variants > 0); - - /* Overflow check */ - if (c->n_variants > (size_t) (SSIZE_MAX/count)) - return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many component combinations."); - count *= c->n_variants; - } - - return count; -} - -static int event_log_show_predictions(Tpm2PCRPrediction *context, uint16_t alg) { - int r; - - assert(context); - - pager_open(arg_pager_flags); - - if (sd_json_format_enabled(arg_json_format_flags)) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + FOREACH_ARRAY(rr, el->records, el->n_records) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; + EventLogRecord *rec = *rr; + uint32_t bit = UINT32_C(1) << rec->pcr; - for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL; + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + continue; - r = tpm2_pcr_prediction_to_json( - context, - tpm2_hash_algorithms[i], - &aj); - if (r < 0) - return r; + if (!FLAGS_SET(always_mask, bit) && + !(FLAGS_SET(separator_mask, bit) && !FLAGS_SET(separator_seen_mask|action_seen_mask, bit))) + continue; - if (sd_json_variant_elements(aj) == 0) - continue; + /* If we hit the separator record, we stop processing the PCRs listed in `separator_mask` */ + if (event_log_record_is_separator(rec)) { + separator_seen_mask |= bit; + continue; + } - r = sd_json_variant_set_field( - &j, - tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]), - aj); - if (r < 0) - return log_error_errno(r, "Failed to add prediction bank to object: %m"); + /* If we hit the special "Calling EFI Application from Boot Option" action we treat this the + * same as a separator here, as that's where firmware passes control to boot loader. Note + * that some EFI implementations forget to generate one of them. */ + r = event_log_record_is_action_calling_efi_app(rec); + if (r < 0) + return log_error_errno(r, "Failed to check if event is 'Calling EFI Application from Boot Option' action: %m"); + if (r > 0) { + action_seen_mask |= bit; + continue; } - if (!j) { - r = sd_json_variant_new_object(&j, NULL, 0); + LIST_FOREACH(banks, bank, rec->banks) { + r = sd_json_variant_append_arraybo( + &digests, + SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), + SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size))); if (r < 0) - return log_error_errno(r, "Failed to allocated empty object: %m"); + return log_error_errno(r, "Failed to build digests array: %m"); } - sd_json_variant_dump(j, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL); - return 0; + r = sd_json_variant_append_arraybo( + FLAGS_SET(separator_seen_mask, bit) ? &array_late : &array_early, + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); + if (r < 0) + return log_error_errno(r, "Failed to build record array: %m"); } - for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { - Tpm2PCRPredictionResult *result; - if (!BIT_SET(context->pcrs, pcr)) - continue; - - if (ordered_set_isempty(context->results[pcr])) { - printf("No results for PCR %u (%s).\n", pcr, tpm2_pcr_index_to_string(pcr)); - continue; - } - - printf("%sResults for PCR %u (%s):%s\n", ansi_underline(), pcr, tpm2_pcr_index_to_string(pcr), ansi_normal()); - - ORDERED_SET_FOREACH(result, context->results[pcr]) { + r = write_pcrlock(array_early, default_pcrlock_early_path); + if (r < 0) + return r; - _cleanup_free_ char *aa = NULL, *h = NULL; - const char *a; + return write_pcrlock(array_late, default_pcrlock_late_path); +} - TPM2B_DIGEST *hash = tpm2_pcr_prediction_result_get_hash(result, alg); - if (!hash) - continue; +VERB_NOARG(verb_unlock_firmware, "unlock-firmware-code", + "Remove .pcrlock file for firmware code"); +static int verb_unlock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { + const char *default_pcrlock_early_path, *default_pcrlock_late_path; + int r; - a = ASSERT_PTR(tpm2_hash_alg_to_string(alg)); - aa = strdup(a); - if (!aa) - return log_oom(); + if (endswith(argv[0], "firmware-code")) { + default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH; + default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH; + } else { + default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH; + default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH; + } - ascii_strlower(aa); + r = unlink_pcrlock(default_pcrlock_early_path); + if (r < 0) + return r; - h = hexmem(hash->buffer, hash->size); - if (!h) - return log_oom(); + if (arg_pcrlock_path) /* if the path is specified don't delete the same thing twice */ + return 0; - printf(" %s%-6s:%s %s\n", ansi_grey(), aa, ansi_normal(), h); - } - } + r = unlink_pcrlock(default_pcrlock_late_path); + if (r < 0) + return r; return 0; } -static int tpm2_pcr_prediction_run( - EventLog *el, - Tpm2PCRPrediction *context) { +VERB(verb_lock_firmware, "lock-firmware-config", NULL, VERB_ANY, 2, 0, + "Generate a .pcrlock file from current firmware configuration"); + +VERB_NOARG(verb_unlock_firmware, "unlock-firmware-config", + "Remove .pcrlock file for firmware configuration"); + +VERB_NOARG(verb_lock_secureboot_policy, "lock-secureboot-policy", + "Generate a .pcrlock file from current SecureBoot policy"); +static int verb_lock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { + static const struct { + sd_id128_t id; + const char *name; + int synthesize_empty; /* 0 → fail, > 0 → synthesize empty db, < 0 → skip */ + } variables[] = { + { EFI_VENDOR_GLOBAL, "SecureBoot", 0 }, + { EFI_VENDOR_GLOBAL, "PK", 1 }, + { EFI_VENDOR_GLOBAL, "KEK", 1 }, + { EFI_VENDOR_DATABASE, "db", 1 }, + { EFI_VENDOR_DATABASE, "dbx", 1 }, + { EFI_VENDOR_DATABASE, "dbt", -1 }, + { EFI_VENDOR_DATABASE, "dbr", -1 }, + }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; int r; - assert(el); - assert(context); + /* Generates expected records from the current SecureBoot state, as readable in the EFI variables + * right now. */ - for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { - _cleanup_free_ Tpm2PCRPredictionResult *result = NULL; + FOREACH_ELEMENT(vv, variables) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL; - if (!BIT_SET(context->pcrs, pcr)) - continue; + _cleanup_free_ char *name = NULL; + if (asprintf(&name, "%s-" SD_ID128_UUID_FORMAT_STR, vv->name, SD_ID128_FORMAT_VAL(vv->id)) < 0) + return log_oom(); - result = new0(Tpm2PCRPredictionResult, 1); - if (!result) + _cleanup_free_ void *data = NULL; + size_t data_size; + r = efi_get_variable(name, NULL, &data, &data_size); + if (r < 0) { + if (r != -ENOENT || vv->synthesize_empty == 0) + return log_error_errno(r, "Failed to read EFI variable '%s': %m", name); + if (vv->synthesize_empty < 0) + continue; + + /* If the main database variables are not set we don't consider this an error, but + * measure an empty database instead. */ + log_debug("EFI variable %s is not set, synthesizing empty variable for measurement.", name); + data_size = 0; + } + + _cleanup_free_ char16_t* name16 = utf8_to_utf16(vv->name, SIZE_MAX); + if (!name16) return log_oom(); + size_t name16_bytes = char16_strlen(name16) * 2; - for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) - event_log_initial_pcr_state(el, pcr, tpm2_hash_alg_to_size(tpm2_hash_algorithms[i]), result->hash + i); + size_t vdata_size = offsetof(UEFI_VARIABLE_DATA, unicodeName) + name16_bytes + data_size; + _cleanup_free_ UEFI_VARIABLE_DATA *vdata = malloc(vdata_size); + if (!vdata) + return log_oom(); - r = event_log_predict_pcrs( - el, - context, - result, - /* component_index= */ 0, - pcr, - /* path= */ NULL); + *vdata = (UEFI_VARIABLE_DATA) { + .unicodeNameLength = name16_bytes / 2, + .variableDataLength = data_size, + }; + + efi_id128_to_guid(vv->id, vdata->variableName); + memcpy(mempcpy(vdata->unicodeName, name16, name16_bytes), data, data_size); + + r = make_pcrlock_record(TPM2_PCR_SECURE_BOOT_POLICY /* =7 */, vdata, vdata_size, &record); if (r < 0) return r; + + r = sd_json_variant_append_array(&array, record); + if (r < 0) + return log_error_errno(r, "Failed to append to JSON array: %m"); } - return 0; + return write_pcrlock(array, PCRLOCK_SECUREBOOT_POLICY_PATH); } -static int verb_predict(int argc, char *argv[], void *userdata) { - _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction context = { - arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK, - }; - _cleanup_(event_log_freep) EventLog *el = NULL; - ssize_t count; +VERB_NOARG(verb_unlock_secureboot_policy, "unlock-secureboot-policy", + "Remove .pcrlock file for SecureBoot policy"); +static int verb_unlock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_SECUREBOOT_POLICY_PATH); +} + +static int event_log_record_is_secureboot_variable(EventLogRecord *rec, sd_id128_t uuid, const char *name) { + _cleanup_free_ char *found_name = NULL; + sd_id128_t found_uuid; int r; - r = event_log_load_and_process(&el); - if (r < 0) - return r; + assert(rec); + assert(name); - count = event_log_calculate_component_combinations(el); - if (count < 0) - return count; + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + return false; - log_info("%zi combinations of components.", count); + if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY) + return false; - r = event_log_reduce_to_safe_pcrs(el, &context.pcrs); - if (r < 0) - return r; + if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) + return false; - r = tpm2_pcr_prediction_run(el, &context); + if (rec->firmware_event_type != EV_EFI_VARIABLE_DRIVER_CONFIG) + return false; + + r = event_log_record_parse_variable_data(rec, &found_uuid, &found_name); + if (r == -EBADMSG) + return false; if (r < 0) return r; - return event_log_show_predictions(&context, el->primary_algorithm); -} + if (!sd_id128_equal(found_uuid, uuid)) + return false; -static int remove_policy_file(const char *path) { - assert(path); + return streq(found_name, name); +} - if (unlink(path) < 0) { - if (errno == ENOENT) - return 0; +static bool event_log_record_is_secureboot_authority(EventLogRecord *rec) { + assert(rec); - return log_error_errno(errno, "Failed to remove policy file '%s': %m", path); - } + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + return false; - log_info("Removed policy file '%s'.", path); - return 1; -} - -static int determine_boot_policy_file(char **ret_path, char **ret_credential_name) { - int r; + if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY) + return false; - _cleanup_free_ char *path = NULL; - r = get_global_boot_credentials_path(&path); - if (r < 0) - return r; - if (r == 0) { - if (ret_path) - *ret_path = NULL; - if (ret_credential_name) - *ret_credential_name = NULL; - return 0; /* not found! */ - } + if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) + return false; - sd_id128_t machine_id; - r = sd_id128_get_machine(&machine_id); - if (r < 0) - return log_error_errno(r, "Failed to read machine ID: %m"); + return rec->firmware_event_type == EV_EFI_VARIABLE_AUTHORITY; +} - r = boot_entry_token_ensure( - /* root= */ NULL, - /* conf_root= */ NULL, - machine_id, - /* machine_id_is_random= */ false, - &arg_entry_token_type, - &arg_entry_token); - if (r < 0) - return r; +static int event_log_ensure_secureboot_consistency(EventLog *el) { + static const struct { + sd_id128_t id; + const char *name; + bool required; + } table[] = { + { EFI_VENDOR_GLOBAL, "SecureBoot", true }, + { EFI_VENDOR_GLOBAL, "PK", true }, + { EFI_VENDOR_GLOBAL, "KEK", true }, + { EFI_VENDOR_DATABASE, "db", true }, + { EFI_VENDOR_DATABASE, "dbx", true }, + { EFI_VENDOR_DATABASE, "dbt", false }, + { EFI_VENDOR_DATABASE, "dbr", false }, + // FIXME: ensure we also find the separator here + }; - _cleanup_free_ char *fn = strjoin("pcrlock.", arg_entry_token, ".cred"); - if (!fn) - return log_oom(); + EventLogRecord *records[ELEMENTSOF(table)] = {}; + EventLogRecord *first_authority = NULL; - if (!filename_is_valid(fn)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name '%s' would not be a valid file name, refusing.", fn); + assert(el); - _cleanup_free_ char *joined = NULL; - if (ret_path) { - joined = path_join(path, fn); - if (!joined) - return log_oom(); - } + /* Ensures that the PCR 7 records are complete and in order. Before we lock down PCR 7 we want to + * ensure its state is actually consistent. */ - _cleanup_free_ char *cn = NULL; - if (ret_credential_name) { - /* The .cred suffix of the file is stripped when PID 1 imports the credential, hence exclude it from - * the embedded credential name. */ - cn = strjoin("pcrlock.", arg_entry_token); - if (!cn) - return log_oom(); + FOREACH_ARRAY(rr, el->records, el->n_records) { + EventLogRecord *rec = *rr; + size_t found = SIZE_MAX; - ascii_strlower(cn); /* lowercase this file, no matter what, since stored on VFAT, and we don't want - * to run into case change incompatibilities */ - } + if (event_log_record_is_secureboot_authority(rec)) { + if (first_authority) + continue; - if (ret_path) - *ret_path = TAKE_PTR(joined); + first_authority = rec; + // FIXME: also check that each authority record's data is also listed in 'db' + continue; + } - if (ret_credential_name) - *ret_credential_name = TAKE_PTR(cn); + for (size_t i = 0; i < ELEMENTSOF(table); i++) + if (event_log_record_is_secureboot_variable(rec, table[i].id, table[i].name)) { + found = i; + break; + } + if (found == SIZE_MAX) + continue; - return 1; /* found! */ -} + /* Require the authority records always come *after* database measurements */ + if (first_authority) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "SecureBoot authority before variable, refusing."); -static int write_boot_policy_file(const char *json_text) { - _cleanup_free_ char *boot_policy_file = NULL, *credential_name = NULL; - int r; + /* Check for duplicates */ + if (records[found]) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate '%s' record, refusing.", rec->description); - assert(json_text); + /* Check for order */ + for (size_t j = found + 1; j < ELEMENTSOF(table); j++) + if (records[j]) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'%s' record before '%s' record, refusing.", records[j]->description, rec->description); - r = determine_boot_policy_file(&boot_policy_file, &credential_name); - if (r < 0) - return r; - if (r == 0) { - log_info("Did not find XBOOTLDR/ESP partition, not writing boot policy file."); - return 0; + records[found] = rec; } - _cleanup_(iovec_done) struct iovec encoded = {}; - r = encrypt_credential_and_warn( - CRED_AES256_GCM_BY_NULL, - credential_name, - now(CLOCK_REALTIME), - /* not_after= */ USEC_INFINITY, - /* tpm2_device= */ NULL, - /* tpm2_hash_pcr_mask= */ 0, - /* tpm2_pubkey_path= */ NULL, - /* tpm2_pubkey_pcr_mask= */ 0, - UID_INVALID, - &IOVEC_MAKE_STRING(json_text), - CREDENTIAL_ALLOW_NULL, - &encoded); - if (r < 0) - return log_error_errno(r, "Failed to encode policy as credential: %m"); - - r = write_base64_file_at( - AT_FDCWD, - boot_policy_file, - &encoded, - WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL); - if (r < 0) - return log_error_errno(r, "Failed to write boot policy file to '%s': %m", boot_policy_file); + /* Check for existence */ + for (size_t i = 0; i < ELEMENTSOF(table); i++) + if (table[i].required && !records[i]) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Required record '%s' not found, refusing.", table[i].name); - log_info("Written new boot policy to '%s'.", boot_policy_file); - return 1; + /* At this point we know that all required variables have been measured, in the right order. */ + return 0; } -static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) { +VERB_NOARG(verb_lock_secureboot_authority, "lock-secureboot-authority", + "Generate a .pcrlock file from current SecureBoot authority"); +static int verb_lock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; + _cleanup_(event_log_freep) EventLog *el = NULL; int r; - /* Here's how this all works: after predicting all possible PCR values for next boot (with - * alternatives) we'll calculate a policy from it as a combination of PolicyPCR + PolicyOR - * expressions. This is then stored in an NV index. When a component of the boot process is changed a - * new prediction is made and the NV index updated (which automatically invalidates any older - * policies). - * - * Whenever we want to lock an encrypted object (for example FDE) against this policy, we'll use a - * PolicyAuthorizeNV expression that pins the NV index in the policy, and permits access to any - * policies matching the current NV index contents. - * - * We grant world-readable read access to the NV index. Write access is controlled by a PIN (which we - * either generate locally or which the user can provide us with) which can also be used for - * recovery. This PIN is sealed to the TPM and is locked via PolicyAuthorizeNV to the NV index it - * protects (i.e. we dogfood 🌭 🐶 hard here). This means in order to update such a policy we need - * the policy to pass. - * - * Information about the used NV Index, the SRK of the TPM, the sealed PIN and the current PCR - * prediction data are stored in a JSON file in /var/lib/. In order to be able to unlock root disks - * this data must be also copied to the ESP so that it is available to the initrd. The data is not - * sensitive, as SRK and NV index are pinned by it, and the prediction data must match the NV index - * to be useful. */ + /* Lock down the EV_EFI_VARIABLE_AUTHORITY records from the existing log. Note that there's not too + * much value in locking this down too much, since it stores only the result of the primary database + * checks, and that's what we should bind policy to. Moreover it's hard to predict, since extension + * card firmware validation will result in additional records here. */ - usec_t start_usec = now(CLOCK_MONOTONIC); + if (!is_efi_secure_boot()) { + log_info("SecureBoot disabled, not generating authority .pcrlock file."); + return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH); + } - _cleanup_(event_log_freep) EventLog *el = NULL; - r = event_log_load_and_process(&el); + el = event_log_new(); + if (!el) + return log_oom(); + + r = event_log_add_algorithms_from_environment(el); if (r < 0) return r; - _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction new_prediction = { - arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK, - }; - r = event_log_reduce_to_safe_pcrs(el, &new_prediction.pcrs); + r = event_log_load(el); if (r < 0) return r; - if (!force && new_prediction.pcrs == 0) - log_notice("Set of PCRs to use for policy is empty. Generated policy will not provide any protection in its current form. Proceeding."); - - usec_t predict_start_usec = now(CLOCK_MONOTONIC); - - r = tpm2_pcr_prediction_run(el, &new_prediction); + r = event_log_read_pcrs(el); if (r < 0) return r; - log_info("Predicted future PCRs in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), predict_start_usec), 1)); - - _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_prediction_json = NULL; - r = tpm2_pcr_prediction_to_json(&new_prediction, el->primary_algorithm, &new_prediction_json); + r = event_log_calculate_pcrs(el); if (r < 0) return r; - if (DEBUG_LOGGING) - (void) sd_json_variant_dump(new_prediction_json, SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO, stderr, NULL); - - /* v257 and older mistakenly used --pcrlock= for the path. To keep backward compatibility, let's fallback to it when - * --policy= is unspecified but --pcrlock is specified. */ - if (!arg_policy_path && arg_pcrlock_path) { - log_notice("Specified --pcrlock= option for make-policy command. Please use --policy= instead."); + /* Before we base anything on the event log records, let's check that the event log state checks + * out. */ - arg_policy_path = strdup(arg_pcrlock_path); - if (!arg_policy_path) - return log_oom(); - } + r = event_log_pcr_mask_checks_out(el, UINT32_C(1) << TPM2_PCR_SECURE_BOOT_POLICY); + if (r < 0) + return r; - _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy old_policy = {}; - r = tpm2_pcrlock_policy_load(arg_policy_path, &old_policy); + r = event_log_validate_record_hashes(el); if (r < 0) return r; - bool have_old_policy = r > 0; + r = event_log_ensure_secureboot_consistency(el); + if (r < 0) + return r; - /* When we update the policy the old serializations for NV, SRK, PIN remain the same */ - _cleanup_(iovec_done) struct iovec - nv_blob = TAKE_STRUCT(old_policy.nv_handle), - nv_public_blob = TAKE_STRUCT(old_policy.nv_public), - srk_blob = TAKE_STRUCT(old_policy.srk_handle), - pin_public = TAKE_STRUCT(old_policy.pin_public), - pin_private = TAKE_STRUCT(old_policy.pin_private); + FOREACH_ARRAY(rr, el->records, el->n_records) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; + EventLogRecord *rec = *rr; - if (have_old_policy) { - if (arg_nv_index != 0 && old_policy.nv_index != arg_nv_index) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Stored policy references different NV index (0x%x) than specified (0x%x), refusing.", old_policy.nv_index, arg_nv_index); + if (!event_log_record_is_secureboot_authority(rec)) + continue; - if (!force && - old_policy.algorithm == el->primary_algorithm && - tpm2_pcr_prediction_equal(&old_policy.prediction, &new_prediction, el->primary_algorithm)) { - log_info("Prediction is identical to current policy, skipping update."); - return 0; /* NOP */ - } - } + log_debug("Locking down authority '%s'.", strna(rec->description)); - _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; - r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); - if (r < 0) - return r; + LIST_FOREACH(banks, bank, rec->banks) { + r = sd_json_variant_append_arraybo( + &digests, + SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), + SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size))); + if (r < 0) + return log_error_errno(r, "Failed to build digests array: %m"); + } - if (!tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV)) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support PolicyAuthorizeNV command, refusing."); - if (!tpm2_supports_alg(tc, TPM2_ALG_SHA256)) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support SHA-256 hash algorithm, refusing."); + r = sd_json_variant_append_arraybo( + &array, + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); + if (r < 0) + return log_error_errno(r, "Failed to build record array: %m"); + } - _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL; + return write_pcrlock(array, PCRLOCK_SECUREBOOT_AUTHORITY_PATH); +} - r = tpm2_deserialize( - tc, - &srk_blob, - &srk_handle); +VERB_NOARG(verb_unlock_secureboot_authority, "unlock-secureboot-authority", + "Remove .pcrlock file for SecureBoot authority"); +static int verb_unlock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH); +} + +VERB(verb_lock_gpt, "lock-gpt", "[DISK]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from GPT header"); +static int verb_lock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *record = NULL; + _cleanup_(sd_device_unrefp) sd_device *d = NULL; + uint8_t h[2 * 4096]; /* space for at least two 4K sectors. GPT header should definitely be in here */ + uint64_t start, n_members, member_size; + _cleanup_close_ int fd = -EBADF; + const GptHeader *p; + size_t found = 0; + ssize_t n; + int r; + + r = block_device_new_from_path( + argc >= 2 ? argv[1] : "/", + BLOCK_DEVICE_LOOKUP_WHOLE_DISK|BLOCK_DEVICE_LOOKUP_BACKING|BLOCK_DEVICE_LOOKUP_ORIGINATING, + &d); if (r < 0) - return log_error_errno(r, "Failed to deserialize SRK TR: %m"); - if (r == 0) { - r = tpm2_get_or_create_srk( - tc, - /* session= */ NULL, - /* ret_public= */ NULL, - /* ret_name= */ NULL, - /* ret_qname= */ NULL, - &srk_handle); - if (r < 0) - return log_error_errno(r, "Failed to install SRK: %m"); + return log_error_errno(r, "Failed to determine root block device: %m"); + + fd = sd_device_open(d, O_CLOEXEC|O_RDONLY|O_NOCTTY); + if (fd < 0) + return log_error_errno(fd, "Failed to open root block device: %m"); + + n = pread(fd, &h, sizeof(h), 0); + if (n < 0) + return log_error_errno(errno, "Failed to read GPT header of block device: %m"); + if ((size_t) n != sizeof(h)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read trying to read GPT header."); + + /* Try a couple of sector sizes */ + for (size_t sz = 512; sz <= 4096; sz <<= 1) { + assert(sizeof(h) >= sz * 2); + p = (const GptHeader*) (h + sz); /* 2nd sector */ + + if (!gpt_header_has_signature(p)) + continue; + + if (found != 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), + "Disk has partition table for multiple sector sizes, refusing."); + + found = sz; } - _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; - r = tpm2_make_encryption_session( - tc, - srk_handle, - /* bind_key= */ &TPM2_HANDLE_NONE, - &encryption_session); - if (r < 0) - return log_error_errno(r, "Failed to allocate encryption session: %m"); + if (found == 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Disk does not have GPT partition table, refusing."); - /* Acquire a recovery PIN, either from the user, or create a randomized one */ - _cleanup_(erase_and_freep) char *pin = NULL; - if (recovery_pin_mode == RECOVERY_PIN_QUERY) { - r = getenv_steal_erase("PIN", &pin); - if (r < 0) - return log_error_errno(r, "Failed to acquire PIN from environment: %m"); - if (r == 0) { - _cleanup_strv_free_erase_ char **l = NULL; + p = (const GptHeader*) (h + found); - AskPasswordRequest req = { - .tty_fd = -EBADF, - .message = "Recovery PIN", - .id = "pcrlock-recovery-pin", - .credential = "pcrlock.recovery-pin", - .until = USEC_INFINITY, - .hup_fd = -EBADF, - }; + if (le32toh(p->header_size) > found) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "GPT header size over long (%" PRIu32 "), refusing.", le32toh(p->header_size)); - r = ask_password_auto( - &req, - /* flags= */ 0, - &l); - if (r < 0) - return log_error_errno(r, "Failed to query for recovery PIN: %m"); + start = le64toh(p->partition_entry_lba); + if (start > UINT64_MAX / found) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Partition table start offset overflow, refusing."); - if (strv_length(l) != 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single PIN only."); + member_size = le32toh(p->size_of_partition_entry); + if (member_size < sizeof(GptPartitionEntry)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Partition entry size too short, refusing."); - pin = TAKE_PTR(l[0]); - l = mfree(l); - } + n_members = le32toh(p->number_of_partition_entries); + uint64_t member_bufsz = n_members * member_size; + if (member_bufsz > 1U*1024U*1024U) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Partition table size too large, refusing."); - } else if (!have_old_policy) { - r = make_recovery_key(&pin); - if (r < 0) - return log_error_errno(r, "Failed to generate a randomized recovery PIN: %m"); + member_bufsz = ROUND_UP(member_bufsz, found); - if (recovery_pin_mode == RECOVERY_PIN_SHOW) - printf("%s Selected recovery PIN is: %s%s%s\n", - glyph(GLYPH_LOCK_AND_KEY), - ansi_highlight_cyan(), - pin, - ansi_normal()); + _cleanup_free_ void *members = malloc(member_bufsz); + if (!members) + return log_oom(); + + n = pread(fd, members, member_bufsz, start * found); + if (n < 0) + return log_error_errno(errno, "Failed to read GPT partition table entries: %m"); + if ((size_t) n != member_bufsz) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading GPT partition table entries."); + + size_t vdata_size = le32toh(p->header_size) + sizeof(le64_t) + member_size * n_members; + _cleanup_free_ void *vdata = malloc0(vdata_size); + if (!vdata) + return log_oom(); + + void *n_measured_entries = mempcpy(vdata, p, sizeof(GptHeader)); /* n_measured_entries is a 64bit value */ + + void *qq = (uint8_t*) n_measured_entries + sizeof(le64_t); + + for (uint64_t i = 0; i < n_members; i++) { + const GptPartitionEntry *entry = (const GptPartitionEntry*) ((const uint8_t*) members + (member_size * i)); + + if (memeqzero(entry->partition_type_guid, sizeof(entry->partition_type_guid))) + continue; + + qq = mempcpy(qq, entry, member_size); + unaligned_write_le64(n_measured_entries, unaligned_read_le64(n_measured_entries) + 1); } - _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; - TPM2_HANDLE nv_index = 0; + vdata_size = (uint8_t*) qq - (uint8_t*) vdata; - r = tpm2_deserialize(tc, &nv_blob, &nv_handle); + r = make_pcrlock_record(TPM2_PCR_BOOT_LOADER_CONFIG /* =5 */, vdata, vdata_size, &record); if (r < 0) - return log_error_errno(r, "Failed to deserialize NV index TR: %m"); - if (r > 0) - nv_index = old_policy.nv_index; + return r; - TPM2B_AUTH auth = {}; - CLEANUP_ERASE(auth); + r = sd_json_variant_new_array(&array, &record, 1); + if (r < 0) + return log_error_errno(r, "Failed to append to JSON array: %m"); - if (pin) { - r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth); - if (r < 0) - return log_error_errno(r, "Failed to hash PIN: %m"); - } else { - assert(iovec_is_set(&pin_public)); - assert(iovec_is_set(&pin_private)); + return write_pcrlock(array, PCRLOCK_GPT_PATH); +} - log_debug("Retrieving PIN from sealed data."); +VERB_NOARG(verb_unlock_gpt, "unlock-gpt", + "Remove .pcrlock file for GPT header"); +static int verb_unlock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_GPT_PATH); +} - usec_t pin_start_usec = now(CLOCK_MONOTONIC); +VERB(verb_lock_pe, "lock-pe", "[BINARY]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from PE binary"); +static int verb_lock_pe(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; + _cleanup_close_ int fd = -EBADF; + int r; - _cleanup_(iovec_done_erase) struct iovec secret = {}; - for (unsigned attempt = 0;; attempt++) { - _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; + // FIXME: Maybe also generate a matching EV_EFI_VARIABLE_AUTHORITY records here for each signature that + // covers this PE plus its hash, as alternatives under the same component name - r = tpm2_make_policy_session( - tc, - srk_handle, - encryption_session, - &policy_session); - if (r < 0) - return log_error_errno(r, "Failed to allocate policy session: %m"); + if (argc >= 2) { + fd = open(argv[1], O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); + } - r = tpm2_policy_super_pcr( - tc, - policy_session, - &old_policy.prediction, - old_policy.algorithm); + if (arg_pcr_mask == 0) + arg_pcr_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; + + for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; + + if (!BIT_SET(arg_pcr_mask, i)) + continue; + + FOREACH_ARRAY(pa, tpm2_hash_algorithms, TPM2_N_HASH_ALGORITHMS) { + _cleanup_free_ void *hash = NULL; + size_t hash_size; + const EVP_MD *md; + const char *a; + + assert_se(a = tpm2_hash_alg_to_string(*pa)); + assert_se(md = sym_EVP_get_digestbyname(a)); + + r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &hash, &hash_size); if (r < 0) - return r; + return log_error_errno(r, "Failed to hash PE binary: %m"); - r = tpm2_policy_authorize_nv( - tc, - policy_session, - nv_handle, - NULL); + r = sd_json_variant_append_arraybo( + &digests, + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), + SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_size))); if (r < 0) - return log_error_errno(r, "Failed to submit AuthorizeNV policy: %m"); + return log_error_errno(r, "Failed to build JSON digest object: %m"); + } - r = tpm2_unseal_data( - tc, - &pin_public, - &pin_private, - srk_handle, - policy_session, - encryption_session, - &secret); - if (r < 0 && (r != -ESTALE || attempt >= 16)) - return log_error_errno(r, "Failed to unseal PIN: %m"); - if (r == 0) - break; + r = sd_json_variant_append_arraybo( + &array, + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", i), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); + if (r < 0) + return log_error_errno(r, "Failed to append record object: %m"); + } + + return write_pcrlock(array, NULL); +} + +VERB_NOARG(verb_unlock_simple, "unlock-pe", + "Remove .pcrlock file for PE binary"); +static int verb_unlock_simple(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(NULL); +} + +typedef void* SectionHashArray[_UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS]; - log_debug("Trying again (attempt %u), as PCR values changed during unlock attempt.", attempt+1); - } +static void section_hashes_array_done(SectionHashArray *array) { + assert(array); - if (secret.iov_len > sizeof_field(TPM2B_AUTH, buffer)) - return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Decrypted PIN too large."); + for (size_t i = 0; i < _UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS; i++) + free((*array)[i]); +} - auth = (TPM2B_AUTH) { - .size = secret.iov_len, - }; +VERB(verb_lock_uki, "lock-uki", "[UKI]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from UKI PE binary"); +static int verb_lock_uki(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *pe_digests = NULL; + _cleanup_(section_hashes_array_done) SectionHashArray section_hashes = {}; + size_t hash_sizes[TPM2_N_HASH_ALGORITHMS]; + _cleanup_close_ int fd = -EBADF; + int r; - memcpy_safe(auth.buffer, secret.iov_base, secret.iov_len); + if (arg_pcr_mask != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PCR not configurable for UKI lock down."); - log_info("Retrieved PIN from TPM2 in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_start_usec), 1)); + if (argc >= 2) { + fd = open(argv[1], O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); } - /* Now convert the PIN into an HMAC-SHA256 key that we can use in PolicySigned to protect access to the nvindex with */ - _cleanup_(tpm2_handle_freep) Tpm2Handle *pin_handle = NULL; - r = tpm2_hmac_key_from_pin(tc, encryption_session, &auth, &pin_handle); - if (r < 0) - return r; + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + _cleanup_free_ void *peh = NULL; + const EVP_MD *md; + const char *a; - TPM2B_NV_PUBLIC nv_public = {}; - usec_t nv_index_start_usec = now(CLOCK_MONOTONIC); + assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); + assert_se(md = sym_EVP_get_digestbyname(a)); - if (!iovec_is_set(&nv_blob)) { - _cleanup_(Esys_Freep) TPM2B_NAME *pin_name = NULL; - r = tpm2_get_name( - tc, - pin_handle, - &pin_name); + r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &peh, hash_sizes + i); if (r < 0) - return log_error_errno(r, "Failed to get name of PIN from TPM2: %m"); + return log_error_errno(r, "Failed to hash PE binary: %m"); - TPM2B_DIGEST recovery_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); - r = tpm2_calculate_policy_signed(&recovery_policy_digest, pin_name); + r = sd_json_variant_append_arraybo( + &pe_digests, + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), + SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(peh, hash_sizes[i]))); if (r < 0) - return log_error_errno(r, "Failed to calculate PolicySigned policy: %m"); + return log_error_errno(r, "Failed to build JSON digest object: %m"); - log_debug("Allocating NV index to write PCR policy to..."); - r = tpm2_define_policy_nv_index( - tc, - encryption_session, - arg_nv_index, - &recovery_policy_digest, - &nv_index, - &nv_handle, - &nv_public); - if (r == -EEXIST) - return log_error_errno(r, "NV index 0x%" PRIx32 " already allocated.", arg_nv_index); + r = uki_hash(fd < 0 ? STDIN_FILENO : fd, md, section_hashes + (i * _UNIFIED_SECTION_MAX), hash_sizes + i); if (r < 0) - return log_error_errno(r, "Failed to allocate NV index: %m"); + return log_error_errno(r, "Failed to UKI hash PE binary: %m"); } - _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; - r = tpm2_make_policy_session( - tc, - srk_handle, - encryption_session, - &policy_session); + r = sd_json_variant_append_arraybo( + &array, + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_BOOT_LOADER_CODE), + SD_JSON_BUILD_PAIR_VARIANT("digests", pe_digests)); if (r < 0) - return log_error_errno(r, "Failed to allocate policy session: %m"); + return log_error_errno(r, "Failed to append record object: %m"); - r = tpm2_policy_signed_hmac_sha256( - tc, - policy_session, - pin_handle, - &IOVEC_MAKE(auth.buffer, auth.size), - /* ret_policy_digest= */ NULL); - if (r < 0) - return log_error_errno(r, "Failed to submit authentication value policy: %m"); + for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *section_digests = NULL, *record = NULL; - log_debug("Calculating new PCR policy to write..."); - TPM2B_DIGEST new_super_pcr_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + if (!unified_section_measure(section)) + continue; - usec_t pcr_policy_start_usec = now(CLOCK_MONOTONIC); + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + const char *a; + void *hash; - r = tpm2_calculate_policy_super_pcr( - &new_prediction, - el->primary_algorithm, - &new_super_pcr_policy_digest); + hash = section_hashes[i * _UNIFIED_SECTION_MAX + section]; + if (!hash) + continue; + + assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); + + r = sd_json_variant_append_arraybo( + §ion_digests, + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), + SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_sizes[i]))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON digest object: %m"); + } + + if (!section_digests) + continue; + + /* So we have digests for this section, hence generate a record for the section name first. */ + r = make_pcrlock_record(TPM2_PCR_KERNEL_BOOT /* =11 */, unified_sections[section], strlen(unified_sections[section]) + 1, &record); + if (r < 0) + return r; + + r = sd_json_variant_append_array(&array, record); + if (r < 0) + return log_error_errno(r, "Failed to append JSON record array: %m"); + + /* And then append a record for the section contents digests as well */ + r = sd_json_variant_append_arraybo( + &array, + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_KERNEL_BOOT), + SD_JSON_BUILD_PAIR_VARIANT("digests", section_digests)); + if (r < 0) + return log_error_errno(r, "Failed to append record object: %m"); + } + + return write_pcrlock(array, NULL); +} + +VERB_NOARG(verb_unlock_simple, "unlock-uki", + "Remove .pcrlock file for UKI PE binary"); + +VERB_NOARG(verb_lock_machine_id, "lock-machine-id", + "Generate a .pcrlock file from current machine ID"); +static int verb_lock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; + _cleanup_free_ char *word = NULL; + int r; + + r = pcrextend_machine_id_word(&word); if (r < 0) - return log_error_errno(r, "Failed to calculate super PCR policy: %m"); + return r; - log_info("Calculated new PCR policy in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pcr_policy_start_usec), 1)); + r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record); + if (r < 0) + return r; - log_debug("Writing new PCR policy to NV index..."); - r = tpm2_write_policy_nv_index( - tc, - policy_session, - nv_index, - nv_handle, - &new_super_pcr_policy_digest); + r = sd_json_variant_new_array(&array, &record, 1); if (r < 0) - return log_error_errno(r, "Failed to write to NV index: %m"); + return log_error_errno(r, "Failed to create record array: %m"); - log_info("Updated NV index in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), nv_index_start_usec), 1)); + return write_pcrlock(array, PCRLOCK_MACHINE_ID_PATH); +} - assert(iovec_is_set(&pin_public) == iovec_is_set(&pin_private)); - if (!iovec_is_set(&pin_public)) { - TPM2B_DIGEST authnv_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); +VERB_NOARG(verb_unlock_machine_id, "unlock-machine-id", + "Remove .pcrlock file for machine ID"); +static int verb_unlock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_MACHINE_ID_PATH); +} - r = tpm2_calculate_policy_authorize_nv(&nv_public, &authnv_policy_digest); - if (r < 0) - return log_error_errno(r, "Failed to calculate AuthorizeNV policy: %m"); +static int pcrlock_file_system_path(const char *normalized_path, char **ret) { + _cleanup_free_ char *s = NULL; - struct iovec data = { - .iov_base = auth.buffer, - .iov_len = auth.size, - }; + assert(normalized_path); + assert(ret); - usec_t pin_seal_start_usec = now(CLOCK_MONOTONIC); + if (path_equal(normalized_path, "/")) + s = strdup(PCRLOCK_ROOT_FILE_SYSTEM_PATH); + else { + /* We reuse the escaping we use for turning paths into unit names */ + _cleanup_free_ char *escaped = NULL; - log_debug("Sealing PIN to NV index policy..."); - r = tpm2_seal_data( - tc, - &data, - srk_handle, - encryption_session, - &authnv_policy_digest, - &pin_public, - &pin_private); - if (r < 0) - return log_error_errno(r, "Failed to seal PIN to NV auth policy: %m"); + assert(normalized_path[0] == '/'); + assert(normalized_path[1] != '/'); - log_info("Sealed PIN in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_seal_start_usec), 1)); - } + escaped = unit_name_escape(normalized_path + 1); + if (!escaped) + return log_oom(); - if (!iovec_is_set(&nv_blob)) { - r = tpm2_serialize(tc, nv_handle, &nv_blob); - if (r < 0) - return log_error_errno(r, "Failed to serialize NV index TR: %m"); + s = strjoin(PCRLOCK_FILE_SYSTEM_PATH_PREFIX, escaped, ".pcrlock"); } + if (!s) + return log_oom(); - if (!iovec_is_set(&srk_blob)) { - r = tpm2_serialize(tc, srk_handle, &srk_blob); + *ret = TAKE_PTR(s); + return 0; +} + +VERB(verb_lock_file_system, "lock-file-system", "[PATH]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from current root fs + /var/"); +static int verb_lock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { + const char* paths[3] = {}; + int r; + + if (argc > 1) + paths[0] = argv[1]; + else { + dev_t a, b; + paths[0] = "/"; + + r = get_block_device("/", &a); if (r < 0) - return log_error_errno(r, "Failed to serialize SRK index TR: %m"); - } + return log_error_errno(r, "Failed to get device of root file system: %m"); - if (!iovec_is_set(&nv_public_blob)) { - r = tpm2_marshal_nv_public(&nv_public, &nv_public_blob.iov_base, &nv_public_blob.iov_len); + r = get_block_device("/var", &b); if (r < 0) - return log_error_errno(r, "Failed to marshal NV public area: %m"); - } + return log_error_errno(r, "Failed to get device of /var/ file system: %m"); - _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_configuration_json = NULL; - r = sd_json_buildo( - &new_configuration_json, - SD_JSON_BUILD_PAIR_STRING("pcrBank", tpm2_hash_alg_to_string(el->primary_algorithm)), - SD_JSON_BUILD_PAIR_VARIANT("pcrValues", new_prediction_json), - SD_JSON_BUILD_PAIR_INTEGER("nvIndex", nv_index), - JSON_BUILD_PAIR_IOVEC_BASE64("nvHandle", &nv_blob), - JSON_BUILD_PAIR_IOVEC_BASE64("nvPublic", &nv_public_blob), - JSON_BUILD_PAIR_IOVEC_BASE64("srkHandle", &srk_blob), - JSON_BUILD_PAIR_IOVEC_BASE64("pinPublic", &pin_public), - JSON_BUILD_PAIR_IOVEC_BASE64("pinPrivate", &pin_private)); - if (r < 0) - return log_error_errno(r, "Failed to generate JSON: %m"); + /* if backing device is distinct, then measure /var/ too */ + if (a != b) + paths[1] = "/var"; - _cleanup_free_ char *text = NULL; - r = sd_json_variant_format(new_configuration_json, 0, &text); - if (r < 0) - return log_error_errno(r, "Failed to format new configuration to JSON: %m"); + enable_json_sse(); + } + + STRV_FOREACH(p, paths) { + _cleanup_free_ char *word = NULL, *normalized_path = NULL, *pcrlock_file = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; - const char *path = arg_policy_path ?: (in_initrd() ? "/run/systemd/pcrlock.json" : "/var/lib/systemd/pcrlock.json"); - r = write_string_file(path, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL); - if (r < 0) - return log_error_errno(r, "Failed to write new configuration to '%s': %m", path); + r = pcrextend_file_system_word(*p, &word, &normalized_path); + if (r < 0) + return r; - if (!arg_policy_path && !in_initrd()) { - r = remove_policy_file("/run/systemd/pcrlock.json"); + r = pcrlock_file_system_path(normalized_path, &pcrlock_file); if (r < 0) return r; - } - log_info("Written new policy to '%s' and digest to TPM2 NV index 0x%x.", path, nv_index); + r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record); + if (r < 0) + return r; - (void) write_boot_policy_file(text); + r = sd_json_variant_new_array(&array, &record, 1); + if (r < 0) + return log_error_errno(r, "Failed to create record array: %m"); - log_info("Overall time spent: %s", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), start_usec), 1)); + r = write_pcrlock(array, pcrlock_file); + if (r < 0) + return r; + } - return 1; /* installed new policy */ + return 0; } -static int verb_make_policy(int argc, char *argv[], void *userdata) { +VERB(verb_unlock_file_system, "unlock-file-system", "[PATH]", VERB_ANY, 2, 0, + "Remove .pcrlock file for root fs + /var/"); +static int verb_unlock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { + const char* paths[3] = {}; int r; - r = make_policy(arg_force, arg_recovery_pin); - if (r < 0) - return r; + if (argc > 1) + paths[0] = argv[1]; + else { + paths[0] = "/"; + paths[1] = "/var"; + } + + STRV_FOREACH(p, paths) { + _cleanup_free_ char *normalized_path = NULL, *pcrlock_file = NULL; + + r = chase(*p, NULL, 0, &normalized_path, NULL); + if (r < 0) + return log_error_errno(r, "Failed to normal path '%s': %m", argv[1]); + + r = pcrlock_file_system_path(normalized_path, &pcrlock_file); + if (r < 0) + return r; + + r = unlink_pcrlock(pcrlock_file); + if (r < 0) + return r; + } return 0; } -static int undefine_policy_nv_index( - uint32_t nv_index, - const struct iovec *nv_blob, - const struct iovec *srk_blob) { +VERB(verb_lock_kernel_cmdline, "lock-kernel-cmdline", "[FILE]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from kernel command line"); +static int verb_lock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; + _cleanup_free_ char *cmdline = NULL; int r; - assert(nv_blob); - assert(srk_blob); - - _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; - r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); + if (argc > 1) { + if (empty_or_dash(argv[1])) + r = read_full_stream(stdin, &cmdline, NULL); + else + r = read_full_file(argv[1], &cmdline, NULL); + } else + r = proc_cmdline(&cmdline); if (r < 0) - return r; + return log_error_errno(r, "Failed to read cmdline: %m"); - _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL; - r = tpm2_deserialize( - tc, - srk_blob, - &srk_handle); - if (r < 0) - return log_error_errno(r, "Failed to deserialize SRK TR: %m"); + delete_trailing_chars(cmdline, "\n"); - _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; - r = tpm2_deserialize( - tc, - nv_blob, - &nv_handle); - if (r < 0) - return log_error_errno(r, "Failed to deserialize NV TR: %m"); + _cleanup_free_ char16_t *u = NULL; + u = utf8_to_utf16(cmdline, SIZE_MAX); + if (!u) + return log_oom(); - _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; - r = tpm2_make_encryption_session( - tc, - srk_handle, - /* bind_key= */ &TPM2_HANDLE_NONE, - &encryption_session); + r = make_pcrlock_record(TPM2_PCR_KERNEL_INITRD /* = 9 */, u, char16_strlen(u)*2+2, &record); if (r < 0) return r; - r = tpm2_undefine_nv_index( - tc, - encryption_session, - nv_index, - nv_handle); + r = sd_json_variant_new_array(&array, &record, 1); + if (r < 0) + return log_error_errno(r, "Failed to create record array: %m"); + + r = write_pcrlock(array, PCRLOCK_KERNEL_CMDLINE_PATH); if (r < 0) return r; - log_info("Removed NV index 0x%x", nv_index); return 0; } -static int remove_policy(void) { - int ret = 0, r; - - _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {}; - r = tpm2_pcrlock_policy_load(arg_policy_path, &policy); - if (r == 0) { - log_info("No policy found."); - return 0; - } +VERB_NOARG(verb_unlock_kernel_cmdline, "unlock-kernel-cmdline", + "Remove .pcrlock file for kernel command line"); +static int verb_unlock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_KERNEL_CMDLINE_PATH); +} - if (r < 0) - log_notice("Failed to load old policy file, assuming it is corrupted, removing."); - else { - r = undefine_policy_nv_index(policy.nv_index, &policy.nv_handle, &policy.srk_handle); - if (r < 0) - log_notice("Failed to remove NV index, assuming data out of date, removing policy file."); +VERB(verb_lock_kernel_initrd, "lock-kernel-initrd", "FILE", VERB_ANY, 2, 0, + "Generate a .pcrlock file from an initrd file"); +static int verb_lock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; + _cleanup_fclose_ FILE *f = NULL; + uint32_t pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_INITRD; + int r; - RET_GATHER(ret, r); + if (argc >= 2) { + f = fopen(argv[1], "re"); + if (!f) + return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); } - if (arg_policy_path) - RET_GATHER(ret, remove_policy_file(arg_policy_path)); - else { - RET_GATHER(ret, remove_policy_file("/var/lib/systemd/pcrlock.json")); - RET_GATHER(ret, remove_policy_file("/run/systemd/pcrlock.json")); - } + r = make_pcrlock_record_from_stream(pcr_mask, f ?: stdin, &records); + if (r < 0) + return r; - _cleanup_free_ char *boot_policy_file = NULL; - r = determine_boot_policy_file(&boot_policy_file, /* ret_credential_name= */ NULL); - if (r == 0) - log_info("Did not find XBOOTLDR/ESP partition, not removing boot policy file."); - else if (r > 0) { - RET_GATHER(ret, remove_policy_file(boot_policy_file)); - } else - RET_GATHER(ret, r); + r = write_pcrlock(records, PCRLOCK_KERNEL_INITRD_PATH); + if (r < 0) + return r; - return ret; + return 0; } -static int verb_remove_policy(int argc, char *argv[], void *userdata) { - return remove_policy(); +VERB_NOARG(verb_unlock_kernel_initrd, "unlock-kernel-initrd", + "Remove .pcrlock file for an initrd file"); +static int verb_unlock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_KERNEL_INITRD_PATH); } -static int test_tpm2_support_pcrlock(Tpm2Support *ret) { +VERB(verb_lock_raw, "lock-raw", "[FILE]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from raw data"); +static int verb_lock_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; + _cleanup_fclose_ FILE *f = NULL; int r; - assert(ret); - - /* First check basic support */ - Tpm2Support s = tpm2_support(); - - /* If basic support is available, let's also check the things we need for systemd-pcrlock */ - if (s == TPM2_SUPPORT_FULL) { - _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; - r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); - if (r < 0) - return r; - - /* We strictly need TPM2_CC_PolicyAuthorizeNV for systemd-pcrlock to work */ - SET_FLAG(s, TPM2_SUPPORT_AUTHORIZE_NV, tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV)); - - log_debug("PolicyAuthorizeNV supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_AUTHORIZE_NV))); - - /* We also strictly need SHA-256 to work */ - SET_FLAG(s, TPM2_SUPPORT_SHA256, tpm2_supports_alg(tc, TPM2_ALG_SHA256)); + if (arg_pcr_mask == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No PCR specified, refusing."); - log_debug("SHA-256 supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_SHA256))); + if (argc >= 2) { + f = fopen(argv[1], "re"); + if (!f) + return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); } - *ret = s; - return 0; -} - -static int verb_is_supported(int argc, char *argv[], void *userdata) { - int r; - - Tpm2Support s; - r = test_tpm2_support_pcrlock(&s); + r = make_pcrlock_record_from_stream(arg_pcr_mask, f ?: stdin, &records); if (r < 0) return r; - if (!arg_quiet) { - if (s == (TPM2_SUPPORT_FULL|TPM2_SUPPORT_API_PCRLOCK)) - printf("%syes%s\n", ansi_green(), ansi_normal()); - else if (FLAGS_SET(s, TPM2_SUPPORT_FULL)) - printf("%sobsolete%s\n", ansi_red(), ansi_normal()); - else if (s == TPM2_SUPPORT_NONE) - printf("%sno%s\n", ansi_red(), ansi_normal()); - else - printf("%spartial%s\n", ansi_yellow(), ansi_normal()); - } - - assert_cc((TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK) <= 255); /* make sure this is safe to use as process exit status */ - - return ~s & (TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK); + return write_pcrlock(records, NULL); } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *commands = NULL, *protections = NULL, *options = NULL; int r; r = terminal_urlify_man("systemd-pcrlock", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sManage a TPM2 PCR lock.%6$s\n" - "\n%3$sCommands:%4$s\n" - " log Show measurement log\n" - " cel Show measurement log in TCG CEL-JSON format\n" - " list-components List defined .pcrlock components\n" - " predict Predict PCR values\n" - " make-policy Predict PCR values and generate TPM2 policy from it\n" - " remove-policy Remove TPM2 policy\n" - " is-supported Tests if TPM2 supports necessary features\n" - "\n%3$sProtections:%4$s\n" - " lock-firmware-code Generate a .pcrlock file from current firmware code\n" - " unlock-firmware-code Remove .pcrlock file for firmware code\n" - " lock-firmware-config Generate a .pcrlock file from current firmware configuration\n" - " unlock-firmware-config Remove .pcrlock file for firmware configuration\n" - " lock-secureboot-policy Generate a .pcrlock file from current SecureBoot policy\n" - " unlock-secureboot-policy Remove .pcrlock file for SecureBoot policy\n" - " lock-secureboot-authority Generate a .pcrlock file from current SecureBoot authority\n" - " unlock-secureboot-authority Remove .pcrlock file for SecureBoot authority\n" - " lock-gpt [DISK] Generate a .pcrlock file from GPT header\n" - " unlock-gpt Remove .pcrlock file for GPT header\n" - " lock-pe [BINARY] Generate a .pcrlock file from PE binary\n" - " unlock-pe Remove .pcrlock file for PE binary\n" - " lock-uki [UKI] Generate a .pcrlock file from UKI PE binary\n" - " unlock-uki Remove .pcrlock file for UKI PE binary\n" - " lock-machine-id Generate a .pcrlock file from current machine ID\n" - " unlock-machine-id Remove .pcrlock file for machine ID\n" - " lock-file-system [PATH] Generate a .pcrlock file from current root fs + /var/\n" - " unlock-file-system [PATH] Remove .pcrlock file for root fs + /var/\n" - " lock-kernel-cmdline [FILE] Generate a .pcrlock file from kernel command line\n" - " unlock-kernel-cmdline Remove .pcrlock file for kernel command line\n" - " lock-kernel-initrd FILE Generate a .pcrlock file from an initrd file\n" - " unlock-kernel-initrd Remove .pcrlock file for an initrd file\n" - " lock-raw [FILE] Generate a .pcrlock file from raw data\n" - " unlock-raw Remove .pcrlock file for raw data\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --no-pager Do not pipe output into a pager\n" - " --json=pretty|short|off Generate JSON output\n" - " --raw-description Show raw firmware record data as description in table\n" - " --pcr=NR Generate .pcrlock for specified PCR\n" - " --nv-index=NUMBER Use the specified NV index, instead of a random one\n" - " --components=PATH Directory to read .pcrlock files from\n" - " --location=STRING[:STRING]\n" - " Do not process components beyond this component name\n" - " --recovery-pin=MODE Controls whether to show, hide, or ask for a recovery PIN\n" - " --pcrlock=PATH .pcrlock file to write expected PCR measurement to\n" - " --policy=PATH JSON file to write policy output to\n" - " --force Write policy even if it matches existing policy\n" - " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n" - " Boot entry token to use for this installation\n" - " -q --quiet Suppress unnecessary output\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&commands); + if (r < 0) + return r; + + r = verbs_get_help_table_group("Protections", &protections); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, commands, protections, options); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sManage a TPM2 PCR lock.%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + printf("\n%sCommands:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(commands); + if (r < 0) + return r; + + printf("\n%sProtections:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(protections); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_JSON, - ARG_RAW_DESCRIPTION, - ARG_PCR, - ARG_NV_INDEX, - ARG_COMPONENTS, - ARG_LOCATION, - ARG_RECOVERY_PIN, - ARG_PCRLOCK, - ARG_POLICY, - ARG_FORCE, - ARG_ENTRY_TOKEN, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "json", required_argument, NULL, ARG_JSON }, - { "raw-description", no_argument, NULL, ARG_RAW_DESCRIPTION }, - { "pcr", required_argument, NULL, ARG_PCR }, - { "nv-index", required_argument, NULL, ARG_NV_INDEX }, - { "components", required_argument, NULL, ARG_COMPONENTS }, - { "location", required_argument, NULL, ARG_LOCATION }, - { "recovery-pin", required_argument, NULL, ARG_RECOVERY_PIN }, - { "pcrlock", required_argument, NULL, ARG_PCRLOCK }, - { "policy", required_argument, NULL, ARG_POLICY }, - { "force", no_argument, NULL, ARG_FORCE }, - { "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN }, - { "quiet", no_argument, NULL, 'q' }, - {} - }; +VERB_NOARG(verb_unlock_simple, "unlock-raw", + "Remove .pcrlock file for raw data"); - bool auto_location = true; - int c, r; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); + + OptionParser state = { argc, argv }; + const char *arg; + bool auto_location = true; + int r; - while ((c = getopt_long(argc, argv, "hq", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - help(0, NULL, NULL); - return 0; + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; break; - case ARG_RAW_DESCRIPTION: + OPTION_LONG("raw-description", NULL, + "Show raw firmware record data as description in table"): arg_raw_description = true; break; - case ARG_PCR: { - r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_pcr_mask); + OPTION_LONG("pcr", "NR", + "Generate .pcrlock for specified PCR"): + r = tpm2_parse_pcr_argument_to_mask(arg, &arg_pcr_mask); if (r < 0) - return log_error_errno(r, "Failed to parse PCR specification: %s", optarg); - + return log_error_errno(r, "Failed to parse PCR specification: %s", arg); break; - } - case ARG_NV_INDEX: - if (isempty(optarg)) + OPTION_LONG("nv-index", "NUMBER", + "Use the specified NV index, instead of a random one"): + if (isempty(arg)) arg_nv_index = 0; else { uint32_t u; - r = safe_atou32_full(optarg, 16, &u); + r = safe_atou32_full(arg, 16, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --nv-index= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --nv-index= argument: %s", arg); if (u < TPM2_NV_INDEX_FIRST || u > TPM2_NV_INDEX_LAST) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument for --nv-index= outside of valid range 0x%" PRIx32 "…0x%" PRIx32 ": 0x%" PRIx32, @@ -5229,10 +5244,11 @@ static int parse_argv(int argc, char *argv[]) { } break; - case ARG_COMPONENTS: { + OPTION_LONG("components", "PATH", + "Directory to read .pcrlock files from"): { _cleanup_free_ char *p = NULL; - r = parse_path_argument(optarg, /* suppress_root= */ false, &p); + r = parse_path_argument(arg, /* suppress_root= */ false, &p); if (r < 0) return r; @@ -5243,21 +5259,22 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_LOCATION: { + OPTION_LONG("location", "START[:END]", + "Do not process components beyond this component name"): { _cleanup_free_ char *start = NULL, *end = NULL; const char *e; auto_location = false; - if (isempty(optarg)) { + if (isempty(arg)) { arg_location_start = mfree(arg_location_start); arg_location_end = mfree(arg_location_end); break; } - e = strchr(optarg, ':'); + e = strchr(arg, ':'); if (e) { - start = strndup(optarg, e - optarg); + start = strndup(arg, e - arg); if (!start) return log_oom(); @@ -5265,11 +5282,11 @@ static int parse_argv(int argc, char *argv[]) { if (!end) return log_oom(); } else { - start = strdup(optarg); + start = strdup(arg); if (!start) return log_oom(); - end = strdup(optarg); + end = strdup(arg); if (!end) return log_oom(); } @@ -5284,17 +5301,19 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_RECOVERY_PIN: - arg_recovery_pin = recovery_pin_mode_from_string(optarg); + OPTION_LONG("recovery-pin", "MODE", + "Controls whether to show, hide, or ask for a recovery PIN"): + arg_recovery_pin = recovery_pin_mode_from_string(arg); if (arg_recovery_pin < 0) - return log_error_errno(arg_recovery_pin, "Failed to parse --recovery-pin= mode: %s", optarg); + return log_error_errno(arg_recovery_pin, "Failed to parse --recovery-pin= mode: %s", arg); break; - case ARG_PCRLOCK: - if (empty_or_dash(optarg)) + OPTION_LONG("pcrlock", "PATH", + ".pcrlock file to write expected PCR measurement to"): + if (empty_or_dash(arg)) arg_pcrlock_path = mfree(arg_pcrlock_path); else { - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_pcrlock_path); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_pcrlock_path); if (r < 0) return r; } @@ -5302,36 +5321,34 @@ static int parse_argv(int argc, char *argv[]) { arg_pcrlock_auto = false; break; - case ARG_POLICY: - if (empty_or_dash(optarg)) + OPTION_LONG("policy", "PATH", + "JSON file to write policy output to"): + if (empty_or_dash(arg)) arg_policy_path = mfree(arg_policy_path); else { - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_policy_path); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_policy_path); if (r < 0) return r; } break; - case ARG_FORCE: + OPTION_LONG("force", NULL, + "Write policy even if it matches existing policy"): arg_force = true; break; - case ARG_ENTRY_TOKEN: - r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token); + OPTION_LONG("entry-token", "TOKEN", + "Boot entry token to use for this installation " + "(machine-id, os-id, os-image-id, auto, literal:…)"): + r = parse_boot_entry_token_type(arg, &arg_entry_token_type, &arg_entry_token); if (r < 0) return r; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress unnecessary output"): arg_quiet = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (auto_location) { @@ -5355,49 +5372,10 @@ static int parse_argv(int argc, char *argv[]) { arg_pager_flags |= PAGER_DISABLE; } + *ret_args = option_parser_get_args(&state); return 1; } -static int pcrlock_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "log", VERB_ANY, 1, VERB_DEFAULT, verb_show_log }, - { "cel", VERB_ANY, 1, 0, verb_show_cel }, - { "list-components", VERB_ANY, 1, 0, verb_list_components }, - { "predict", VERB_ANY, 1, 0, verb_predict }, - { "lock-firmware-code", VERB_ANY, 2, 0, verb_lock_firmware }, - { "unlock-firmware-code", VERB_ANY, 1, 0, verb_unlock_firmware }, - { "lock-firmware-config", VERB_ANY, 2, 0, verb_lock_firmware }, - { "unlock-firmware-config", VERB_ANY, 1, 0, verb_unlock_firmware }, - { "lock-secureboot-policy", VERB_ANY, 1, 0, verb_lock_secureboot_policy }, - { "unlock-secureboot-policy", VERB_ANY, 1, 0, verb_unlock_secureboot_policy }, - { "lock-secureboot-authority", VERB_ANY, 1, 0, verb_lock_secureboot_authority }, - { "unlock-secureboot-authority", VERB_ANY, 1, 0, verb_unlock_secureboot_authority }, - { "lock-gpt", VERB_ANY, 2, 0, verb_lock_gpt }, - { "unlock-gpt", VERB_ANY, 1, 0, verb_unlock_gpt }, - { "lock-pe", VERB_ANY, 2, 0, verb_lock_pe }, - { "unlock-pe", VERB_ANY, 1, 0, verb_unlock_simple }, - { "lock-uki", VERB_ANY, 2, 0, verb_lock_uki }, - { "unlock-uki", VERB_ANY, 1, 0, verb_unlock_simple }, - { "lock-machine-id", VERB_ANY, 1, 0, verb_lock_machine_id }, - { "unlock-machine-id", VERB_ANY, 1, 0, verb_unlock_machine_id }, - { "lock-file-system", VERB_ANY, 2, 0, verb_lock_file_system }, - { "unlock-file-system", VERB_ANY, 2, 0, verb_unlock_file_system }, - { "lock-kernel-cmdline", VERB_ANY, 2, 0, verb_lock_kernel_cmdline }, - { "unlock-kernel-cmdline", VERB_ANY, 1, 0, verb_unlock_kernel_cmdline }, - { "lock-kernel-initrd", VERB_ANY, 2, 0, verb_lock_kernel_initrd }, - { "unlock-kernel-initrd", VERB_ANY, 1, 0, verb_unlock_kernel_initrd }, - { "lock-raw", VERB_ANY, 2, 0, verb_lock_raw }, - { "unlock-raw", VERB_ANY, 1, 0, verb_unlock_simple }, - { "make-policy", VERB_ANY, 1, 0, verb_make_policy }, - { "remove-policy", VERB_ANY, 1, 0, verb_remove_policy }, - { "is-supported", VERB_ANY, 1, 0, verb_is_supported }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int vl_method_read_event_log(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { _cleanup_(event_log_freep) EventLog *el = NULL; uint64_t recnum = 0; @@ -5419,7 +5397,7 @@ static int vl_method_read_event_log(sd_varlink *link, sd_json_variant *parameter return r; // FIXME: We can't use a NULL sentinel here because the output fields in the IDL are non-nullable. - r = varlink_set_sentinel(link, NULL); + r = sd_varlink_set_sentinel(link, NULL); if (r < 0) return r; @@ -5489,10 +5467,15 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + if (arg_varlink) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; @@ -5521,7 +5504,7 @@ static int run(int argc, char *argv[]) { return EXIT_SUCCESS; } - return pcrlock_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/pcrlock/pcrlock.d/750-os-separator.pcrlock b/src/pcrlock/pcrlock.d/750-os-separator.pcrlock new file mode 100644 index 0000000000000..aaeba174d1cbb --- /dev/null +++ b/src/pcrlock/pcrlock.d/750-os-separator.pcrlock @@ -0,0 +1 @@ +{"records":[{"pcr":0,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":1,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":2,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":3,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":4,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":5,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":6,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":7,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":9,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":12,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":13,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":14,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]}]} diff --git a/src/pcrlock/pcrlock.d/770-nvpcr-separator.pcrlock b/src/pcrlock/pcrlock.d/770-nvpcr-separator.pcrlock new file mode 100644 index 0000000000000..2cca29b550328 --- /dev/null +++ b/src/pcrlock/pcrlock.d/770-nvpcr-separator.pcrlock @@ -0,0 +1 @@ +{"records":[{"pcr":9,"digests":[{"hashAlg":"sha512","digest":"25d91b8f45d61cab950206c6edd86bd46ecbb1f2369bc1cdc7a8956861b4a0e30792e8b327548a7b31d5013088200f061970a8843dbb2504e400b80d46202642"},{"hashAlg":"sha384","digest":"196288284fffef2010ebff9d05ccc0b527fe0dcfd950c0524cbf36ed039aeecc25c8628abd1511ae77da685b7b5e17b4"},{"hashAlg":"sha256","digest":"97c5f96d4cd3fb14c7e272c47762af208f712c1a47733567e5c233e2b6f6c5cb"},{"hashAlg":"sha1","digest":"9cfca2696175a850a1ed68a6a1f075b38beab7cf"}]}]} diff --git a/src/portable/portable.c b/src/portable/portable.c index c86a6c27228e3..3f3c0cda48891 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -137,6 +137,9 @@ PortableMetadata *portable_metadata_unref(PortableMetadata *i) { } static int compare_metadata(PortableMetadata *const *x, PortableMetadata *const *y) { + assert(x); + assert(y); + return strcmp((*x)->name, (*y)->name); } @@ -146,6 +149,8 @@ int portable_metadata_hashmap_to_sorted_array(Hashmap *unit_files, PortableMetad PortableMetadata *item; size_t k = 0; + assert(ret); + sorted = new(PortableMetadata*, hashmap_size(unit_files)); if (!sorted) return -ENOMEM; @@ -358,7 +363,7 @@ static int extract_now( _cleanup_free_ char *relative = NULL, *resolved = NULL; _cleanup_closedir_ DIR *d = NULL; - r = chase_and_opendirat(rfd, *i, CHASE_AT_RESOLVE_IN_ROOT, &relative, &d); + r = chase_and_opendirat(rfd, rfd, *i, /* chase_flags= */ 0, &relative, &d); if (r < 0) { log_debug_errno(r, "Failed to open unit path '%s', ignoring: %m", *i); continue; @@ -381,7 +386,7 @@ static int extract_now( continue; /* Filter out duplicates */ - if (hashmap_get(unit_files, de->d_name)) + if (hashmap_contains(unit_files, de->d_name)) continue; if (!IN_SET(de->d_type, DT_LNK, DT_REG)) @@ -519,7 +524,7 @@ static int portable_extract_by_path( seq[0] = safe_close(seq[0]); errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); - if (setns(CLONE_NEWUSER, userns_fd) < 0) { + if (setns(userns_fd, CLONE_NEWUSER) < 0) { r = log_debug_errno(errno, "Failed to join userns: %m"); report_errno_and_exit(errno_pipe_fd[1], r); } @@ -605,8 +610,8 @@ static int portable_extract_by_path( * there, and extract the metadata we need. The metadata is sent from the child back to us. */ /* Load some libraries before we fork workers off that want to use them */ - (void) dlopen_cryptsetup(); - (void) dlopen_libmount(); + (void) dlopen_cryptsetup(LOG_DEBUG); + (void) dlopen_libmount(LOG_DEBUG); r = mkdtemp_malloc("/tmp/inspect-XXXXXX", &tmpdir); if (r < 0) @@ -1266,19 +1271,14 @@ static int portable_changes_add_with_prefix( return portable_changes_add(changes, n_changes, type_or_errno, path, source); } -void portable_changes_free(PortableChange *changes, size_t n_changes) { - size_t i; - - assert(changes || n_changes == 0); - - for (i = 0; i < n_changes; i++) { - free(changes[i].path); - free(changes[i].source); - } - - free(changes); +static void portable_change_done(PortableChange *change) { + assert(change); + change->path = mfree(change->path); + change->source = mfree(change->source); } +DEFINE_ARRAY_FREE_FUNC(portable_changes_free, PortableChange, portable_change_done); + static const char *root_setting_from_image(ImageType type) { switch (type) { case IMAGE_DIRECTORY: @@ -1382,9 +1382,19 @@ static int append_release_log_fields( /* Find an ID first, in order of preference from more specific to less specific: IMAGE_ID -> ID */ id = strv_find_first_field((char *const *)field_ids[type], fields); + if (id && string_has_cc(id, /* ok= */ NULL)) { + log_debug("os-release file '%s' contains control characters in the ID field, skipping.", + release->name); + id = NULL; + } /* Then the version, same logic, prefer the more specific one */ version = strv_find_first_field((char *const *)field_versions[type], fields); + if (version && string_has_cc(version, /* ok= */ NULL)) { + log_debug("os-release file '%s' contains control characters in the version field, skipping.", + release->name); + version = NULL; + } /* If there's no valid version to be found, simply omit it. */ if (!id && !version) @@ -1491,65 +1501,64 @@ static int install_chroot_dropin( if (r < 0) return r; - if (m->image_path && !path_equal(m->image_path, image_path)) - ORDERED_HASHMAP_FOREACH(ext, extension_images) { + ORDERED_HASHMAP_FOREACH(ext, extension_images) { - const char *extension_setting = extension_setting_from_image(ext->type); - if (!extension_setting) - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Image type '%s' not supported for extensions: %m", image_type_to_string(ext->type)); + const char *extension_setting = extension_setting_from_image(ext->type); + if (!extension_setting) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Image type '%s' not supported for extensions: %m", image_type_to_string(ext->type)); - _cleanup_free_ char *extension_base_name = NULL; - r = path_extract_filename(ext->path, &extension_base_name); - if (r < 0) - return log_debug_errno(r, "Failed to extract basename from '%s': %m", ext->path); + _cleanup_free_ char *extension_base_name = NULL; + r = path_extract_filename(ext->path, &extension_base_name); + if (r < 0) + return log_debug_errno(r, "Failed to extract basename from '%s': %m", ext->path); - if (!strextend(&text, + if (!strextend(&text, + "\n", + extension_setting, + ext->path, + /* With --force tell PID1 to avoid enforcing that the image and + * extension-release. have to match. */ + !IN_SET(ext->type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) && + FLAGS_SET(flags, PORTABLE_FORCE_EXTENSION) ? + ":x-systemd.relax-extension-release-check\n" : "\n", - extension_setting, - ext->path, - /* With --force tell PID1 to avoid enforcing that the image and - * extension-release. have to match. */ - !IN_SET(ext->type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) && - FLAGS_SET(flags, PORTABLE_FORCE_EXTENSION) ? - ":x-systemd.relax-extension-release-check\n" : - "\n", - /* In PORTABLE= we list the 'main' image name for this unit - * (the image where the unit was extracted from), but we are - * stacking multiple images, so list those too. */ - "LogExtraFields=PORTABLE_EXTENSION=", extension_base_name, "\n")) - return -ENOMEM; - - if (pinned_ext_image_policy && !IN_SET(ext->type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME)) { - _cleanup_free_ char *policy_str = NULL; - - r = image_policy_to_string(pinned_ext_image_policy, /* simplify= */ true, &policy_str); - if (r < 0) - return log_debug_errno(r, "Failed to serialize pinned image policy: %m"); + /* In PORTABLE= we list the 'main' image name for this unit + * (the image where the unit was extracted from), but we are + * stacking multiple images, so list those too. */ + "LogExtraFields=PORTABLE_EXTENSION=", extension_base_name, "\n")) + return -ENOMEM; - if (!strextend(&text, - "ExtensionImagePolicy=", policy_str, "\n")) - return -ENOMEM; - } + if (pinned_ext_image_policy && !IN_SET(ext->type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME)) { + _cleanup_free_ char *policy_str = NULL; - /* Look for image/version identifiers in the extension release files. We - * look for all possible IDs, but typically only 1 or 2 will be set, so - * the number of fields added shouldn't be too large. We prefix the DDI - * name to the value, so that we can add the same field multiple times and - * still be able to identify what applies to what. */ - r = append_release_log_fields(&text, - ordered_hashmap_get(extension_releases, ext->name), - IMAGE_SYSEXT, - "PORTABLE_EXTENSION_NAME_AND_VERSION"); + r = image_policy_to_string(pinned_ext_image_policy, /* simplify= */ true, &policy_str); if (r < 0) - return r; + return log_debug_errno(r, "Failed to serialize pinned image policy: %m"); - r = append_release_log_fields(&text, - ordered_hashmap_get(extension_releases, ext->name), - IMAGE_CONFEXT, - "PORTABLE_EXTENSION_NAME_AND_VERSION"); - if (r < 0) - return r; + if (!strextend(&text, + "ExtensionImagePolicy=", policy_str, "\n")) + return -ENOMEM; } + + /* Look for image/version identifiers in the extension release files. We + * look for all possible IDs, but typically only 1 or 2 will be set, so + * the number of fields added shouldn't be too large. We prefix the DDI + * name to the value, so that we can add the same field multiple times and + * still be able to identify what applies to what. */ + r = append_release_log_fields(&text, + ordered_hashmap_get(extension_releases, ext->name), + IMAGE_SYSEXT, + "PORTABLE_EXTENSION_NAME_AND_VERSION"); + if (r < 0) + return r; + + r = append_release_log_fields(&text, + ordered_hashmap_get(extension_releases, ext->name), + IMAGE_CONFEXT, + "PORTABLE_EXTENSION_NAME_AND_VERSION"); + if (r < 0) + return r; + } } r = write_string_file(dropin, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_SYNC); diff --git a/src/portable/portable.h b/src/portable/portable.h index 5065babaab24c..af5ef3ba652a2 100644 --- a/src/portable/portable.h +++ b/src/portable/portable.h @@ -112,7 +112,7 @@ int portable_get_state( int portable_get_profiles(RuntimeScope scope, char ***ret); -void portable_changes_free(PortableChange *changes, size_t n_changes); +void portable_changes_free(PortableChange *array, size_t n); DECLARE_STRING_TABLE_LOOKUP(portable_change_type, int); diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index dfe6df3bf310d..2c555ee359877 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -13,8 +13,6 @@ #include "bus-wait-for-jobs.h" #include "chase.h" #include "env-file.h" -#include "fd-util.h" -#include "fileio.h" #include "format-table.h" #include "fs-util.h" #include "install.h" @@ -59,6 +57,8 @@ static bool is_portable_managed(const char *unit) { static int determine_image(const char *image, bool permit_non_existing, char **ret) { int r; + assert(ret); + /* If the specified name is a valid image name, we pass it as-is to portabled, which will search for it in the * usual search directories. Otherwise we presume it's a path, and will normalize it on the client's side * (among other things, to make the path independent of the client's working directory) before passing it @@ -132,6 +132,8 @@ static int extract_prefix(const char *path, char **ret) { size_t m; int r; + assert(ret); + r = path_extract_filename(path, &bn); if (r < 0) return r; @@ -155,7 +157,7 @@ static int extract_prefix(const char *path, char **ret) { /* A slightly reduced version of what's permitted in unit names. With ':' and '\' are removed, as well as '_' * which we use as delimiter for the second part of the image string, which we ignore for now. */ - if (!in_charset(name, DIGITS LETTERS "-.")) + if (!in_charset(name, ALPHANUMERICAL "-.")) return -EINVAL; if (!filename_is_valid(name)) @@ -237,6 +239,8 @@ static int acquire_bus(sd_bus **bus) { static int maybe_reload(sd_bus **bus) { int r; + assert(bus); + if (!arg_reload) return 0; @@ -288,7 +292,7 @@ static int get_image_metadata(sd_bus *bus, const char *image, char **matches, sd return 0; } -static int inspect_image(int argc, char *argv[], void *userdata) { +static int verb_inspect_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_strv_free_ char **matches = NULL; @@ -332,15 +336,12 @@ static int inspect_image(int argc, char *argv[], void *userdata) { nl = true; } else { _cleanup_free_ char *pretty_portable = NULL, *pretty_os = NULL; - _cleanup_fclose_ FILE *f = NULL; - - f = fmemopen_unlocked((void*) data, sz, "r"); - if (!f) - return log_error_errno(errno, "Failed to open /etc/os-release buffer: %m"); - r = parse_env_file(f, "/etc/os-release", - "PORTABLE_PRETTY_NAME", &pretty_portable, - "PRETTY_NAME", &pretty_os); + r = parse_env_data( + data, sz, + "/etc/os-release", + "PORTABLE_PRETTY_NAME", &pretty_portable, + "PRETTY_NAME", &pretty_os); if (r < 0) return log_error_errno(r, "Failed to parse /etc/os-release: %m"); @@ -396,33 +397,30 @@ static int inspect_image(int argc, char *argv[], void *userdata) { *confext_version_id = NULL, *confext_scope = NULL, *confext_image_id = NULL, *confext_image_version = NULL, *confext_build_id = NULL; - _cleanup_fclose_ FILE *f = NULL; - - f = fmemopen_unlocked((void*) data, sz, "r"); - if (!f) - return log_error_errno(errno, "Failed to open extension-release buffer: %m"); - - r = parse_env_file(f, name, - "SYSEXT_ID", &sysext_id, - "SYSEXT_VERSION_ID", &sysext_version_id, - "SYSEXT_BUILD_ID", &sysext_build_id, - "SYSEXT_IMAGE_ID", &sysext_image_id, - "SYSEXT_IMAGE_VERSION", &sysext_image_version, - "SYSEXT_SCOPE", &sysext_scope, - "SYSEXT_LEVEL", &sysext_level, - "SYSEXT_PRETTY_NAME", &sysext_pretty_os, - "CONFEXT_ID", &confext_id, - "CONFEXT_VERSION_ID", &confext_version_id, - "CONFEXT_BUILD_ID", &confext_build_id, - "CONFEXT_IMAGE_ID", &confext_image_id, - "CONFEXT_IMAGE_VERSION", &confext_image_version, - "CONFEXT_SCOPE", &confext_scope, - "CONFEXT_LEVEL", &confext_level, - "CONFEXT_PRETTY_NAME", &confext_pretty_os, - "ID", &id, - "VERSION_ID", &version_id, - "PORTABLE_PRETTY_NAME", &pretty_portable, - "PORTABLE_PREFIXES", &portable_prefixes); + + r = parse_env_data( + data, sz, + name, + "SYSEXT_ID", &sysext_id, + "SYSEXT_VERSION_ID", &sysext_version_id, + "SYSEXT_BUILD_ID", &sysext_build_id, + "SYSEXT_IMAGE_ID", &sysext_image_id, + "SYSEXT_IMAGE_VERSION", &sysext_image_version, + "SYSEXT_SCOPE", &sysext_scope, + "SYSEXT_LEVEL", &sysext_level, + "SYSEXT_PRETTY_NAME", &sysext_pretty_os, + "CONFEXT_ID", &confext_id, + "CONFEXT_VERSION_ID", &confext_version_id, + "CONFEXT_BUILD_ID", &confext_build_id, + "CONFEXT_IMAGE_ID", &confext_image_id, + "CONFEXT_IMAGE_VERSION", &confext_image_version, + "CONFEXT_SCOPE", &confext_scope, + "CONFEXT_LEVEL", &confext_level, + "CONFEXT_PRETTY_NAME", &confext_pretty_os, + "ID", &id, + "VERSION_ID", &version_id, + "PORTABLE_PRETTY_NAME", &pretty_portable, + "PORTABLE_PREFIXES", &portable_prefixes); if (r < 0) return log_error_errno(r, "Failed to parse extension release from '%s': %m", name); @@ -958,15 +956,15 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) { return 0; } -static int attach_image(int argc, char *argv[], void *userdata) { +static int verb_attach_image(int argc, char *argv[], uintptr_t _data, void *userdata) { return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) && !arg_force ? "AttachImage" : "AttachImageWithExtensions"); } -static int reattach_image(int argc, char *argv[], void *userdata) { +static int verb_reattach_image(int argc, char *argv[], uintptr_t _data, void *userdata) { return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) && !arg_force ? "ReattachImage" : "ReattachImageWithExtensions"); } -static int detach_image(int argc, char *argv[], void *userdata) { +static int verb_detach_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1020,7 +1018,7 @@ static int detach_image(int argc, char *argv[], void *userdata) { return 0; } -static int list_images(int argc, char *argv[], void *userdata) { +static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1079,9 +1077,9 @@ static int list_images(int argc, char *argv[], void *userdata) { table_set_header(table, arg_legend); - r = table_print(table, NULL); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; } if (arg_legend) { @@ -1094,7 +1092,7 @@ static int list_images(int argc, char *argv[], void *userdata) { return 0; } -static int remove_image(int argc, char *argv[], void *userdata) { +static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, i; @@ -1125,7 +1123,7 @@ static int remove_image(int argc, char *argv[], void *userdata) { return 0; } -static int read_only_image(int argc, char *argv[], void *userdata) { +static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int b = true, r; @@ -1149,7 +1147,7 @@ static int read_only_image(int argc, char *argv[], void *userdata) { return 0; } -static int set_limit(int argc, char *argv[], void *userdata) { +static int verb_set_limit(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; uint64_t limit; @@ -1182,7 +1180,7 @@ static int set_limit(int argc, char *argv[], void *userdata) { return 0; } -static int is_image_attached(int argc, char *argv[], void *userdata) { +static int verb_is_image_attached(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1257,7 +1255,7 @@ static int dump_profiles(void) { return 0; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -1319,6 +1317,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -1375,7 +1377,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -1493,16 +1495,16 @@ static int parse_argv(int argc, char *argv[]) { static int run(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "list", VERB_ANY, 1, VERB_DEFAULT, list_images }, - { "attach", 2, VERB_ANY, 0, attach_image }, - { "detach", 2, VERB_ANY, 0, detach_image }, - { "inspect", 2, VERB_ANY, 0, inspect_image }, - { "is-attached", 2, 2, 0, is_image_attached }, - { "read-only", 2, 3, 0, read_only_image }, - { "remove", 2, VERB_ANY, 0, remove_image }, - { "set-limit", 3, 3, 0, set_limit }, - { "reattach", 2, VERB_ANY, 0, reattach_image }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list_images }, + { "attach", 2, VERB_ANY, 0, verb_attach_image }, + { "detach", 2, VERB_ANY, 0, verb_detach_image }, + { "inspect", 2, VERB_ANY, 0, verb_inspect_image }, + { "is-attached", 2, 2, 0, verb_is_image_attached }, + { "read-only", 2, 3, 0, verb_read_only_image }, + { "remove", 2, VERB_ANY, 0, verb_remove_image }, + { "set-limit", 3, 3, 0, verb_set_limit }, + { "reattach", 2, VERB_ANY, 0, verb_reattach_image }, {} }; diff --git a/src/ptyfwd/ptyfwd-tool.c b/src/ptyfwd/ptyfwd-tool.c index 519bd3641d514..089c84a40c33c 100644 --- a/src/ptyfwd/ptyfwd-tool.c +++ b/src/ptyfwd/ptyfwd-tool.c @@ -1,14 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" #include "build.h" #include "event-util.h" #include "fd-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pidref.h" #include "pretty-print.h" @@ -28,95 +29,77 @@ STATIC_DESTRUCTOR_REGISTER(arg_title, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-pty-forward", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sRun command with a custom terminal background color or title.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " -q --quiet Suppress information messages during runtime\n" - " --read-only Do not accept any user input on stdin\n" - " --background=COLOR Set ANSI color for background\n" - " --title=TITLE Set terminal title\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sRun command with a custom terminal background color or title.%s\n" + "\n%sOptions:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_READ_ONLY, - ARG_BACKGROUND, - ARG_TITLE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "quiet", no_argument, NULL, 'q' }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "background", required_argument, NULL, ARG_BACKGROUND }, - { "title", required_argument, NULL, ARG_TITLE }, - {} - }; - - int c, r; - +static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); + assert(remaining_args); - optind = 0; - while ((c = getopt_long(argc, argv, "+hq", options, NULL)) >= 0) + OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + const char *arg; + int r; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'q': + OPTION('q', "quiet", NULL, "Suppress information messages during runtime"): arg_quiet = true; break; - case ARG_READ_ONLY: + OPTION_LONG("read-only", NULL, "Do not accept any user input on stdin"): arg_read_only = true; break; - case ARG_BACKGROUND: - r = parse_background_argument(optarg, &arg_background); + OPTION_LONG("background", "COLOR", "Set ANSI color for background"): + r = parse_background_argument(arg, &arg_background); if (r < 0) return r; break; - case ARG_TITLE: - r = free_and_strdup_warn(&arg_title, optarg); + OPTION_LONG("title", "TITLE", "Set terminal title"): + r = free_and_strdup_warn(&arg_title, arg); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind >= argc) + if (option_parser_get_n_args(&state) == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected command line, refusing."); + *remaining_args = option_parser_get_args(&state); return 1; } @@ -155,11 +138,12 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - _cleanup_strv_free_ char **l = strv_copy(argv + optind); + _cleanup_strv_free_ char **l = strv_copy(args); if (!l) return log_oom(); diff --git a/src/random-seed/random-seed-tool.c b/src/random-seed/random-seed-tool.c index b539c1f654fb6..3e40fcdddaf36 100644 --- a/src/random-seed/random-seed-tool.c +++ b/src/random-seed/random-seed-tool.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -14,18 +13,20 @@ #include "build.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" #include "fs-util.h" #include "io-util.h" #include "log.h" #include "main-func.h" #include "mkdir.h" +#include "options.h" #include "parse-util.h" #include "pretty-print.h" #include "random-util.h" #include "sha256.h" -#include "string-table.h" #include "string-util.h" #include "sync-util.h" +#include "verbs.h" #include "xattr-util.h" typedef enum SeedAction { @@ -296,75 +297,75 @@ static int save_seed_file( return 0; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-random-seed", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND\n" - "\n%5$sLoad and save the system random seed at boot and shutdown.%6$s\n" - "\n%3$sCommands:%4$s\n" - " load Load a random seed saved on disk into the kernel entropy pool\n" - " save Save a new random seed on disk\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] COMMAND\n" + "\n%sLoad and save the system random seed at boot and shutdown.%s\n" + "\nCommands:\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\nOptions:\n"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static const char* const seed_action_table[_ACTION_MAX] = { - [ACTION_LOAD] = "load", - [ACTION_SAVE] = "save", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(seed_action, SeedAction); +VERB_FULL(verb_set_action, "load", NULL, VERB_ANY, 1, 0, ACTION_LOAD, + "Load a random seed saved on disk into the kernel entropy pool"); +VERB_FULL(verb_set_action, "save", NULL, VERB_ANY, 1, 0, ACTION_SAVE, + "Save a new random seed on disk"); +static int verb_set_action(int argc, char *argv[], uintptr_t data, void *userdata) { + arg_action = data; + return 0; +} static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(0, NULL, NULL); - case ARG_VERSION: - return version(); - case '?': - return -EINVAL; - default: - assert_not_reached(); - } + OPTION_COMMON_HELP: + return help(); - if (optind + 1 != argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires one argument."); + OPTION_COMMON_VERSION: + return version(); + } - arg_action = seed_action_from_string(argv[optind]); - if (arg_action < 0) - return log_error_errno(arg_action, "Unknown action '%s'", argv[optind]); + r = dispatch_verb_with_args(option_parser_get_args(&state), NULL); + if (r < 0) + return r; return 1; } diff --git a/src/repart/iso9660.c b/src/repart/iso9660.c new file mode 100644 index 0000000000000..a555fb9f9e3b6 --- /dev/null +++ b/src/repart/iso9660.c @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "iso9660.h" +#include "log.h" +#include "stdio-util.h" +#include "string-util.h" +#include "time-util.h" + +void iso9660_datetime_zero(struct iso9660_datetime *ret) { + assert(ret); + + memcpy(ret->year, "0000", 4); + memcpy(ret->month, "00", 2); + memcpy(ret->day, "00", 2); + memcpy(ret->hour, "00", 2); + memcpy(ret->minute, "00", 2); + memcpy(ret->second, "00", 2); + memcpy(ret->deci, "00", 2); + ret->zone = 0; +} + +static int validate_tm(const struct tm *t) { + assert(t); + + /* Safety checks on bounded fields of struct tm, ranges as per tm(3type). Mostly in place because + * ISO9660 date/time ranges and struct tm ranges differ. */ + + if (t->tm_mon < 0 || t->tm_mon > 11) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Month out of range."); + if (t->tm_mday < 1 || t->tm_mday > 31) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Day of month out of range."); + if (t->tm_hour < 0 || t->tm_hour > 23) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Hour out of range."); + if (t->tm_min < 0 || t->tm_min > 59) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Minute out of range."); + if (t->tm_sec < 0 || t->tm_sec > 60) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Seconds out of range."); + + return 0; +} + +int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *ret) { + struct tm t; + int r; + + assert(ret); + + r = localtime_or_gmtime_usec(usec, utc, &t); + if (r < 0) + return r; + + r = validate_tm(&t); + if (r < 0) + return r; + + if (t.tm_year >= 10000 - 1900) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Year has more than 4 digits and is incompatible with ISO9660."); + if (t.tm_year + 1900 < 0) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Year is negative and is incompatible with ISO9660."); + + long offset = t.tm_gmtoff / (15*60); /* The time zone is encoded by 15 minutes increments */ + if (offset < INT8_MIN || offset > INT8_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "GMT offset out of range."); + + char buf[17]; + assert_cc(sizeof(buf)-1 == offsetof(struct iso9660_datetime, zone)); + /* Ignore leap seconds, no real hope for hardware. Deci-seconds always zero. */ + xsprintf(buf, "%04d%02d%02d%02d%02d%02d00", + t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, + t.tm_hour, t.tm_min, MIN(t.tm_sec, 59)); + memcpy(ret, buf, sizeof(buf)-1); + + ret->zone = offset; + + return 0; +} + +int iso9660_dir_datetime_from_usec(usec_t usec, bool utc, struct iso9660_dir_time *ret) { + struct tm t; + int r; + + assert(ret); + + r = localtime_or_gmtime_usec(usec, utc, &t); + if (r < 0) + return r; + + r = validate_tm(&t); + if (r < 0) + return r; + + if (t.tm_year < 0 || t.tm_year > UINT8_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Year is incompatible with ISO9660."); + + long offset = t.tm_gmtoff / (15*60); /* The time zone is encoded by 15 minutes increments */ + if (offset < INT8_MIN || offset > INT8_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "GMT offset out of range."); + + *ret = (struct iso9660_dir_time) { + .year = t.tm_year, + .month = t.tm_mon + 1, + .day = t.tm_mday, + .hour = t.tm_hour, + .minute = t.tm_min, + .second = MIN(t.tm_sec, 59), + /* The time zone is encoded by 15 minutes increments */ + .offset = offset, + }; + + return 0; +} + +static bool iso9660_valid_string(const char *str, bool allow_a_chars) { + /* note that a-chars are not supposed to accept lower case letters, but it looks like common practice + * to use them + */ + return in_charset(str, allow_a_chars ? UPPERCASE_LETTERS LOWERCASE_LETTERS DIGITS " _!\"%&'()*+,-./:;<=>?" : UPPERCASE_LETTERS DIGITS "_"); +} + +int iso9660_set_string(char target[], size_t len, const char *source, bool allow_a_chars) { + assert(target || len == 0); + + if (source) { + if (!iso9660_valid_string(source, allow_a_chars)) + return -EINVAL; + + size_t slen = strlen(source); + if (slen > len) + return -EINVAL; + + memset(mempcpy(target, source, slen), ' ', len - slen); + } else + memset(target, ' ', len); + + return 0; +} + +bool iso9660_volume_name_valid(const char *name) { + /* In theory the volume identifier should be d-chars, but in practice, a-chars are allowed */ + return iso9660_valid_string(name, /* allow_a_chars= */ true) && + strlen(name) <= 32; +} + +bool iso9660_system_name_valid(const char *name) { + return iso9660_valid_string(name, /* allow_a_chars= */ true) && + strlen(name) <= 32; +} + +bool iso9660_publisher_name_valid(const char *name) { + return iso9660_valid_string(name, /* allow_a_chars= */ true) && + strlen(name) <= 128; +} diff --git a/src/repart/iso9660.h b/src/repart/iso9660.h new file mode 100644 index 0000000000000..cdb6eef22678f --- /dev/null +++ b/src/repart/iso9660.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" +#include "sparse-endian.h" + +/* ISO9660 is 5 blocks: + * - Primary descriptor + * - El torito descriptor + * - Terminal descriptor + * - El Torito boot catalog + * - Root directory + */ +#define ISO9660_BLOCK_SIZE 2048U +#define ISO9660_START 16U +#define ISO9660_PRIMARY_DESCRIPTOR (ISO9660_START+0U) +#define ISO9660_ELTORITO_DESCRIPTOR (ISO9660_START+1U) +#define ISO9660_TERMINAL_DESCRIPTOR (ISO9660_START+2U) +#define ISO9660_BOOT_CATALOG (ISO9660_START+3U) +#define ISO9660_ROOT_DIRECTORY (ISO9660_START+4U) +#define ISO9660_SIZE 5U + +struct _packed_ iso9660_volume_descriptor_header { + uint8_t type; + char identifier[5]; + uint8_t version; +}; + +struct _packed_ iso9660_terminal_descriptor { + struct iso9660_volume_descriptor_header header; + uint8_t data[2041]; +}; +assert_cc(sizeof(struct iso9660_terminal_descriptor) == 2048); + +struct _packed_ iso9660_datetime { + char year[4]; + char month[2]; + char day[2]; + char hour[2]; + char minute[2]; + char second[2]; + char deci[2]; + int8_t zone; +}; + +struct _packed_ iso9660_eltorito_descriptor { + struct iso9660_volume_descriptor_header header; + + char boot_system_identifier[32]; + uint8_t unused_1[32]; + le32_t boot_catalog_sector; + uint8_t unused_2[1973]; +}; + +assert_cc(sizeof(struct iso9660_eltorito_descriptor) == 2048); + +struct _packed_ iso9660_dir_time { + uint8_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + int8_t offset; +}; + +struct _packed_ iso9660_directory_entry { + uint8_t len; + uint8_t xattr_len; + le32_t extent_loc_little; + be32_t extent_loc_big; + le32_t data_len_little; + be32_t data_len_big; + struct iso9660_dir_time time; + uint8_t flags; + uint8_t unit_size; + uint8_t gap_size; + le16_t volume_seq_num_little; + be16_t volume_seq_num_big; + uint8_t ident_len; + char ident[1]; /* variable */ +}; + +struct _packed_ iso9660_primary_volume_descriptor { + struct iso9660_volume_descriptor_header header; + + uint8_t unused_1; + char system_identifier[32]; + char volume_identifier[32]; + uint8_t unused_2[8]; + le32_t volume_space_size_little; + be32_t volume_space_size_big; + uint8_t unused_3[32]; + + le16_t volume_set_size_little; + be16_t volume_set_size_big; + le16_t volume_sequence_number_little; + be16_t volume_sequence_number_big; + le16_t logical_block_size_little; + be16_t logical_block_size_big; + + le32_t path_table_size_little; + be32_t path_table_size_big; + + le32_t path_table_little; + le32_t opt_path_table_little; + + be32_t path_table_big; + be32_t opt_path_table_big; + + struct iso9660_directory_entry root_directory_entry; + + char volume_set_identifier[128]; + char publisher_identifier[128]; + char data_preparer_identifier[128]; + char application_identifier[128]; + + char copyright_file_identifier[37]; + char abstract_file_identifier[37]; + char bibliographic_file_identifier[37]; + + struct iso9660_datetime volume_creation_date; + struct iso9660_datetime volume_modification_date; + struct iso9660_datetime volume_expiration_date; + struct iso9660_datetime volume_effective_date; + + uint8_t file_structure_version; /* 1 */ + uint8_t unused_5; + char application_used[512]; + uint8_t reserved[653]; +}; +assert_cc(sizeof(struct iso9660_primary_volume_descriptor) == 2048); + +struct _packed_ el_torito_validation_entry { + uint8_t header_indicator; + uint8_t platform; + uint8_t reserved[2]; + char id_string[24]; + le16_t checksum; + uint8_t key_bytes[2]; +}; + +struct _packed_ el_torito_initial_entry { + uint8_t boot_indicator; + uint8_t boot_media_type; + le16_t load_segment; + uint8_t system_type; + uint8_t unused_1[1]; + le16_t sector_count; + le32_t load_rba; + uint8_t unused_2[20]; +}; + +struct _packed_ el_torito_section_header { + uint8_t header_indicator; + uint8_t platform; + le16_t nentries; + char id_string[28]; +}; + +void iso9660_datetime_zero(struct iso9660_datetime *ret); +int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *ret); +int iso9660_dir_datetime_from_usec(usec_t usec, bool utc, struct iso9660_dir_time *ret); +int iso9660_set_string(char target[], size_t len, const char *source, bool allow_a_chars); + +static inline void iso9660_set_const_string(char target[], size_t len, const char *source, bool allow_a_chars) { + assert_se(iso9660_set_string(target, len, source, allow_a_chars) == 0); +} + +bool iso9660_volume_name_valid(const char *name); +bool iso9660_system_name_valid(const char *name); +bool iso9660_publisher_name_valid(const char *name); diff --git a/src/repart/meson.build b/src/repart/meson.build index e6e32f54c7a25..9b89f56f7a0f2 100644 --- a/src/repart/meson.build +++ b/src/repart/meson.build @@ -8,16 +8,15 @@ executables += [ executable_template + { 'name' : 'systemd-repart', 'public' : true, - 'extract' : files('repart.c'), - 'link_with' : [ - libshared, - libshared_fdisk, - ], + 'extract' : files( + 'repart.c', + 'iso9660.c', + ), 'dependencies' : [ libblkid_cflags, - libfdisk, + libfdisk_cflags, libmount_cflags, - libopenssl, + libopenssl_cflags, threads, ], }, @@ -28,15 +27,14 @@ executables += [ 'link_with' : [ libc_wrapper_static, libbasic_static, - libshared_fdisk, libshared_static, libsystemd_static, ], 'dependencies' : [ libblkid_cflags, - libfdisk, + libfdisk_cflags, libmount_cflags, - libopenssl, + libopenssl_cflags, threads, ], }, diff --git a/src/repart/repart.c b/src/repart/repart.c index f6ca9aca0565a..0aab755d2cf25 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -25,6 +24,7 @@ #include "conf-parser.h" #include "constants.h" #include "copy.h" +#include "crypto-util.h" #include "cryptsetup-util.h" #include "device-util.h" #include "devnum-util.h" @@ -49,6 +49,7 @@ #include "initrd-util.h" #include "install-file.h" #include "io-util.h" +#include "iso9660.h" #include "json-util.h" #include "libmount-util.h" #include "list.h" @@ -59,7 +60,7 @@ #include "mount-util.h" #include "mountpoint-util.h" #include "nulstr-util.h" -#include "openssl-util.h" +#include "options.h" #include "parse-argument.h" #include "parse-helpers.h" #include "parse-util.h" @@ -200,6 +201,7 @@ static size_t arg_n_defer_partitions = 0; static bool arg_defer_partitions_empty = false; static bool arg_defer_partitions_factory_reset = false; static uint64_t arg_sector_size = 0; +static uint64_t arg_grain_size = 0; static ImagePolicy *arg_image_policy = NULL; static Architecture arg_architecture = _ARCHITECTURE_INVALID; static int arg_offline = -1; @@ -212,6 +214,10 @@ static char *arg_generate_crypttab = NULL; static Set *arg_verity_settings = NULL; static bool arg_relax_copy_block_security = false; static bool arg_varlink = false; +static bool arg_eltorito = false; +static char *arg_eltorito_system = NULL; +static char *arg_eltorito_volume = NULL; +static char *arg_eltorito_publisher = NULL; STATIC_DESTRUCTOR_REGISTER(arg_node, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); @@ -236,6 +242,9 @@ STATIC_DESTRUCTOR_REGISTER(arg_make_ddi, freep); STATIC_DESTRUCTOR_REGISTER(arg_generate_fstab, freep); STATIC_DESTRUCTOR_REGISTER(arg_generate_crypttab, freep); STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, set_freep); +STATIC_DESTRUCTOR_REGISTER(arg_eltorito_system, freep); +STATIC_DESTRUCTOR_REGISTER(arg_eltorito_volume, freep); +STATIC_DESTRUCTOR_REGISTER(arg_eltorito_publisher, freep); typedef enum ProgressPhase { PROGRESS_LOADING_DEFINITIONS, @@ -281,6 +290,14 @@ typedef enum IntegrityAlg { _INTEGRITY_ALG_INVALID = -EINVAL, } IntegrityAlg; +typedef enum EncryptKDF { + ENCRYPT_KDF_ARGON2ID, + ENCRYPT_KDF_PBKDF2, + ENCRYPT_KDF_MINIMAL, + _ENCRYPT_KDF_MAX, + _ENCRYPT_KDF_INVALID = -EINVAL, +} EncryptKDF; + typedef enum VerityMode { VERITY_OFF, VERITY_DATA, @@ -463,6 +480,7 @@ typedef struct Partition { size_t tpm2_n_hash_pcr_values; IntegrityMode integrity; IntegrityAlg integrity_alg; + EncryptKDF encrypt_kdf; VerityMode verity; char *verity_match_key; MinimizeMode minimize; @@ -471,6 +489,7 @@ typedef struct Partition { char *compression; char *compression_level; uint64_t fs_sector_size; + int discard; int add_validatefs; CopyFiles *copy_files; @@ -588,6 +607,12 @@ static const char *integrity_alg_table[_INTEGRITY_ALG_MAX] = { [INTEGRITY_ALG_HMAC_SHA512] = "hmac-sha512", }; +static const char *encrypt_kdf_table[_ENCRYPT_KDF_MAX] = { + [ENCRYPT_KDF_ARGON2ID] = "argon2id", + [ENCRYPT_KDF_PBKDF2] = "pbkdf2", + [ENCRYPT_KDF_MINIMAL] = "minimal", +}; + static const char *verity_mode_table[_VERITY_MODE_MAX] = { [VERITY_OFF] = "off", [VERITY_DATA] = "data", @@ -617,11 +642,16 @@ static const char *progress_phase_table[_PROGRESS_PHASE_MAX] = { [PROGRESS_REREADING_TABLE] = "rereading-table", }; +static uint64_t determine_grain_size(uint64_t sector_size) { + return MAX(arg_grain_size > 0 ? arg_grain_size : 4096U, sector_size); +} + DEFINE_PRIVATE_STRING_TABLE_LOOKUP(empty_mode, EmptyMode); DEFINE_PRIVATE_STRING_TABLE_LOOKUP(append_mode, AppendMode); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(integrity_mode, IntegrityMode, INTEGRITY_INLINE); DEFINE_PRIVATE_STRING_TABLE_LOOKUP(integrity_alg, IntegrityAlg); +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(encrypt_kdf, EncryptKDF); DEFINE_PRIVATE_STRING_TABLE_LOOKUP(verity_mode, VerityMode); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(minimize_mode, MinimizeMode, MINIMIZE_BEST); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(progress_phase, ProgressPhase); @@ -647,6 +677,8 @@ static int calculate_verity_hash_size( uint64_t data_block_size, uint64_t *ret_bytes) { + assert(ret_bytes); + /* The calculation here is based on the documented on-disk format of the dm-verity * https://docs.kernel.org/admin-guide/device-mapper/verity.html#hash-tree * @@ -721,6 +753,8 @@ static Partition *partition_new(Context *c) { .last_percent = UINT_MAX, .progress_ratelimit = { 100 * USEC_PER_MSEC, 1 }, .fs_sector_size = UINT64_MAX, + .discard = -1, + .encrypt_kdf = _ENCRYPT_KDF_INVALID, }; return p; @@ -759,9 +793,9 @@ static Partition* partition_free(Partition *p) { strv_free(p->drop_in_files); if (p->current_partition) - fdisk_unref_partition(p->current_partition); + sym_fdisk_unref_partition(p->current_partition); if (p->new_partition) - fdisk_unref_partition(p->new_partition); + sym_fdisk_unref_partition(p->new_partition); if (p->copy_blocks_path_is_our_file) unlink_and_free(p->copy_blocks_path); @@ -844,6 +878,7 @@ static void partition_foreignize(Partition *p) { p->verity = VERITY_OFF; p->add_validatefs = false; p->fs_sector_size = UINT64_MAX; + p->discard = -1; partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints); p->mountpoints = NULL; @@ -941,7 +976,7 @@ static Context* context_free(Context *context) { context_free_free_areas(context); if (context->fdisk_context) - fdisk_unref_context(context->fdisk_context); + sym_fdisk_unref_context(context->fdisk_context); safe_close(context->backing_fd); if (context->node_is_our_file) @@ -950,9 +985,11 @@ static Context* context_free(Context *context) { free(context->node); #if HAVE_OPENSSL - X509_free(context->certificate); + if (context->certificate) + sym_X509_free(context->certificate); openssl_ask_password_ui_free(context->ui); - EVP_PKEY_free(context->private_key); + if (context->private_key) + sym_EVP_PKEY_free(context->private_key); #endif context->link = sd_varlink_unref(context->link); @@ -1098,7 +1135,7 @@ static uint64_t partition_min_size(const Context *context, const Partition *p) { * exists the current size is what we really need. If it doesn't exist yet refuse to allocate less * than 4K. * - * DEFAULT_MIN_SIZE is the default SizeMin= we configure if nothing else is specified. */ + * DEFAULT_MIN_SIZE is the default SizeMinBytes= we configure if nothing else is specified. */ if (PARTITION_IS_FOREIGN(p)) { /* Don't allow changing size of partitions not managed by us */ @@ -1115,16 +1152,16 @@ static uint64_t partition_min_size(const Context *context, const Partition *p) { uint64_t d = 0; if (p->encrypt != ENCRYPT_OFF) - d += round_up_size(LUKS2_METADATA_KEEP_FREE, context->grain_size); + assert_se(INC_SAFE(&d, round_up_size(LUKS2_METADATA_KEEP_FREE, context->grain_size))); if (p->copy_blocks_size != UINT64_MAX) - d += round_up_size(p->copy_blocks_size, context->grain_size); + assert_se(INC_SAFE(&d, round_up_size(p->copy_blocks_size, context->grain_size))); else if (p->format || p->encrypt != ENCRYPT_OFF) { uint64_t f; /* If we shall synthesize a file system, take minimal fs size into account (assumed to be 4K if not known) */ f = partition_fstype_min_size(context, p); - d += f == UINT64_MAX ? context->grain_size : round_up_size(f, context->grain_size); + assert_se(INC_SAFE(&d, f == UINT64_MAX ? context->grain_size : round_up_size(f, context->grain_size))); } if (d > sz) @@ -1229,6 +1266,8 @@ static uint64_t free_area_current_end(Context *context, const FreeArea *a) { assert(a->after->offset != UINT64_MAX); assert(a->after->current_size != UINT64_MAX); + /* Silence static analyzers */ + assert(a->after->current_size <= UINT64_MAX - a->after->offset); /* Calculate where the free area ends, based on the offset of the partition preceding it. */ return round_up_size(a->after->offset + a->after->current_size, context->grain_size) + free_area_available(a); @@ -1259,6 +1298,8 @@ static uint64_t free_area_available_for_new_partitions(Context *context, const F } static int free_area_compare(FreeArea *const *a, FreeArea *const*b, Context *context) { + assert(a); + assert(b); assert(context); return CMP(free_area_available_for_new_partitions(context, *a), @@ -1701,7 +1742,7 @@ static void context_place_partitions(Context *context) { for (size_t i = 0; i < context->n_free_areas; i++) { FreeArea *a = context->free_areas[i]; - _unused_ uint64_t left; + uint64_t left; uint64_t start; if (a->after) { @@ -1717,6 +1758,8 @@ static void context_place_partitions(Context *context) { left = a->size; LIST_FOREACH(partitions, p, context->partitions) { + uint64_t gap; + if (p->allocated_to_area != a) continue; @@ -1730,6 +1773,21 @@ static void context_place_partitions(Context *context) { assert(left >= p->new_padding); start += p->new_padding; left -= p->new_padding; + + /* Re-align start to the grain after each partition, so that the next + * partition placed into this free area also starts on a grain boundary. + * This matters when the grain is larger than the default (e.g. 1 MiB via + * --grain-size=) and a small partition like verity-sig (16 KiB) precedes + * a larger one: without this, the successor would start at an unaligned + * offset. */ + gap = round_up_size(start, context->grain_size) - start; + if (gap > left) { + log_warning("Not enough space left in free area to re-align partition start to grain size, " + "next partition may start at an unaligned offset."); + gap = 0; + } + start += gap; + left -= gap; } } } @@ -2652,6 +2710,8 @@ static int parse_key_file(const char *filename, struct iovec *key) { size_t n = 0; int r; + assert(key); + r = read_full_file_full( AT_FDCWD, filename, /* offset= */ UINT64_MAX, @@ -2694,6 +2754,7 @@ static int config_parse_key_file( static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_integrity, integrity_mode, IntegrityMode, INTEGRITY_OFF); static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_integrity_alg, integrity_alg, IntegrityAlg, INTEGRITY_ALG_HMAC_SHA256); +static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_encrypt_kdf, encrypt_kdf, EncryptKDF, _ENCRYPT_KDF_INVALID); static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, VerityMode, VERITY_OFF); static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_minimize, minimize_mode, MinimizeMode, MINIMIZE_OFF); @@ -2796,7 +2857,7 @@ static int context_notify( if (c->link) { r = sd_varlink_notifybo( c->link, - SD_JSON_BUILD_PAIR("phase", JSON_BUILD_STRING_UNDERSCORIFY(progress_phase_to_string(phase))), + JSON_BUILD_PAIR_ENUM("phase", progress_phase_to_string(phase)), JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object), JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("progress", percent, UINT_MAX)); if (r < 0) @@ -2851,11 +2912,13 @@ static int partition_read_definition( { "Partition", "KeyFile", config_parse_key_file, 0, p }, { "Partition", "Integrity", config_parse_integrity, 0, &p->integrity }, { "Partition", "IntegrityAlgorithm", config_parse_integrity_alg, 0, &p->integrity_alg }, + { "Partition", "EncryptKDF", config_parse_encrypt_kdf, 0, &p->encrypt_kdf }, { "Partition", "Compression", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression }, { "Partition", "CompressionLevel", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression_level }, { "Partition", "SupplementFor", config_parse_string, 0, &p->supplement_for_name }, { "Partition", "AddValidateFS", config_parse_tristate, 0, &p->add_validatefs }, { "Partition", "FileSystemSectorSize", config_parse_fs_sector_size, 0, &p->fs_sector_size }, + { "Partition", "Discard", config_parse_tristate, 0, &p->discard }, {} }; _cleanup_free_ char *filename = NULL; @@ -2929,7 +2992,7 @@ static int partition_read_definition( if (streq_ptr(p->format, "empty")) { p->format = mfree(p->format); - if (p->no_auto < 0) + if (p->no_auto < 0 && gpt_partition_type_knows_no_auto(p->type)) p->no_auto = true; if (!p->new_label) { @@ -2943,9 +3006,13 @@ static int partition_read_definition( return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "Minimize= can only be enabled if Format= or Verity=hash are set."); - if (p->minimize == MINIMIZE_BEST && (p->format && !fstype_is_ro(p->format)) && p->verity != VERITY_HASH) + if (p->minimize == MINIMIZE_BEST && + p->format && + !fstype_is_ro(p->format) && + !streq(p->format, "btrfs") && + p->verity != VERITY_HASH) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Minimize=best can only be used with read-only filesystems or Verity=hash."); + "Minimize=best can only be used with read-only filesystems, btrfs, or Verity=hash."); if (partition_needs_populate(p) && !mkfs_supports_root_option(p->format) && geteuid() != 0) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EPERM), @@ -2957,7 +3024,7 @@ static int partition_read_definition( "Cannot format %s filesystem without source files, refusing.", p->format); if (p->verity != VERITY_OFF || p->encrypt != ENCRYPT_OFF) { - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return log_syntax(NULL, LOG_ERR, path, 1, r, "libcryptsetup not found, Verity=/Encrypt= are not supported: %m"); @@ -2972,10 +3039,20 @@ static int partition_read_definition( "VerityMatchKey= can only be set if Verity= is not \"%s\".", verity_mode_to_string(p->verity)); - if (IN_SET(p->verity, VERITY_HASH, VERITY_SIG) && (p->copy_blocks_path || p->copy_blocks_auto || p->format || partition_needs_populate(p))) - return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "CopyBlocks=/CopyFiles=/Format=/MakeDirectories=/MakeSymlinks= cannot be used with Verity=%s.", - verity_mode_to_string(p->verity)); + if (IN_SET(p->verity, VERITY_HASH, VERITY_SIG)) { + if (p->format || partition_needs_populate(p)) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "CopyFiles=/Format=/MakeDirectories=/MakeSymlinks= cannot be used with Verity=%s.", + verity_mode_to_string(p->verity)); + + /* Later we check that the same CopyBlocks= type (auto vs path) is used for the entire verity set. + * So we assume that CopyBlocks=auto is going to be correct and just path based blocks might result in + * a broken setup */ + if (p->copy_blocks_path) + log_syntax(NULL, LOG_DEBUG, path, 1, 0, + "CopyBlocks= with Verity=%s bypasses dm-verity hash/signature computation; repart cannot verify the resulting setup is correct.", + verity_mode_to_string(p->verity)); + } if (p->verity != VERITY_OFF && p->encrypt != ENCRYPT_OFF) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), @@ -3006,6 +3083,18 @@ static int partition_read_definition( "SupplementFor= cannot be combined with CopyBlocks=/Encrypt=/Verity="); } + if (p->encrypt == ENCRYPT_OFF && p->discard > 0) + log_syntax(NULL, LOG_WARNING, path, 1, 0, + "Discard=yes has no effect with Encrypt=off."); + + if (p->encrypt == ENCRYPT_OFF && p->encrypt_kdf >= 0) + log_syntax(NULL, LOG_WARNING, path, 1, 0, + "EncryptKDF= has no effect with Encrypt=off."); + + if (p->encrypt != ENCRYPT_OFF && p->integrity == INTEGRITY_INLINE && p->discard > 0) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "Integrity=inline is incompatible with Discard=yes."); + /* Verity partitions are read only, let's imply the RO flag hence, unless explicitly configured otherwise. */ if ((partition_designator_is_verity_hash(p->type.designator) || partition_designator_is_verity_sig(p->type.designator) || @@ -3111,31 +3200,31 @@ static int determine_current_padding( assert(p); assert(ret); - if (!fdisk_partition_has_end(p)) + if (!sym_fdisk_partition_has_end(p)) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition has no end."); - offset = fdisk_partition_get_end(p); + offset = sym_fdisk_partition_get_end(p); assert(offset < UINT64_MAX); offset++; /* The end is one sector before the next partition or padding. */ assert(offset < UINT64_MAX / secsz); offset *= secsz; - n_partitions = fdisk_table_get_nents(t); + n_partitions = sym_fdisk_table_get_nents(t); for (size_t i = 0; i < n_partitions; i++) { struct fdisk_partition *q; uint64_t start; - q = fdisk_table_get_partition(t, i); + q = sym_fdisk_table_get_partition(t, i); if (!q) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); - if (fdisk_partition_is_used(q) <= 0) + if (sym_fdisk_partition_is_used(q) <= 0) continue; - if (!fdisk_partition_has_start(q)) + if (!sym_fdisk_partition_has_start(q)) continue; - start = fdisk_partition_get_start(q); + start = sym_fdisk_partition_get_start(q); assert(start < UINT64_MAX / secsz); start *= secsz; @@ -3145,7 +3234,7 @@ static int determine_current_padding( if (next == UINT64_MAX) { /* No later partition? In that case check the end of the usable area */ - next = fdisk_get_last_lba(c); + next = sym_fdisk_get_last_lba(c); assert(next < UINT64_MAX); next++; /* The last LBA is one sector before the end */ @@ -3164,26 +3253,6 @@ static int determine_current_padding( return 0; } -static int verify_regular_or_block(int fd) { - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - if (S_ISDIR(st.st_mode)) - return -EISDIR; - - if (S_ISLNK(st.st_mode)) - return -ELOOP; - - if (!S_ISREG(st.st_mode) && !S_ISBLK(st.st_mode)) - return -EBADFD; - - return 0; -} - static int context_copy_from_one(Context *context, const char *src) { _cleanup_close_ int fd = -EBADF; _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL; @@ -3199,29 +3268,29 @@ static int context_copy_from_one(Context *context, const char *src) { if (r < 0) return r; - r = verify_regular_or_block(fd); + r = fd_verify_regular_or_block(fd); if (r < 0) - return log_error_errno(r, "%s is not a file nor a block device: %m", src); + return log_error_errno(r, "'%s' is not a file nor a block device: %m", src); r = fdisk_new_context_at(fd, /* path= */ NULL, /* read_only= */ true, /* sector_size= */ UINT32_MAX, &c); if (r < 0) return log_error_errno(r, "Failed to create fdisk context: %m"); - secsz = fdisk_get_sector_size(c); - grainsz = fdisk_get_grain_size(c); + secsz = sym_fdisk_get_sector_size(c); + grainsz = sym_fdisk_get_grain_size(c); /* Insist on a power of two, and that it's a multiple of 512, i.e. the traditional sector size. */ if (secsz < 512 || !ISPOWEROF2(secsz)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Sector size %lu is not a power of two larger than 512? Refusing.", secsz); - if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) + if (!sym_fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Cannot copy from disk %s with no GPT disk label.", src); - r = fdisk_get_partitions(c, &t); + r = sym_fdisk_get_partitions(c, &t); if (r < 0) return log_error_errno(r, "Failed to acquire partition table: %m"); - n_partitions = fdisk_table_get_nents(t); + n_partitions = sym_fdisk_table_get_nents(t); for (size_t i = 0; i < n_partitions; i++) { _cleanup_(partition_freep) Partition *np = NULL; _cleanup_free_ char *label_copy = NULL; @@ -3231,16 +3300,16 @@ static int context_copy_from_one(Context *context, const char *src) { sd_id128_t ptid, id; GptPartitionType type; - p = fdisk_table_get_partition(t, i); + p = sym_fdisk_table_get_partition(t, i); if (!p) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); - if (fdisk_partition_is_used(p) <= 0) + if (sym_fdisk_partition_is_used(p) <= 0) continue; - if (fdisk_partition_has_start(p) <= 0 || - fdisk_partition_has_size(p) <= 0 || - fdisk_partition_has_partno(p) <= 0) + if (sym_fdisk_partition_has_start(p) <= 0 || + sym_fdisk_partition_has_size(p) <= 0 || + sym_fdisk_partition_has_partno(p) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a position, size or number."); r = fdisk_partition_get_type_as_id128(p, &ptid); @@ -3253,18 +3322,18 @@ static int context_copy_from_one(Context *context, const char *src) { if (r < 0) return log_error_errno(r, "Failed to query partition UUID: %m"); - label = fdisk_partition_get_name(p); + label = sym_fdisk_partition_get_name(p); if (!isempty(label)) { label_copy = strdup(label); if (!label_copy) return log_oom(); } - sz = fdisk_partition_get_size(p); + sz = sym_fdisk_partition_get_size(p); assert(sz <= UINT64_MAX/secsz); sz *= secsz; - start = fdisk_partition_get_start(p); + start = sym_fdisk_partition_get_start(p); assert(start <= UINT64_MAX/secsz); start *= secsz; @@ -3468,6 +3537,27 @@ static int context_read_definitions(Context *context) { } } + LIST_FOREACH(partitions, p, context->partitions) { + if (!IN_SET(p->verity, VERITY_HASH, VERITY_SIG)) + continue; + + /* We check all verity siblings up until our current type and ensure that if we are using CopyBlocks= + * the previous ones are using the same type of CopyBlocks=. */ + for (VerityMode mode = VERITY_DATA; mode < p->verity; mode++) { + Partition *q = ASSERT_PTR(p->siblings[mode]); + + if (p->copy_blocks_auto && !q->copy_blocks_auto) + return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), + "CopyBlocks=auto set with Verity=%s but Verity=%s partition does not set CopyBlocks=auto.", + verity_mode_to_string(p->verity), verity_mode_to_string(mode)); + + if (p->copy_blocks_path && !q->copy_blocks_path) + return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), + "CopyBlocks= set with Verity=%s but Verity=%s partition does not set CopyBlocks=.", + verity_mode_to_string(p->verity), verity_mode_to_string(mode)); + } + } + LIST_FOREACH(partitions, p, context->partitions) { Partition *dp; @@ -3518,14 +3608,14 @@ static int fdisk_ask_cb(struct fdisk_context *c, struct fdisk_ask *ask, void *da _cleanup_free_ char *ids = NULL; int r; - if (fdisk_ask_get_type(ask) != FDISK_ASKTYPE_STRING) + if (sym_fdisk_ask_get_type(ask) != FDISK_ASKTYPE_STRING) return -EINVAL; ids = new(char, SD_ID128_UUID_STRING_MAX); if (!ids) return -ENOMEM; - r = fdisk_ask_string_set_result(ask, sd_id128_to_uuid_string(*(sd_id128_t*) data, ids)); + r = sym_fdisk_ask_string_set_result(ask, sd_id128_to_uuid_string(*(sd_id128_t*) data, ids)); if (r < 0) return r; @@ -3536,15 +3626,15 @@ static int fdisk_ask_cb(struct fdisk_context *c, struct fdisk_ask *ask, void *da static int fdisk_set_disklabel_id_by_uuid(struct fdisk_context *c, sd_id128_t id) { int r; - r = fdisk_set_ask(c, fdisk_ask_cb, &id); + r = sym_fdisk_set_ask(c, fdisk_ask_cb, &id); if (r < 0) return r; - r = fdisk_set_disklabel_id(c); + r = sym_fdisk_set_disklabel_id(c); if (r < 0) return r; - return fdisk_set_ask(c, NULL, NULL); + return sym_fdisk_set_ask(c, NULL, NULL); } static int derive_uuid(sd_id128_t base, const char *token, sd_id128_t *ret) { @@ -3579,7 +3669,7 @@ static int context_load_fallback_metrics(Context *context) { assert(context); context->sector_size = arg_sector_size > 0 ? arg_sector_size : 512; - context->grain_size = MAX(context->sector_size, 4096U); + context->grain_size = determine_grain_size(context->sector_size); context->default_fs_sector_size = arg_sector_size > 0 ? arg_sector_size : DEFAULT_FILESYSTEM_SECTOR_SIZE; return 1; /* Starting from scratch */ } @@ -3605,13 +3695,13 @@ static int context_load_partition_table(Context *context) { context_notify(context, PROGRESS_LOADING_TABLE, /* object= */ NULL, UINT_MAX); - c = fdisk_new_context(); + c = sym_fdisk_new_context(); if (!c) return log_oom(); if (arg_sector_size > 0) { fs_secsz = arg_sector_size; - r = fdisk_save_user_sector_size(c, /* phy= */ 0, arg_sector_size); + r = sym_fdisk_save_user_sector_size(c, /* phy= */ 0, arg_sector_size); } else { uint32_t ssz; struct stat st; @@ -3644,14 +3734,14 @@ static int context_load_partition_table(Context *context) { } } - r = fdisk_save_user_sector_size(c, /* phy= */ 0, ssz); + r = sym_fdisk_save_user_sector_size(c, /* phy= */ 0, ssz); } if (r < 0) return log_error_errno(r, "Failed to set sector size: %m"); /* libfdisk doesn't have an API to operate on arbitrary fds, hence reopen the fd going via the * /proc/self/fd/ magic path if we have an existing fd. Open the original file otherwise. */ - r = fdisk_assign_device( + r = sym_fdisk_assign_device( c, context->backing_fd >= 0 ? FORMAT_PROC_FD_PATH(context->backing_fd) : context->node, context->dry_run); @@ -3670,9 +3760,9 @@ static int context_load_partition_table(Context *context) { if (S_ISREG(st.st_mode) && st.st_size == 0) { /* Use the fallback values if we have no better idea */ - context->sector_size = fdisk_get_sector_size(c); + context->sector_size = sym_fdisk_get_sector_size(c); context->default_fs_sector_size = fs_secsz; - context->grain_size = MAX(context->sector_size, 4096U); + context->grain_size = determine_grain_size(context->sector_size); return /* from_scratch= */ true; } @@ -3683,7 +3773,7 @@ static int context_load_partition_table(Context *context) { if (context->backing_fd < 0) { /* If we have no fd referencing the device yet, make a copy of the fd now, so that we have one */ - r = context_open_and_lock_backing_fd(FORMAT_PROC_FD_PATH(fdisk_get_devfd(c)), + r = context_open_and_lock_backing_fd(FORMAT_PROC_FD_PATH(sym_fdisk_get_devfd(c)), context->dry_run ? LOCK_SH : LOCK_EX, &context->backing_fd); if (r < 0) @@ -3695,15 +3785,15 @@ static int context_load_partition_table(Context *context) { * it for all our needs. Note that the values we use ourselves always are in bytes though, thus mean * the same thing universally. Also note that regardless what kind of sector size is in use we'll * place partitions at multiples of 4K. */ - unsigned long secsz = fdisk_get_sector_size(c); + unsigned long secsz = sym_fdisk_get_sector_size(c); /* Insist on a power of two, and that it's a multiple of 512, i.e. the traditional sector size. */ if (secsz < 512 || !ISPOWEROF2(secsz)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Sector size %lu is not a power of two larger than 512? Refusing.", secsz); - /* Use at least 4K, and ensure it's a multiple of the sector size, regardless if that is smaller or - * larger */ - grainsz = MAX(secsz, 4096U); + /* Determine the grain size: by default at least 4K and a multiple of the sector size, but may be + * overridden via --grain-size=. */ + grainsz = determine_grain_size(secsz); log_debug("Sector size of device is %lu bytes. Using default filesystem sector size of %" PRIu64 " and grain size of %" PRIu64 ".", secsz, fs_secsz, grainsz); @@ -3711,14 +3801,14 @@ static int context_load_partition_table(Context *context) { case EMPTY_REFUSE: /* Refuse empty disks, insist on an existing GPT partition table */ - if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) + if (!sym_fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) return log_notice_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not repartitioning.", context->node); break; case EMPTY_REQUIRE: /* Require an empty disk, refuse any existing partition table */ - r = fdisk_has_label(c); + r = sym_fdisk_has_label(c); if (r < 0) return log_error_errno(r, "Failed to determine whether disk %s has a disk label: %m", context->node); if (r > 0) @@ -3729,11 +3819,11 @@ static int context_load_partition_table(Context *context) { case EMPTY_ALLOW: /* Allow both an empty disk and an existing partition table, but only GPT */ - r = fdisk_has_label(c); + r = sym_fdisk_has_label(c); if (r < 0) return log_error_errno(r, "Failed to determine whether disk %s has a disk label: %m", context->node); if (r > 0) { - if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) + if (!sym_fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) return log_notice_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has non-GPT disk label, not repartitioning.", context->node); } else from_scratch = true; @@ -3751,7 +3841,7 @@ static int context_load_partition_table(Context *context) { } if (from_scratch) { - r = fdisk_create_disklabel(c, "gpt"); + r = sym_fdisk_create_disklabel(c, "gpt"); if (r < 0) return log_error_errno(r, "Failed to create GPT disk label: %m"); @@ -3766,7 +3856,7 @@ static int context_load_partition_table(Context *context) { goto add_initial_free_area; } - r = fdisk_get_disklabel_id(c, &disk_uuid_string); + r = sym_fdisk_get_disklabel_id(c, &disk_uuid_string); if (r < 0) return log_error_errno(r, "Failed to get current GPT disk label UUID: %m"); @@ -3776,17 +3866,17 @@ static int context_load_partition_table(Context *context) { if (r < 0) return log_error_errno(r, "Failed to acquire disk GPT uuid: %m"); - r = fdisk_set_disklabel_id(c); + r = sym_fdisk_set_disklabel_id(c); if (r < 0) return log_error_errno(r, "Failed to set GPT disk label: %m"); } else if (r < 0) return log_error_errno(r, "Failed to parse current GPT disk label UUID: %m"); - r = fdisk_get_partitions(c, &t); + r = sym_fdisk_get_partitions(c, &t); if (r < 0) return log_error_errno(r, "Failed to acquire partition table: %m"); - n_partitions = fdisk_table_get_nents(t); + n_partitions = sym_fdisk_table_get_nents(t); for (size_t i = 0; i < n_partitions; i++) { _cleanup_free_ char *label_copy = NULL; Partition *last = NULL; @@ -3797,16 +3887,16 @@ static int context_load_partition_table(Context *context) { sd_id128_t ptid, id; size_t partno; - p = fdisk_table_get_partition(t, i); + p = sym_fdisk_table_get_partition(t, i); if (!p) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); - if (fdisk_partition_is_used(p) <= 0) + if (sym_fdisk_partition_is_used(p) <= 0) continue; - if (fdisk_partition_has_start(p) <= 0 || - fdisk_partition_has_size(p) <= 0 || - fdisk_partition_has_partno(p) <= 0) + if (sym_fdisk_partition_has_start(p) <= 0 || + sym_fdisk_partition_has_size(p) <= 0 || + sym_fdisk_partition_has_partno(p) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a position, size or number."); r = fdisk_partition_get_type_as_id128(p, &ptid); @@ -3817,22 +3907,22 @@ static int context_load_partition_table(Context *context) { if (r < 0) return log_error_errno(r, "Failed to query partition UUID: %m"); - label = fdisk_partition_get_name(p); + label = sym_fdisk_partition_get_name(p); if (!isempty(label)) { label_copy = strdup(label); if (!label_copy) return log_oom(); } - sz = fdisk_partition_get_size(p); + sz = sym_fdisk_partition_get_size(p); assert(sz <= UINT64_MAX/secsz); sz *= secsz; - start = fdisk_partition_get_start(p); + start = sym_fdisk_partition_get_start(p); assert(start <= UINT64_MAX/secsz); start *= secsz; - partno = fdisk_partition_get_partno(p); + partno = sym_fdisk_partition_get_partno(p); if (left_boundary == UINT64_MAX || left_boundary > start) left_boundary = start; @@ -3853,7 +3943,7 @@ static int context_load_partition_table(Context *context) { pp->current_label = TAKE_PTR(label_copy); pp->current_partition = p; - fdisk_ref_partition(p); + sym_fdisk_ref_partition(p); r = determine_current_padding(c, t, p, secsz, grainsz, &pp->current_padding); if (r < 0) @@ -3886,7 +3976,7 @@ static int context_load_partition_table(Context *context) { np->current_label = TAKE_PTR(label_copy); np->current_partition = p; - fdisk_ref_partition(p); + sym_fdisk_ref_partition(p); r = determine_current_padding(c, t, p, secsz, grainsz, &np->current_padding); if (r < 0) @@ -3908,15 +3998,15 @@ static int context_load_partition_table(Context *context) { p->supplement_for->suppressing = NULL; add_initial_free_area: - nsectors = fdisk_get_nsectors(c); + nsectors = sym_fdisk_get_nsectors(c); assert(nsectors <= UINT64_MAX/secsz); nsectors *= secsz; - first_lba = fdisk_get_first_lba(c); + first_lba = sym_fdisk_get_first_lba(c); assert(first_lba <= UINT64_MAX/secsz); first_lba *= secsz; - last_lba = fdisk_get_last_lba(c); + last_lba = sym_fdisk_get_last_lba(c); assert(last_lba < UINT64_MAX); last_lba++; assert(last_lba <= UINT64_MAX/secsz); @@ -3984,12 +4074,12 @@ static void context_unload_partition_table(Context *context) { p->offset = UINT64_MAX; if (p->current_partition) { - fdisk_unref_partition(p->current_partition); + sym_fdisk_unref_partition(p->current_partition); p->current_partition = NULL; } if (p->new_partition) { - fdisk_unref_partition(p->new_partition); + sym_fdisk_unref_partition(p->new_partition); p->new_partition = NULL; } @@ -4010,7 +4100,7 @@ static void context_unload_partition_table(Context *context) { context->total = UINT64_MAX; if (context->fdisk_context) { - fdisk_unref_context(context->fdisk_context); + sym_fdisk_unref_context(context->fdisk_context); context->fdisk_context = NULL; } @@ -4020,6 +4110,8 @@ static void context_unload_partition_table(Context *context) { static int format_size_change(uint64_t from, uint64_t to, char **ret) { char *t; + assert(ret); + if (from != UINT64_MAX) { if (from == to || to == UINT64_MAX) t = strdup(FORMAT_BYTES(from)); @@ -4145,7 +4237,7 @@ static int context_dump_partitions(Context *context) { activity = "resize"; label = partition_label(p); - partname = p->partno != UINT64_MAX ? fdisk_partname(context->node, p->partno+1) : NULL; + partname = p->partno != UINT64_MAX ? sym_fdisk_partname(context->node, p->partno+1) : NULL; r = format_size_change(p->current_size, p->new_size, &size_change); if (r < 0) @@ -4298,6 +4390,8 @@ static int partition_hint(const Partition *p, const char *node, char **ret) { const char *label; sd_id128_t id; + assert(ret); + /* Tries really hard to find a suitable description for this partition */ if (p->definition_path) @@ -4310,7 +4404,7 @@ static int partition_hint(const Partition *p, const char *node, char **ret) { } if (p->partno != UINT64_MAX) { - buf = fdisk_partname(node, p->partno+1); + buf = sym_fdisk_partname(node, p->partno+1); goto done; } @@ -4522,16 +4616,16 @@ static int context_wipe_range(Context *context, uint64_t offset, uint64_t size) assert(offset != UINT64_MAX); assert(size != UINT64_MAX); - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_ERR); if (r < 0) - return log_error_errno(r, "Failed to load libblkid: %m"); + return r; probe = sym_blkid_new_probe(); if (!probe) return log_oom(); errno = 0; - r = sym_blkid_probe_set_device(probe, fdisk_get_devfd(context->fdisk_context), offset, size); + r = sym_blkid_probe_set_device(probe, sym_fdisk_get_devfd(context->fdisk_context), offset, size); if (r < 0) return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to allocate device probe for wiping."); @@ -4595,7 +4689,7 @@ static int context_discard_range( if (size <= 0) return 0; - assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + assert_se((fd = sym_fdisk_get_devfd(context->fdisk_context)) >= 0); if (fstat(fd, &st) < 0) return -errno; @@ -4692,7 +4786,7 @@ static int context_discard_gap_after(Context *context, Partition *p) { * existing partitions may be before that so ensure the gap * starts at the first actually usable lba */ - gap = fdisk_get_first_lba(context->fdisk_context) * context->sector_size; + gap = sym_fdisk_get_first_lba(context->fdisk_context) * context->sector_size; LIST_FOREACH(partitions, q, context->partitions) { if (q->dropped) @@ -4709,7 +4803,7 @@ static int context_discard_gap_after(Context *context, Partition *p) { } if (next == UINT64_MAX) { - next = (fdisk_get_last_lba(context->fdisk_context) + 1) * context->sector_size; + next = (sym_fdisk_get_last_lba(context->fdisk_context) + 1) * context->sector_size; if (gap > next) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition end beyond disk end."); } @@ -4906,7 +5000,7 @@ static int prepare_temporary_file(Context *context, PartitionTarget *t, uint64_t return log_error_errno(fd, "Failed to create temporary file: %m"); if (context->fdisk_context) { - r = read_attr_fd(fdisk_get_devfd(context->fdisk_context), &attrs); + r = read_attr_fd(sym_fdisk_get_devfd(context->fdisk_context), &attrs); if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) log_warning_errno(r, "Failed to read file attributes of %s, ignoring: %m", context->node); @@ -4947,7 +5041,7 @@ static int partition_target_prepare( assert(p); assert(ret); - assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + assert_se((whole_fd = sym_fdisk_get_devfd(context->fdisk_context)) >= 0); t = new(PartitionTarget, 1); if (!t) @@ -5022,7 +5116,7 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget assert(p); assert(t); - assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + assert_se((whole_fd = sym_fdisk_get_devfd(context->fdisk_context)) >= 0); log_info("Syncing future partition %"PRIu64" contents to disk.", p->partno); @@ -5039,9 +5133,6 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget if (lseek(whole_fd, p->offset, SEEK_SET) < 0) return log_error_errno(errno, "Failed to seek to partition offset: %m"); - if (lseek(t->fd, 0, SEEK_SET) < 0) - return log_error_errno(errno, "Failed to seek to start of temporary file: %m"); - if (fstat(t->fd, &st) < 0) return log_error_errno(errno, "Failed to stat temporary file: %m"); @@ -5050,7 +5141,7 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget "Partition %" PRIu64 "'s contents (%s) don't fit in the partition (%s).", p->partno, FORMAT_BYTES(st.st_size), FORMAT_BYTES(p->new_size)); - r = copy_bytes(t->fd, whole_fd, UINT64_MAX, COPY_REFLINK|COPY_HOLES|COPY_FSYNC); + r = copy_bytes(t->fd, whole_fd, UINT64_MAX, COPY_REFLINK|COPY_HOLES|COPY_FSYNC|COPY_SEEK0_SOURCE); if (r < 0) return log_error_errno(r, "Failed to copy bytes to partition: %m"); } else { @@ -5111,9 +5202,9 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta assert(p); assert(p->encrypt != ENCRYPT_OFF); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_ERR); if (r < 0) - return log_error_errno(r, "libcryptsetup not found, cannot encrypt: %m"); + return r; log_info("Encrypting future partition %" PRIu64 "...", p->partno); @@ -5164,7 +5255,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta return log_oom(); } - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; r = sym_crypt_init(&cd, offline ? hp : node); if (r < 0) return log_error_errno(r, "Failed to allocate libcryptsetup context for %s: %m", hp); @@ -5200,6 +5291,46 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta if (r < 0) return log_error_errno(r, "Failed to LUKS2 format future partition: %m"); + /* If an explicit KDF is configured, apply it before adding any keyslots. */ + if (p->encrypt_kdf >= 0) { + if (p->encrypt_kdf == ENCRYPT_KDF_MINIMAL) { + /* Minimal PBKDF2 with sha512, 1000 iterations, no benchmarking — appropriate + * for high-entropy keys where the KDF only satisfies the LUKS2 format requirement + * (e.g. kdump with crashkernel=512MB). */ + r = cryptsetup_set_minimal_pbkdf(cd); + } else { + /* For argon2id or pbkdf2, set the type and let libcryptsetup benchmark + * and determine the parameters. */ + static const struct crypt_pbkdf_type argon2id_pbkdf = { + .type = "argon2id", + }; + static const struct crypt_pbkdf_type pbkdf2_pbkdf = { + .type = "pbkdf2", + }; + + r = sym_crypt_set_pbkdf_type(cd, p->encrypt_kdf == ENCRYPT_KDF_ARGON2ID ? + &argon2id_pbkdf : &pbkdf2_pbkdf); + } + if (r < 0) + return log_error_errno(r, "Failed to set KDF type: %m"); + } + + bool allow_discards = p->integrity != INTEGRITY_INLINE && (arg_discard ? p->discard != 0 : p->discard > 0); + if (allow_discards) { + uint32_t flags; + + r = sym_crypt_persistent_flags_get(cd, CRYPT_FLAGS_ACTIVATION, &flags); + if (r < 0) + return log_error_errno(r, "Failed to get persistent activation flags for %s: %m", node); + + if (!FLAGS_SET(flags, CRYPT_ACTIVATE_ALLOW_DISCARDS)) { + flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS; + r = sym_crypt_persistent_flags_set(cd, CRYPT_FLAGS_ACTIVATION, flags); + if (r < 0) + return log_error_errno(r, "Failed to set persistent activation flags for %s: %m", node); + } + } + if (p->encrypted_volume && p->encrypted_volume->fixate_volume_key) { _cleanup_free_ char *key_id = NULL, *hash_option = NULL; @@ -5518,7 +5649,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta dm_name, NULL, /* volume_key_size= */ volume_key_size, - (arg_discard && p->integrity != INTEGRITY_INLINE ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0) | CRYPT_ACTIVATE_PRIVATE); + (allow_discards ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0) | CRYPT_ACTIVATE_PRIVATE); if (r < 0) return log_error_errno(r, "Failed to activate LUKS superblock: %m"); @@ -5580,7 +5711,7 @@ static int partition_format_verity_hash( #if HAVE_LIBCRYPTSETUP Partition *dp; _cleanup_(partition_target_freep) PartitionTarget *t = NULL; - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_free_ char *hint = NULL; int r; @@ -5595,7 +5726,7 @@ static int partition_format_verity_hash( if (PARTITION_EXISTS(p)) /* Never format existing partitions */ return 0; - /* Minimized partitions will use the copy blocks logic so skip those here. */ + /* Either we are minimizing the partition or we were instructed to copy an existing hash block directly. */ if (p->copy_blocks_fd >= 0) return 0; @@ -5604,9 +5735,9 @@ static int partition_format_verity_hash( (void) partition_hint(p, node, &hint); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_ERR); if (r < 0) - return log_error_errno(r, "libcryptsetup not found, cannot setup verity: %m"); + return r; if (!node) { r = partition_target_prepare(context, p, p->new_size, /* need_path= */ true, &t); @@ -5697,7 +5828,7 @@ static int sign_verity_roothash( _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL; _cleanup_free_ char *hex = NULL; _cleanup_free_ uint8_t *sig = NULL; - int sigsz; + int sigsz, r; assert(context); assert(context->certificate); @@ -5706,23 +5837,27 @@ static int sign_verity_roothash( assert(iovec_is_set(roothash)); assert(ret_signature); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + hex = hexmem(roothash->iov_base, roothash->iov_len); if (!hex) return log_oom(); - rb = BIO_new_mem_buf(hex, -1); + rb = sym_BIO_new_mem_buf(hex, -1); if (!rb) return log_oom(); - p7 = PKCS7_sign(context->certificate, context->private_key, NULL, rb, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY); + p7 = sym_PKCS7_sign(context->certificate, context->private_key, NULL, rb, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY); if (!p7) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to calculate PKCS7 signature: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - sigsz = i2d_PKCS7(p7, &sig); + sigsz = sym_i2d_PKCS7(p7, &sig); if (sigsz < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); *ret_signature = IOVEC_MAKE(TAKE_PTR(sig), sigsz); @@ -5732,21 +5867,41 @@ static int sign_verity_roothash( #endif } -static const VeritySettings *lookup_verity_settings_by_uuid_pair(sd_id128_t data_uuid, sd_id128_t hash_uuid) { - uint8_t root_hash_key[sizeof(sd_id128_t) * 2]; +static int iovec_roothash_from_uuid_pair( + sd_id128_t data_uuid, + sd_id128_t hash_uuid, + struct iovec *ret_roothash) { + + uint8_t roothash_bytes[sizeof(sd_id128_t) * 2]; + + assert(ret_roothash); if (sd_id128_is_null(data_uuid) || sd_id128_is_null(hash_uuid)) - return NULL; + return -EINVAL; /* As per the https://uapi-group.org/specifications/specs/discoverable_partitions_specification/ the * UUIDs of the data and verity partitions are respectively the first and second halves of the * dm-verity roothash, so we can use them to match the signature to the right partition. */ - memcpy(root_hash_key, data_uuid.bytes, sizeof(sd_id128_t)); - memcpy(root_hash_key + sizeof(sd_id128_t), hash_uuid.bytes, sizeof(sd_id128_t)); + memcpy(roothash_bytes, data_uuid.bytes, sizeof(sd_id128_t)); + memcpy(roothash_bytes + sizeof(sd_id128_t), hash_uuid.bytes, sizeof(sd_id128_t)); + + if (!iovec_memdup(&IOVEC_MAKE(roothash_bytes, sizeof(roothash_bytes)), ret_roothash)) + return -ENOMEM; + + return 0; +} + +static const VeritySettings *lookup_verity_settings_by_uuid_pair(sd_id128_t data_uuid, sd_id128_t hash_uuid) { + _cleanup_(iovec_done) struct iovec roothash = {}; + int r; + + r = iovec_roothash_from_uuid_pair(data_uuid, hash_uuid, &roothash); + if (r < 0) + return NULL; VeritySettings key = { - .root_hash = IOVEC_MAKE(root_hash_key, sizeof(root_hash_key)), + .root_hash = roothash, }; return set_get(arg_verity_settings, &key); @@ -5769,11 +5924,23 @@ static int partition_format_verity_sig(Context *context, Partition *p) { if (PARTITION_EXISTS(p)) return 0; + /* We were instructed to copy an existing signature block directly */ + if (p->copy_blocks_fd >= 0) + return 0; + assert_se(hp = p->siblings[VERITY_HASH]); assert(!hp->dropped); assert_se(rp = p->siblings[VERITY_DATA]); assert(!rp->dropped); + /* Currently only set while formatting the hash partition. But if this is skipped via CopyBlocks= + * we just derive the roothash from the UUIDs from the data + hash partition. */ + if (!iovec_is_set(&hp->roothash)) { + r = iovec_roothash_from_uuid_pair(rp->new_uuid, hp->new_uuid, &hp->roothash); + if (r < 0) + return log_error_errno(r, "Unable to derive roothash: %m"); + } + verity_settings = lookup_verity_settings_by_uuid_pair(rp->current_uuid, hp->current_uuid); if (!verity_settings) { @@ -5793,7 +5960,7 @@ static int partition_format_verity_sig(Context *context, Partition *p) { (void) partition_hint(p, context->node, &hint); - assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + assert_se((whole_fd = sym_fdisk_get_devfd(context->fdisk_context)) >= 0); _cleanup_(iovec_done) struct iovec sig_free = {}; const struct iovec *roothash, *sig; @@ -6757,7 +6924,7 @@ static int partition_populate_filesystem(Context *context, Partition *p, const c * appear in the host namespace. Hence we fork a child that has its own file system namespace and * detached mount propagation. */ - (void) dlopen_libmount(); + (void) dlopen_libmount(LOG_DEBUG); r = pidref_safe_fork( "(sd-copy)", @@ -6888,6 +7055,9 @@ static int finalize_extra_mkfs_options(const Partition *p, const char *root, cha if (r < 0) return r; } + + if (p->minimize != MINIMIZE_OFF && strv_extend(&sv, "--shrink") < 0) + return log_oom(); } *ret = TAKE_PTR(sv); @@ -7260,7 +7430,7 @@ static int set_gpt_flags(struct fdisk_partition *q, uint64_t flags) { return r; } - return fdisk_partition_set_attrs(q, strempty(a)); + return sym_fdisk_partition_set_attrs(q, strempty(a)); } static uint64_t partition_merge_flags(Partition *p) { @@ -7333,11 +7503,11 @@ static int context_mangle_partitions(Context *context) { assert(p->new_size >= p->current_size); assert(p->new_size % context->sector_size == 0); - r = fdisk_partition_size_explicit(p->current_partition, true); + r = sym_fdisk_partition_size_explicit(p->current_partition, true); if (r < 0) return log_error_errno(r, "Failed to enable explicit sizing: %m"); - r = fdisk_partition_set_size(p->current_partition, p->new_size / context->sector_size); + r = sym_fdisk_partition_set_size(p->current_partition, p->new_size / context->sector_size); if (r < 0) return log_error_errno(r, "Failed to grow partition: %m"); @@ -7346,7 +7516,7 @@ static int context_mangle_partitions(Context *context) { } if (!sd_id128_equal(p->new_uuid, p->current_uuid)) { - r = fdisk_partition_set_uuid(p->current_partition, SD_ID128_TO_UUID_STRING(p->new_uuid)); + r = sym_fdisk_partition_set_uuid(p->current_partition, SD_ID128_TO_UUID_STRING(p->new_uuid)); if (r < 0) return log_error_errno(r, "Failed to set partition UUID: %m"); @@ -7355,7 +7525,7 @@ static int context_mangle_partitions(Context *context) { } if (!streq_ptr(p->new_label, p->current_label)) { - r = fdisk_partition_set_name(p->current_partition, strempty(p->new_label)); + r = sym_fdisk_partition_set_name(p->current_partition, strempty(p->new_label)); if (r < 0) return log_error_errno(r, "Failed to set partition label: %m"); @@ -7366,7 +7536,7 @@ static int context_mangle_partitions(Context *context) { if (changed) { assert(!PARTITION_IS_FOREIGN(p)); /* never touch foreign partitions */ - r = fdisk_set_partition(context->fdisk_context, p->partno, p->current_partition); + r = sym_fdisk_set_partition(context->fdisk_context, p->partno, p->current_partition); if (r < 0) return log_error_errno(r, "Failed to update partition: %m"); } @@ -7379,43 +7549,43 @@ static int context_mangle_partitions(Context *context) { assert(p->new_size % context->sector_size == 0); assert(p->new_label); - t = fdisk_new_parttype(); + t = sym_fdisk_new_parttype(); if (!t) return log_oom(); - r = fdisk_parttype_set_typestr(t, SD_ID128_TO_UUID_STRING(p->type.uuid)); + r = sym_fdisk_parttype_set_typestr(t, SD_ID128_TO_UUID_STRING(p->type.uuid)); if (r < 0) return log_error_errno(r, "Failed to initialize partition type: %m"); - q = fdisk_new_partition(); + q = sym_fdisk_new_partition(); if (!q) return log_oom(); - r = fdisk_partition_set_type(q, t); + r = sym_fdisk_partition_set_type(q, t); if (r < 0) return log_error_errno(r, "Failed to set partition type: %m"); - r = fdisk_partition_size_explicit(q, true); + r = sym_fdisk_partition_size_explicit(q, true); if (r < 0) return log_error_errno(r, "Failed to enable explicit sizing: %m"); - r = fdisk_partition_set_start(q, p->offset / context->sector_size); + r = sym_fdisk_partition_set_start(q, p->offset / context->sector_size); if (r < 0) return log_error_errno(r, "Failed to position partition: %m"); - r = fdisk_partition_set_size(q, p->new_size / context->sector_size); + r = sym_fdisk_partition_set_size(q, p->new_size / context->sector_size); if (r < 0) return log_error_errno(r, "Failed to grow partition: %m"); - r = fdisk_partition_set_partno(q, p->partno); + r = sym_fdisk_partition_set_partno(q, p->partno); if (r < 0) return log_error_errno(r, "Failed to set partition number: %m"); - r = fdisk_partition_set_uuid(q, SD_ID128_TO_UUID_STRING(p->new_uuid)); + r = sym_fdisk_partition_set_uuid(q, SD_ID128_TO_UUID_STRING(p->new_uuid)); if (r < 0) return log_error_errno(r, "Failed to set partition UUID: %m"); - r = fdisk_partition_set_name(q, strempty(p->new_label)); + r = sym_fdisk_partition_set_name(q, strempty(p->new_label)); if (r < 0) return log_error_errno(r, "Failed to set partition label: %m"); @@ -7426,7 +7596,7 @@ static int context_mangle_partitions(Context *context) { log_info("Adding new partition %" PRIu64 " to partition table.", p->partno); - r = fdisk_add_partition(context->fdisk_context, q, NULL); + r = sym_fdisk_add_partition(context->fdisk_context, q, NULL); if (r < 0) return log_error_errno(r, "Failed to add partition: %m"); @@ -7571,7 +7741,7 @@ static int context_split(Context *context) { continue; if (fd < 0) { - assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + assert_se((fd = sym_fdisk_get_devfd(context->fdisk_context)) >= 0); r = read_attr_fd(fd, &attrs); if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) @@ -7590,14 +7760,376 @@ static int context_split(Context *context) { if (lseek(fd, p->offset, SEEK_SET) < 0) return log_error_errno(errno, "Failed to seek to partition offset: %m"); - r = copy_bytes(fd, fdt, p->new_size, COPY_REFLINK|COPY_HOLES|COPY_TRUNCATE); - if (r < 0) - return log_error_errno(r, "Failed to copy to split partition %s: %m", p->split_path); + /* Verity signature partitions contain a JSON object NUL-padded out to the partition + * size. The on-disk partition must keep the padding, but the split-out file is a + * standalone artifact, so trim the trailing NUL bytes there to avoid tripping jq. */ + if (partition_designator_is_verity_sig(p->type.designator)) { + _cleanup_free_ char *buf = malloc(p->new_size); + if (!buf) + return log_oom(); + + r = loop_read_exact(fd, buf, p->new_size, /* do_poll= */ false); + if (r < 0) + return log_error_errno(r, "Failed to read verity signature partition: %m"); + + size_t len = strnlen(buf, p->new_size); + if (len == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Verity signature partition is empty"); + + r = loop_write(fdt, buf, len); + if (r < 0) + return log_error_errno(r, "Failed to write to split partition %s: %m", p->split_path); + } else { + r = copy_bytes(fd, fdt, p->new_size, COPY_REFLINK|COPY_HOLES|COPY_TRUNCATE); + if (r < 0) + return log_error_errno(r, "Failed to copy to split partition %s: %m", p->split_path); + } } return 0; } +static int write_primary_descriptor( + int fd, + uint32_t root_sector, + usec_t usec, + bool utc, + const char *system_id, + const char *volume_id, + const char *publisher_id) { + int r; + + assert(fd >= 0); + + struct iso9660_primary_volume_descriptor desc = { + .header = { + .type = 1, + .version = 1, + }, + .volume_space_size_little = htole32(ISO9660_START + ISO9660_SIZE), + .volume_space_size_big = htobe32(ISO9660_START + ISO9660_SIZE), + .volume_set_size_little = htole16(1), + .volume_set_size_big = htobe16(1), + .volume_sequence_number_little = htole16(1), + .volume_sequence_number_big = htobe16(1), + .logical_block_size_little = htole16(ISO9660_BLOCK_SIZE), + .logical_block_size_big = htobe16(ISO9660_BLOCK_SIZE), + .file_structure_version = 1, + .root_directory_entry = { + .len = sizeof(struct iso9660_directory_entry), + .extent_loc_little = htole32(root_sector), + .extent_loc_big = htobe32(root_sector), + .data_len_little = htole32(2*sizeof(struct iso9660_directory_entry)), /* 2 entries with ident size 1: . and .. */ + .data_len_big = htobe32(2*sizeof(struct iso9660_directory_entry)), /* 2 entries with ident size 1: . and .. */ + .flags = 2, /* directory */ + .volume_seq_num_little = htole16(1), + .volume_seq_num_big = htobe16(1), + .ident_len = 1, + .ident[0] = 0, /* special value for root */ + } + }; + + iso9660_set_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); + + r = iso9660_dir_datetime_from_usec(usec, utc, &desc.root_directory_entry.time); + if (r < 0) + return r; + + r = iso9660_set_string(desc.system_identifier, sizeof(desc.system_identifier), system_id, /* allow_a_chars= */ true); + if (r < 0) + return r; + + /* In theory the volume identifier should be d-chars, but in practice, a-chars are allowed */ + r = iso9660_set_string(desc.volume_identifier, sizeof(desc.volume_identifier), volume_id, /* allow_a_chars= */ true); + if (r < 0) + return r; + + iso9660_set_const_string(desc.volume_set_identifier, sizeof(desc.volume_set_identifier), NULL, /* allow_a_chars= */ false); + + r = iso9660_set_string(desc.publisher_identifier, sizeof(desc.publisher_identifier), publisher_id, /* allow_a_chars= */ true); + if (r < 0) + return r; + + iso9660_set_const_string(desc.data_preparer_identifier, sizeof(desc.data_preparer_identifier), NULL, /* allow_a_chars= */ true); + iso9660_set_const_string(desc.application_identifier, sizeof(desc.application_identifier), "SYSTEMD-REPART", /* allow_a_chars= */ true); + iso9660_set_const_string(desc.copyright_file_identifier, sizeof(desc.copyright_file_identifier), NULL, /* allow_a_chars= */ false); + iso9660_set_const_string(desc.abstract_file_identifier, sizeof(desc.abstract_file_identifier), NULL, /* allow_a_chars= */ false); + iso9660_set_const_string(desc.bibliographic_file_identifier, sizeof(desc.bibliographic_file_identifier), NULL, /* allow_a_chars= */ false); + + r = iso9660_datetime_from_usec(usec, utc, &desc.volume_creation_date); + if (r < 0) + return r; + + r = iso9660_datetime_from_usec(usec, utc, &desc.volume_modification_date); + if (r < 0) + return r; + + iso9660_datetime_zero(&desc.volume_expiration_date); + iso9660_datetime_zero(&desc.volume_effective_date); + + ssize_t s = pwrite(fd, &desc, sizeof(desc), ISO9660_PRIMARY_DESCRIPTOR*ISO9660_BLOCK_SIZE); + if (s < 0) + return log_error_errno(errno, "Failed to write ISO9660 primary descriptor: %m"); + if (s != sizeof(desc)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to fully write ISO9660 primary descriptor"); + + return 0; +} + +static int write_eltorito_descriptor(int fd, uint32_t catalog_sector) { + assert(fd >= 0); + + struct iso9660_eltorito_descriptor desc = { + .header = { + .type = 0, + .version = 1, + }, + .boot_catalog_sector = htole32(catalog_sector), + }; + + iso9660_set_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); + + strncpy(desc.boot_system_identifier, "EL TORITO SPECIFICATION", sizeof(desc.boot_system_identifier)); + + ssize_t s = pwrite(fd, &desc, sizeof(desc), ISO9660_ELTORITO_DESCRIPTOR*ISO9660_BLOCK_SIZE); + if (s < 0) + return log_error_errno(errno, "Failed to write ISO9660 El-Torito descriptor: %m"); + if (s != sizeof(desc)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to fully write ISO9660 El-Torito descriptor"); + + return 0; +} + +static int write_terminal_descriptor(int fd) { + assert(fd >= 0); + + struct iso9660_terminal_descriptor desc = { + .header = { + .type = 255, + .version = 1, + }, + }; + + iso9660_set_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); + + ssize_t s = pwrite(fd, &desc, sizeof(desc), ISO9660_TERMINAL_DESCRIPTOR*ISO9660_BLOCK_SIZE); + if (s < 0) + return log_error_errno(errno, "Failed to write ISO9660 terminal descriptor: %m"); + if (s != sizeof(desc)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to fully write ISO9660 terminal descriptor"); + + return 0; +} + +static uint16_t calculate_validation_entry_checksum(const void *p, size_t size) { + assert(p || size == 0); + assert(size % 2 == 0); + + uint16_t checksum = 0; + + for (size_t i = 0; i < (size/2); i++) + checksum -= le16toh(((const le16_t*)p)[i]); + + return checksum; +} + +static int write_boot_catalog(int fd, uint32_t load_block) { + assert(fd >= 0); + + struct el_torito_validation_entry ve = { + .header_indicator = 1, + .platform = 0xef, /* EFI */ + .key_bytes = {0x55, 0xaa}, + }; + + ve.checksum = htole16(calculate_validation_entry_checksum(&ve, sizeof(ve))); + + struct el_torito_initial_entry ie = { + .boot_indicator = 0x88, /* bootable */ + .boot_media_type = 0, /* no emul */ + /* From UEFI specification: + * > If the value of Sector Count is set to 0 or 1, EFI will assume the system partition + * > consumes the space from the beginning of the “no emulation” image to the end of the + * > CD-ROM. + */ + .sector_count = htole16(0), + .load_rba = htole32(load_block), + + }; + + struct el_torito_section_header sh = { + .header_indicator = 0x91, /* final header */ + .nentries = htole16(0), /* no more entries */ + }; + + uint8_t sector[ISO9660_BLOCK_SIZE] = {}; + uint8_t *p = sector; + p = mempcpy(p, &ve, sizeof(ve)); + p = mempcpy(p, &ie, sizeof(ie)); + p = mempcpy(p, &sh, sizeof(sh)); + assert((size_t) (p - sector) <= sizeof(sector)); + + ssize_t s = pwrite(fd, §or, sizeof(sector), ISO9660_BOOT_CATALOG*ISO9660_BLOCK_SIZE); + if (s < 0) + return log_error_errno(errno, "Failed to write El-Torito boot catalog: %m"); + if (s != sizeof(sector)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to fully write El-Torito boot catalog"); + + return 0; +} + +static int write_directories( + int fd, + usec_t usec, + bool utc, + uint32_t root_sector) { + + int r; + + assert(fd >= 0); + + uint32_t dir_size = 2*sizeof(struct iso9660_directory_entry); /* 2 entries with ident size 1: . and .. */ + + struct iso9660_directory_entry self = { + .len = sizeof(struct iso9660_directory_entry), + .extent_loc_little = htole32(root_sector), + .extent_loc_big = htobe32(root_sector), + .data_len_little = htole32(dir_size), + .data_len_big = htobe32(dir_size), + .flags = 2, /* directory */ + .volume_seq_num_little = htole16(1), + .volume_seq_num_big = htobe16(1), + .ident_len = 1, + .ident[0] = 0, /* special value for self */ + }; + + r = iso9660_dir_datetime_from_usec(usec, utc, &self.time); + if (r < 0) + return r; + + struct iso9660_directory_entry parent = { + .len = sizeof(struct iso9660_directory_entry), + .extent_loc_little = htole32(root_sector), + .extent_loc_big = htobe32(root_sector), + .data_len_little = htole32(dir_size), + .data_len_big = htobe32(dir_size), + .flags = 2, /* directory */ + .volume_seq_num_little = htole16(1), + .volume_seq_num_big = htobe16(1), + .ident_len = 1, + .ident[0] = 1, /* special value for parent */ + }; + + // TODO: we should probably add some text file explaining there is no content through ISO9660 + + r = iso9660_dir_datetime_from_usec(usec, utc, &parent.time); + if (r < 0) + return r; + + uint8_t sector[ISO9660_BLOCK_SIZE] = {}; + uint8_t *p = sector; + p = mempcpy(p, &self, sizeof(self)); + p = mempcpy(p, &parent, sizeof(parent)); + assert((size_t) (p - sector) <= sizeof(sector)); + + ssize_t s = pwrite(fd, §or, sizeof(sector), ISO9660_ROOT_DIRECTORY*ISO9660_BLOCK_SIZE); + if (s < 0) + return log_error_errno(errno, "Failed to write ISO9660 root directory: %m"); + if (s != sizeof(sector)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to fully write ISO9660 root directory"); + + return 0; +} + +static int write_eltorito( + int fd, + usec_t usec, + bool utc, + uint32_t load_block, /* in iso9660 blocks */ + const char *system_id, + const char *volume_id, + const char *publisher_id) { + + int r; + + assert(fd >= 0); + + r = write_primary_descriptor(fd, ISO9660_ROOT_DIRECTORY, usec, utc, system_id, volume_id, publisher_id); + if (r < 0) + return r; + + r = write_eltorito_descriptor(fd, ISO9660_BOOT_CATALOG); + if (r < 0) + return r; + + r = write_terminal_descriptor(fd); + if (r < 0) + return r; + + r = write_boot_catalog(fd, load_block); + if (r < 0) + return r; + + r = write_directories(fd, usec, utc, ISO9660_ROOT_DIRECTORY); + if (r < 0) + return r; + + return 0; +} + +static int context_verify_eltorito_overlap(Context *context) { + /* Check if the partition table collides with the ISO9660 El Torito area. */ + assert(context); + + if (!arg_eltorito) + return 0; + + /* Check how many GPT partition entries can be stored. */ + size_t nents = sym_fdisk_get_npartitions(context->fdisk_context); + /* The GPT contains + * - 1 unused block (protective MBR) + * - GPT header + * - N entries of 128 bytes each. + */ + size_t first_free_offset = 2*context->sector_size + round_up_size(nents*128, context->sector_size); + + if (first_free_offset > ISO9660_START*ISO9660_BLOCK_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The partition table is overlapping with the El Torito boot catalog."); + + /* The first lba is the first block where a partition could exist. Even if there is no partition + * there, we should still not overlap with it since a partition could be added later. + * It is unexpected for tools to change the first lba in the GPT header. So this should be safe. + */ + if (sym_fdisk_get_first_lba(context->fdisk_context) * context->sector_size < (ISO9660_START+ISO9660_SIZE)*ISO9660_BLOCK_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "El Torito is overlapping with the first partition block."); + + return 0; +} + +static int context_find_esp_offset(Context *context, uint64_t *ret) { + assert(ret); + + uint64_t esp_offset = UINT64_MAX; + LIST_FOREACH(partitions, p, context->partitions) { + if (p->dropped || PARTITION_IS_FOREIGN(p)) + continue; + if (p->type.designator == PARTITION_ESP) { + esp_offset = p->offset; + break; + } + } + + if (esp_offset == UINT64_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "El Torito boot catalog requires an ESP."); + if (esp_offset / ISO9660_BLOCK_SIZE > UINT32_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP offset is farther than El Torito boot catalog can support."); + if (esp_offset % ISO9660_BLOCK_SIZE != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP offset not aligned on 2K blocks."); + + *ret = esp_offset; + return 0; +} + static int context_write_partition_table(Context *context) { _cleanup_(fdisk_unref_tablep) struct fdisk_table *original_table = NULL; int capable, r; @@ -7638,7 +8170,7 @@ static int context_write_partition_table(Context *context) { } } - r = fdisk_get_partitions(context->fdisk_context, &original_table); + r = sym_fdisk_get_partitions(context->fdisk_context, &original_table); if (r < 0) return log_error_errno(r, "Failed to acquire partition table: %m"); @@ -7664,11 +8196,11 @@ static int context_write_partition_table(Context *context) { (void) context_notify(context, PROGRESS_WRITING_TABLE, /* object= */ NULL, UINT_MAX); - r = fdisk_write_disklabel(context->fdisk_context); + r = sym_fdisk_write_disklabel(context->fdisk_context); if (r < 0) return log_error_errno(r, "Failed to write partition table: %m"); - capable = blockdev_partscan_enabled_fd(fdisk_get_devfd(context->fdisk_context)); + capable = blockdev_partscan_enabled_fd(sym_fdisk_get_devfd(context->fdisk_context)); if (capable == -ENOTBLK) log_debug("Not telling kernel to reread partition table, since we are not operating on a block device."); else if (capable < 0) @@ -7677,13 +8209,52 @@ static int context_write_partition_table(Context *context) { log_info("Informing kernel about changed partitions..."); (void) context_notify(context, PROGRESS_REREADING_TABLE, /* object= */ NULL, UINT_MAX); - r = reread_partition_table_fd(fdisk_get_devfd(context->fdisk_context), /* flags= */ 0); + r = reread_partition_table_fd(sym_fdisk_get_devfd(context->fdisk_context), /* flags= */ 0); if (r < 0) return log_error_errno(r, "Failed to reread partition table: %m"); } else log_notice("Not telling kernel to reread partition table, because selected image does not support kernel partition block devices."); - log_info("All done."); + log_info("Partition table written."); + + return 0; +} + +static int context_write_eltorito(Context *context) { + int r; + + assert(context); + + if (!arg_eltorito) + return 0; + + if (context->dry_run) + return 0; + + bool utc = true; + usec_t usec = parse_source_date_epoch(); + if (usec == USEC_INFINITY) { + usec = now(CLOCK_REALTIME); + utc = false; + } + + uint64_t esp_offset; + r = context_find_esp_offset(context, &esp_offset); + if (r < 0) + return r; + + log_info("Writing El Torito boot catalog."); + + r = write_eltorito( + sym_fdisk_get_devfd(context->fdisk_context), + usec, + utc, + esp_offset / ISO9660_BLOCK_SIZE, + arg_eltorito_system, + arg_eltorito_volume, + arg_eltorito_publisher); + if (r < 0) + return log_error_errno(r, "Failed to write El Torito boot catalog: %m"); return 0; } @@ -7742,7 +8313,7 @@ static int context_factory_reset(Context *context) { log_info("Removing partition %" PRIu64 " for factory reset.", p->partno); - r = fdisk_delete_partition(context->fdisk_context, p->partno); + r = sym_fdisk_delete_partition(context->fdisk_context, p->partno); if (r < 0) return log_error_errno(r, "Failed to remove partition %" PRIu64 ": %m", p->partno); @@ -7754,7 +8325,7 @@ static int context_factory_reset(Context *context) { return 0; } - r = fdisk_write_disklabel(context->fdisk_context); + r = sym_fdisk_write_disklabel(context->fdisk_context); if (r < 0) return log_error_errno(r, "Failed to write disk label: %m"); @@ -7814,9 +8385,9 @@ static int resolve_copy_blocks_auto_candidate( return log_error_errno(r, "Failed to open block device " DEVNUM_FORMAT_STR ": %m", DEVNUM_FORMAT_VAL(whole_devno)); - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_ERR); if (r < 0) - return log_error_errno(r, "Failed to find libblkid: %m"); + return r; b = sym_blkid_new_probe(); if (!b) @@ -8646,6 +9217,8 @@ static int context_minimize(Context *context) { if (!p->format) continue; + bool is_btrfs = streq(p->format, "btrfs"); + if (p->copy_blocks_fd >= 0) continue; @@ -8661,7 +9234,7 @@ static int context_minimize(Context *context) { (void) partition_hint(p, context->node, &hint); - log_info("Pre-populating %s filesystem of partition %s twice to calculate minimal partition size", + log_info("Pre-populating %s filesystem of partition %s to calculate minimal partition size", p->format, strna(hint)); if (!vt) { @@ -8681,9 +9254,11 @@ static int context_minimize(Context *context) { attrs & FS_NOCOW_FL ? XO_NOCOW : 0, 0600); if (fd < 0) - return log_error_errno(errno, "Failed to open temporary file %s: %m", temp); + return log_error_errno(fd, "Failed to open temporary file %s: %m", temp); - if (fstype_is_ro(p->format)) + if (fstype_is_ro(p->format) || is_btrfs) + /* Read-only filesystems and btrfs (with mkfs.btrfs --shrink) produce a minimal + * filesystem in one pass, so we can use the real UUID directly. */ fs_uuid = p->fs_uuid; else { /* This may seem huge but it will be created sparse so it doesn't take up any space @@ -8705,7 +9280,7 @@ static int context_minimize(Context *context) { return r; } - if (!d || fstype_is_ro(p->format) || (streq_ptr(p->format, "btrfs") && p->compression)) { + if (!d || fstype_is_ro(p->format)) { if (!mkfs_supports_root_option(p->format)) return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "Loop device access is required to populate %s filesystems.", @@ -8735,8 +9310,9 @@ static int context_minimize(Context *context) { return r; /* Read-only filesystems are minimal from the first try because they create and size the - * loopback file for us. */ - if (fstype_is_ro(p->format)) { + * loopback file for us. Similarly, mkfs.btrfs --shrink populates the filesystem from the + * root directory and then shrinks the backing file to the minimal size. */ + if (fstype_is_ro(p->format) || is_btrfs) { fd = safe_close(fd); fd = open(temp, O_RDONLY|O_CLOEXEC|O_NONBLOCK); @@ -8771,10 +9347,8 @@ static int context_minimize(Context *context) { /* Other filesystems need to be provided with a pre-sized loopback file and will adapt to * fully occupy it. Because we gave the filesystem a 1T sparse file, we need to shrink the - * filesystem down to a reasonable size again to fit it in the disk image. While there are - * some filesystems that support shrinking, it doesn't always work properly (e.g. shrinking - * btrfs gives us a 2.0G filesystem regardless of what we put in it). Instead, let's populate - * the filesystem again, but this time, instead of providing the filesystem with a 1T sparse + * filesystem down to a reasonable size again to fit it in the disk image. Let's populate the + * filesystem again, but this time, instead of providing the filesystem with a 1T sparse * loopback file, let's size the loopback file based on the actual data used by the * filesystem in the sparse file after the first attempt. This should be a good guess of the * minimal amount of space needed in the filesystem to fit all the required data. @@ -8859,6 +9433,9 @@ static int context_minimize(Context *context) { if (PARTITION_EXISTS(p)) /* Never format existing partitions */ continue; + if (p->copy_blocks_fd >= 0) + continue; + if (p->minimize == MINIMIZE_OFF) continue; @@ -8891,7 +9468,7 @@ static int context_minimize(Context *context) { attrs & FS_NOCOW_FL ? XO_NOCOW : 0, 0600); if (fd < 0) - return log_error_errno(errno, "Failed to open temporary file %s: %m", temp); + return log_error_errno(fd, "Failed to open temporary file %s: %m", temp); r = partition_format_verity_hash(context, p, temp, dp->copy_blocks_path); if (r < 0) @@ -9056,405 +9633,335 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [DEVICE]\n" - "\n%5$sGrow and add partitions to a partition table, and generate disk images (DDIs).%6$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - "\n%3$sOperation:%4$s\n" - " --dry-run=BOOL Whether to run dry-run operation\n" - " --empty=MODE One of refuse, allow, require, force, create; controls\n" - " how to handle empty disks lacking partition tables\n" - " --offline=BOOL Whether to build the image offline\n" - " --discard=BOOL Whether to discard backing blocks for new partitions\n" - " --sector-size=SIZE Set the logical sector size for the image\n" - " --architecture=ARCH Set the generic architecture for the image\n" - " --size=BYTES Grow loopback file to specified size\n" - " --seed=UUID 128-bit seed UUID to derive all UUIDs from\n" - " --split=BOOL Whether to generate split artifacts\n" - "\n%3$sOutput:%4$s\n" - " --pretty=BOOL Whether to show pretty summary before doing changes\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - "\n%3$sFactory Reset:%4$s\n" - " --factory-reset=BOOL Whether to remove data partitions before recreating\n" - " them\n" - " --can-factory-reset Test whether factory reset is defined\n" - "\n%3$sConfiguration & Image Control:%4$s\n" - " --root=PATH Operate relative to root path\n" - " --image=PATH Operate relative to image file\n" - " --image-policy=POLICY\n" - " Specify disk image dissection policy\n" - " --definitions=DIR Find partition definitions in specified directory\n" - " --list-devices List candidate block devices to operate on\n" - "\n%3$sVerity:%4$s\n" - " --private-key=PATH|URI\n" - " Private key to use when generating verity roothash\n" - " signatures, or an engine or provider specific\n" - " designation if --private-key-source= is used\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used when generating\n" - " verity roothash signatures\n" - " --certificate=PATH|URI\n" - " PEM certificate to use when generating verity roothash\n" - " signatures, or a provider specific designation if\n" - " --certificate-source= is used\n" - " --certificate-source=file|provider:PROVIDER\n" - " Specify how to interpret the certificate from\n" - " --certificate=. Allows the certificate to be loaded\n" - " from an OpenSSL provider\n" - " --join-signature=HASH:SIG\n" - " Specify root hash and pkcs7 signature of root hash for\n" - " verity as a tuple of hex encoded hash and a DER\n" - " encoded PKCS7, either as a path to a file or as an\n" - " ASCII base64 encoded string prefixed by 'base64:'\n" - "\n%3$sEncryption:%4$s\n" - " --key-file=PATH Key to use when encrypting partitions\n" - " --tpm2-device=PATH Path to TPM2 device node to use\n" - " --tpm2-device-key=PATH\n" - " Enroll a TPM2 device using its public key\n" - " --tpm2-seal-key-handle=HANDLE\n" - " Specify handle of key to use for sealing\n" - " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n" - " TPM2 PCR indexes to use for TPM2 enrollment\n" - " --tpm2-public-key=PATH\n" - " Enroll signed TPM2 PCR policy against PEM public key\n" - " --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n" - " Enroll signed TPM2 PCR policy for specified TPM2 PCRs\n" - " --tpm2-pcrlock=PATH\n" - " Specify pcrlock policy to lock against\n" - "\n%3$sPartition Control:%4$s\n" - " --include-partitions=PARTITION1,PARTITION2,PARTITION3,…\n" - " Ignore partitions not of the specified types\n" - " --exclude-partitions=PARTITION1,PARTITION2,PARTITION3,…\n" - " Ignore partitions of the specified types\n" - " --defer-partitions=PARTITION1,PARTITION2,PARTITION3,…\n" - " Take partitions of the specified types into account\n" - " but don't populate them yet\n" - " --defer-partitions-empty=yes\n" - " Defer all partitions marked for formatting as empty\n" - " --defer-partitions-factory-reset=yes\n" - " Defer all partitions marked for factory reset\n" - "\n%3$sCopying:%4$s\n" - " -s --copy-source=PATH Specify the primary source tree to copy files from\n" - " --copy-from=IMAGE Copy partitions from the given image(s)\n" - "\n%3$sDDI Profile:%4$s\n" - " -S --make-ddi=sysext Make a system extension DDI\n" - " -C --make-ddi=confext Make a configuration extension DDI\n" - " -P --make-ddi=portable Make a portable service DDI\n" - "\n%3$sAuxiliary Resource Generation:%4$s\n" - " --append-fstab=MODE One of no, auto, replace; controls how to join the\n" - " content of a pre-existing fstab with the generated one\n" - " --generate-fstab=PATH\n" - " Write fstab configuration to the given path\n" - " --generate-crypttab=PATH\n" - " Write crypttab configuration to the given path\n" - "\nSee the %2$s for details.\n", + static const char *const option_groups[] = { + "Options", + "Operation", + "Output", + "Factory Reset", + "Configuration & Image Control", + "Verity", + "Encryption", + "Partition Control", + "Copying", + "DDI Profile", + "Auxiliary Resource Generation", + "El Torito boot catalog", + }; + + _cleanup_(table_unref_many) Table *option_tables[ELEMENTSOF(option_groups) + 1] = {}; + + for (size_t i = 0; i < ELEMENTSOF(option_groups); i++) { + r = option_parser_get_help_table_group(option_groups[i], &option_tables[i]); + if (r < 0) + return r; + } + + (void) table_sync_column_widths(0, + option_tables[0], option_tables[1], option_tables[2], + option_tables[3], option_tables[4], option_tables[5], + option_tables[6], option_tables[7], option_tables[8], + option_tables[9], option_tables[10], option_tables[11]); + + printf("%s [OPTIONS...] [DEVICE]\n" + "\n%sGrow and add partitions to a partition table, and generate disk images (DDIs).%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + for (size_t i = 0; i < ELEMENTSOF(option_groups); i++) { + printf("\n%s%s:%s\n", ansi_underline(), option_groups[i], ansi_normal()); + + r = table_print_or_warn(option_tables[i]); + if (r < 0) + return r; + } + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_DRY_RUN, - ARG_EMPTY, - ARG_DISCARD, - ARG_FACTORY_RESET, - ARG_CAN_FACTORY_RESET, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_SEED, - ARG_PRETTY, - ARG_DEFINITIONS, - ARG_SIZE, - ARG_JSON, - ARG_KEY_FILE, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - ARG_CERTIFICATE, - ARG_CERTIFICATE_SOURCE, - ARG_TPM2_DEVICE, - ARG_TPM2_DEVICE_KEY, - ARG_TPM2_SEAL_KEY_HANDLE, - ARG_TPM2_PCRS, - ARG_TPM2_PUBLIC_KEY, - ARG_TPM2_PUBLIC_KEY_PCRS, - ARG_TPM2_PCRLOCK, - ARG_SPLIT, - ARG_INCLUDE_PARTITIONS, - ARG_EXCLUDE_PARTITIONS, - ARG_DEFER_PARTITIONS, - ARG_DEFER_PARTITIONS_EMPTY, - ARG_DEFER_PARTITIONS_FACTORY_RESET, - ARG_SECTOR_SIZE, - ARG_SKIP_PARTITIONS, - ARG_ARCHITECTURE, - ARG_OFFLINE, - ARG_COPY_FROM, - ARG_MAKE_DDI, - ARG_APPEND_FSTAB, - ARG_GENERATE_FSTAB, - ARG_GENERATE_CRYPTTAB, - ARG_LIST_DEVICES, - ARG_JOIN_SIGNATURE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "dry-run", required_argument, NULL, ARG_DRY_RUN }, - { "empty", required_argument, NULL, ARG_EMPTY }, - { "discard", required_argument, NULL, ARG_DISCARD }, - { "factory-reset", required_argument, NULL, ARG_FACTORY_RESET }, - { "can-factory-reset", no_argument, NULL, ARG_CAN_FACTORY_RESET }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "seed", required_argument, NULL, ARG_SEED }, - { "pretty", required_argument, NULL, ARG_PRETTY }, - { "definitions", required_argument, NULL, ARG_DEFINITIONS }, - { "size", required_argument, NULL, ARG_SIZE }, - { "json", required_argument, NULL, ARG_JSON }, - { "key-file", required_argument, NULL, ARG_KEY_FILE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "tpm2-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY }, - { "tpm2-seal-key-handle", required_argument, NULL, ARG_TPM2_SEAL_KEY_HANDLE }, - { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, - { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY }, - { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS }, - { "tpm2-pcrlock", required_argument, NULL, ARG_TPM2_PCRLOCK }, - { "split", required_argument, NULL, ARG_SPLIT }, - { "include-partitions", required_argument, NULL, ARG_INCLUDE_PARTITIONS }, - { "exclude-partitions", required_argument, NULL, ARG_EXCLUDE_PARTITIONS }, - { "defer-partitions", required_argument, NULL, ARG_DEFER_PARTITIONS }, - { "defer-partitions-empty", required_argument, NULL, ARG_DEFER_PARTITIONS_EMPTY }, - { "defer-partitions-factory-reset", required_argument, NULL, ARG_DEFER_PARTITIONS_FACTORY_RESET }, - { "sector-size", required_argument, NULL, ARG_SECTOR_SIZE }, - { "architecture", required_argument, NULL, ARG_ARCHITECTURE }, - { "offline", required_argument, NULL, ARG_OFFLINE }, - { "copy-from", required_argument, NULL, ARG_COPY_FROM }, - { "copy-source", required_argument, NULL, 's' }, - { "make-ddi", required_argument, NULL, ARG_MAKE_DDI }, - { "append-fstab", required_argument, NULL, ARG_APPEND_FSTAB }, - { "generate-fstab", required_argument, NULL, ARG_GENERATE_FSTAB }, - { "generate-crypttab", required_argument, NULL, ARG_GENERATE_CRYPTTAB }, - { "list-devices", no_argument, NULL, ARG_LIST_DEVICES }, - { "join-signature", required_argument, NULL, ARG_JOIN_SIGNATURE }, - {} - }; - - bool auto_public_key_pcr_mask = true, auto_pcrlock = true; - int c, r; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hs:SCP", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + bool auto_public_key_pcr_mask = true, auto_pcrlock = true; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_GROUP("Options"): {} + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case ARG_DRY_RUN: - r = parse_boolean_argument("--dry-run=", optarg, &arg_dry_run); + OPTION_GROUP("Operation"): {} + + OPTION_LONG("dry-run", "BOOL", + "Whether to run dry-run operation"): + r = parse_boolean_argument("--dry-run=", arg, &arg_dry_run); if (r < 0) return r; break; - case ARG_EMPTY: - if (isempty(optarg)) { + OPTION_LONG("empty", "MODE", + "How to handle empty disks lacking partition tables (refuse, allow, require, force, create)"): + if (isempty(arg)) { arg_empty = EMPTY_UNSET; break; } - arg_empty = empty_mode_from_string(optarg); + arg_empty = empty_mode_from_string(arg); if (arg_empty < 0) - return log_error_errno(arg_empty, "Failed to parse --empty= parameter: %s", optarg); + return log_error_errno(arg_empty, "Failed to parse --empty= parameter: %s", arg); break; - case ARG_DISCARD: - r = parse_boolean_argument("--discard=", optarg, &arg_discard); + OPTION_LONG("offline", "BOOL", + "Whether to build the image offline"): + r = parse_tristate_argument_with_auto("--offline=", arg, &arg_offline); if (r < 0) return r; break; - case ARG_FACTORY_RESET: - r = parse_boolean_argument("--factory-reset=", optarg, NULL); + OPTION_LONG("discard", "BOOL", + "Whether to discard backing blocks for new partitions"): + r = parse_boolean_argument("--discard=", arg, &arg_discard); if (r < 0) return r; - arg_factory_reset = r; break; - case ARG_CAN_FACTORY_RESET: - arg_can_factory_reset = true; - break; - - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root); + OPTION_LONG("sector-size", "SIZE", + "Set the logical sector size for the image"): + r = parse_sector_size(arg, &arg_sector_size); if (r < 0) return r; + break; - case ARG_IMAGE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + OPTION_LONG("grain-size", "BYTES", + "Set the grain size for partition alignment"): + r = parse_size(arg, 1024, &arg_grain_size); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse --grain-size= parameter: %s", arg); + if (arg_grain_size < 512 || !ISPOWEROF2(arg_grain_size)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Grain size must be a power of 2 >= 512."); - arg_relax_copy_block_security = false; + break; + OPTION_LONG("architecture", "ARCH", + "Set the generic architecture for the image"): + r = architecture_from_string(arg); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid architecture '%s'.", arg); + + arg_architecture = r; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("size", "BYTES", + "Grow loopback file to specified size"): { + uint64_t parsed, rounded; + + if (streq(arg, "auto")) { + arg_size = UINT64_MAX; + arg_size_auto = true; + break; + } + + r = parse_size(arg, 1024, &parsed); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse --size= parameter: %s", arg); + + rounded = round_up_size(parsed, 4096); + if (rounded == 0) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Specified image size too small, refusing."); + if (rounded == UINT64_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Specified image size too large, refusing."); + + if (rounded != parsed) + log_warning("Specified size is not a multiple of 4096, rounding up automatically. (%" PRIu64 " %s %" PRIu64 ")", + parsed, glyph(GLYPH_ARROW_RIGHT), rounded); + + arg_size = rounded; + arg_size_auto = false; break; + } - case ARG_SEED: - if (isempty(optarg)) { + OPTION_LONG("seed", "UUID", + "128-bit seed UUID to derive all UUIDs from"): + if (isempty(arg)) { arg_seed = SD_ID128_NULL; arg_randomize = false; - } else if (streq(optarg, "random")) + } else if (streq(arg, "random")) arg_randomize = true; else { - r = sd_id128_from_string(optarg, &arg_seed); + r = sd_id128_from_string(arg, &arg_seed); if (r < 0) - return log_error_errno(r, "Failed to parse seed: %s", optarg); + return log_error_errno(r, "Failed to parse seed: %s", arg); arg_randomize = false; } break; - case ARG_PRETTY: - r = parse_boolean_argument("--pretty=", optarg, NULL); + OPTION_LONG("split", "BOOL", + "Whether to generate split artifacts"): + r = parse_boolean_argument("--split=", arg, NULL); if (r < 0) return r; - arg_pretty = r; + + arg_split = r; break; - case ARG_DEFINITIONS: { - _cleanup_free_ char *path = NULL; - r = parse_path_argument(optarg, false, &path); + OPTION_GROUP("Output"): {} + + OPTION_LONG("pretty", "BOOL", + "Whether to show pretty summary before doing changes"): + r = parse_boolean_argument("--pretty=", arg, NULL); if (r < 0) return r; - if (strv_consume(&arg_definitions, TAKE_PTR(path)) < 0) - return log_oom(); + arg_pretty = r; break; - } - case ARG_SIZE: { - uint64_t parsed, rounded; + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); + if (r <= 0) + return r; - if (streq(optarg, "auto")) { - arg_size = UINT64_MAX; - arg_size_auto = true; - break; - } + break; + + OPTION_GROUP("Factory Reset"): {} - r = parse_size(optarg, 1024, &parsed); + OPTION_LONG("factory-reset", "BOOL", + "Whether to remove data partitions before recreating them"): + r = parse_boolean_argument("--factory-reset=", arg, NULL); if (r < 0) - return log_error_errno(r, "Failed to parse --size= parameter: %s", optarg); + return r; + arg_factory_reset = r; + break; - rounded = round_up_size(parsed, 4096); - if (rounded == 0) - return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Specified image size too small, refusing."); - if (rounded == UINT64_MAX) - return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Specified image size too large, refusing."); + OPTION_LONG("can-factory-reset", NULL, + "Test whether factory reset is defined"): + arg_can_factory_reset = true; + break; - if (rounded != parsed) - log_warning("Specified size is not a multiple of 4096, rounding up automatically. (%" PRIu64 " %s %" PRIu64 ")", - parsed, glyph(GLYPH_ARROW_RIGHT), rounded); + OPTION_GROUP("Configuration & Image Control"): {} - arg_size = rounded; - arg_size_auto = false; + OPTION_LONG("root", "PATH", + "Operate relative to root path"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root); + if (r < 0) + return r; break; - } - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) + OPTION_LONG("image", "PATH", + "Operate relative to image file"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); + if (r < 0) return r; + arg_relax_copy_block_security = false; + break; - case ARG_KEY_FILE: { - r = parse_key_file(optarg, &arg_key); + OPTION_LONG("image-policy", "POLICY", + "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); if (r < 0) return r; break; - } - case ARG_PRIVATE_KEY: { - r = free_and_strdup_warn(&arg_private_key, optarg); + OPTION_LONG("definitions", "DIR", + "Find partition definitions in specified directory"): { + _cleanup_free_ char *path = NULL; + r = parse_path_argument(arg, false, &path); if (r < 0) return r; + if (strv_consume(&arg_definitions, TAKE_PTR(path)) < 0) + return log_oom(); break; } - case ARG_PRIVATE_KEY_SOURCE: + OPTION_LONG("list-devices", NULL, + "List candidate block devices to operate on"): + r = blockdev_list(BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING|BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_IGNORE_ZRAM, /* ret_devices= */ NULL, /* ret_n_devices= */ NULL); + if (r < 0) + return r; + + return 0; + + OPTION_GROUP("Verity"): {} + + OPTION_COMMON_PRIVATE_KEY("Private key to use when generating verity roothash signatures"): + r = free_and_strdup_warn(&arg_private_key, arg); + if (r < 0) + return r; + break; + + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - optarg, + arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) return r; break; - case ARG_CERTIFICATE: - r = free_and_strdup_warn(&arg_certificate, optarg); + OPTION_COMMON_CERTIFICATE("PEM certificate to use when generating verity roothash signatures"): + r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - optarg, + arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) return r; break; - case ARG_TPM2_DEVICE: { + OPTION_LONG("join-signature", "HASH:SIG", + "Specify root hash and pkcs7 signature of root hash for verity as a tuple of " + "hex-encoded hash and a DER-encoded PKCS7, either as a path to a file or as an " + "ASCII base64-encoded string prefixed by 'base64:'"): + r = parse_join_signature(arg, &arg_verity_settings); + if (r < 0) + return r; + break; + + OPTION_GROUP("Encryption"): {} + + OPTION_LONG("key-file", "PATH", + "Key to use when encrypting partitions"): + r = parse_key_file(arg, &arg_key); + if (r < 0) + return r; + break; + + OPTION_LONG("tpm2-device", "PATH", + "Path to TPM2 device node to use"): { _cleanup_free_ char *device = NULL; - if (streq(optarg, "list")) + if (streq(arg, "list")) return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(arg, "auto")) { + device = strdup(arg); if (!device) return log_oom(); } @@ -9464,64 +9971,65 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_TPM2_DEVICE_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_device_key); + OPTION_LONG("tpm2-device-key", "PATH", + "Enroll a TPM2 device using its public key"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_device_key); if (r < 0) return r; break; - case ARG_TPM2_SEAL_KEY_HANDLE: - r = safe_atou32_full(optarg, 16, &arg_tpm2_seal_key_handle); + OPTION_LONG("tpm2-seal-key-handle", "HANDLE", + "Specify handle of key to use for sealing"): + r = safe_atou32_full(arg, 16, &arg_tpm2_seal_key_handle); if (r < 0) - return log_error_errno(r, "Could not parse TPM2 seal key handle index '%s': %m", optarg); + return log_error_errno(r, "Could not parse TPM2 seal key handle index '%s': %m", arg); break; - case ARG_TPM2_PCRS: - r = tpm2_parse_pcr_argument_append(optarg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); + OPTION_LONG("tpm2-pcrs", "PCR1+PCR2+…", + "TPM2 PCR indexes to use for TPM2 enrollment"): + r = tpm2_parse_pcr_argument_append(arg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); if (r < 0) return r; break; - case ARG_TPM2_PUBLIC_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key); + OPTION_LONG("tpm2-public-key", "PATH", + "Enroll signed TPM2 PCR policy against PEM public key"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_public_key); if (r < 0) return r; break; - case ARG_TPM2_PUBLIC_KEY_PCRS: + OPTION_LONG("tpm2-public-key-pcrs", "PCR1+PCR2+…", + "Enroll signed TPM2 PCR policy for specified TPM2 PCRs"): auto_public_key_pcr_mask = false; - r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); + r = tpm2_parse_pcr_argument_to_mask(arg, &arg_tpm2_public_key_pcr_mask); if (r < 0) return r; break; - case ARG_TPM2_PCRLOCK: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_pcrlock); + OPTION_LONG("tpm2-pcrlock", "PATH", + "Specify pcrlock policy to lock against"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_pcrlock); if (r < 0) return r; auto_pcrlock = false; break; - case ARG_SPLIT: - r = parse_boolean_argument("--split=", optarg, NULL); - if (r < 0) - return r; - - arg_split = r; - break; + OPTION_GROUP("Partition Control"): {} - case ARG_INCLUDE_PARTITIONS: + OPTION_LONG("include-partitions", "PART1,PART2…", + "Ignore partitions not of the specified types"): if (arg_filter_partitions_type == FILTER_PARTITIONS_EXCLUDE) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Combination of --include-partitions= and --exclude-partitions= is invalid."); - r = parse_partition_types(optarg, &arg_filter_partitions, &arg_n_filter_partitions); + r = parse_partition_types(arg, &arg_filter_partitions, &arg_n_filter_partitions); if (r < 0) return r; @@ -9529,12 +10037,13 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_EXCLUDE_PARTITIONS: + OPTION_LONG("exclude-partitions", "PART1,PART2…", + "Ignore partitions of the specified types"): if (arg_filter_partitions_type == FILTER_PARTITIONS_INCLUDE) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Combination of --include-partitions= and --exclude-partitions= is invalid."); - r = parse_partition_types(optarg, &arg_filter_partitions, &arg_n_filter_partitions); + r = parse_partition_types(arg, &arg_filter_partitions, &arg_n_filter_partitions); if (r < 0) return r; @@ -9542,59 +10051,44 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_DEFER_PARTITIONS: - r = parse_partition_types(optarg, &arg_defer_partitions, &arg_n_defer_partitions); + OPTION_LONG("defer-partitions", "PART1,PART2…", + "Take partitions of the specified types into account but don't populate them yet"): + r = parse_partition_types(arg, &arg_defer_partitions, &arg_n_defer_partitions); if (r < 0) return r; break; - case ARG_DEFER_PARTITIONS_EMPTY: - r = parse_boolean_argument("--defer-partitions-empty=", optarg, &arg_defer_partitions_empty); + OPTION_LONG("defer-partitions-empty", "BOOL", + "Defer all partitions marked for formatting as empty"): + r = parse_boolean_argument("--defer-partitions-empty=", arg, &arg_defer_partitions_empty); if (r < 0) return r; break; - case ARG_DEFER_PARTITIONS_FACTORY_RESET: - r = parse_boolean_argument("--defer-partitions-factory-reset=", optarg, &arg_defer_partitions_factory_reset); + OPTION_LONG("defer-partitions-factory-reset", "BOOL", + "Defer all partitions marked for factory reset"): + r = parse_boolean_argument("--defer-partitions-factory-reset=", arg, &arg_defer_partitions_factory_reset); if (r < 0) return r; break; - case ARG_SECTOR_SIZE: - r = parse_sector_size(optarg, &arg_sector_size); - if (r < 0) - return r; - - break; + OPTION_GROUP("Copying"): {} - case ARG_ARCHITECTURE: - r = architecture_from_string(optarg); + OPTION('s', "copy-source", "PATH", + "Specify the primary source tree to copy files from"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_copy_source); if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid architecture '%s'.", optarg); - - arg_architecture = r; - break; - - case ARG_OFFLINE: - if (streq(optarg, "auto")) - arg_offline = -1; - else { - r = parse_boolean_argument("--offline=", optarg, NULL); - if (r < 0) - return r; - - arg_offline = r; - } - + return r; break; - case ARG_COPY_FROM: { + OPTION_LONG("copy-from", "IMAGE", + "Copy partitions from the given image"): { _cleanup_free_ char *p = NULL; - r = parse_path_argument(optarg, /* suppress_root= */ false, &p); + r = parse_path_argument(arg, /* suppress_root= */ false, &p); if (r < 0) return r; @@ -9604,83 +10098,110 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 's': - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_copy_source); - if (r < 0) - return r; - break; + OPTION_GROUP("DDI Profile"): {} - case ARG_MAKE_DDI: - if (!filename_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid DDI type: %s", optarg); + OPTION_LONG("make-ddi", "TYPE", + "Create a DDI of the given type"): + if (!filename_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid DDI type: %s", arg); - r = free_and_strdup_warn(&arg_make_ddi, optarg); + r = free_and_strdup_warn(&arg_make_ddi, arg); if (r < 0) return r; break; - case 'S': + OPTION_SHORT('S', NULL, "Same as --make-ddi=sysext, make a system extension"): r = free_and_strdup_warn(&arg_make_ddi, "sysext"); if (r < 0) return r; break; - case 'C': + OPTION_SHORT('C', NULL, "Same as --make-ddi=confext, make a configuration extension"): r = free_and_strdup_warn(&arg_make_ddi, "confext"); if (r < 0) return r; break; - case 'P': + OPTION_SHORT('P', NULL, "Same as --make-ddi=portable, make a portable service"): r = free_and_strdup_warn(&arg_make_ddi, "portable"); if (r < 0) return r; break; - case ARG_APPEND_FSTAB: - if (isempty(optarg)) { + OPTION_GROUP("Auxiliary Resource Generation"): {} + + OPTION_LONG("append-fstab", "MODE", + "How to join the content of a pre-existing fstab with the generated one " + "(no, auto, replace)"): + if (isempty(arg)) { arg_append_fstab = APPEND_AUTO; break; } - arg_append_fstab = append_mode_from_string(optarg); + arg_append_fstab = append_mode_from_string(arg); if (arg_append_fstab < 0) - return log_error_errno(arg_append_fstab, "Failed to parse --append-fstab= parameter: %s", optarg); + return log_error_errno(arg_append_fstab, "Failed to parse --append-fstab= parameter: %s", arg); break; - case ARG_GENERATE_FSTAB: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_generate_fstab); + OPTION_LONG("generate-fstab", "PATH", + "Write fstab configuration to the given path"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_generate_fstab); if (r < 0) return r; break; - case ARG_GENERATE_CRYPTTAB: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_generate_crypttab); + OPTION_LONG("generate-crypttab", "PATH", + "Write crypttab configuration to the given path"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_generate_crypttab); if (r < 0) return r; break; - case ARG_LIST_DEVICES: - r = blockdev_list(BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING|BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_IGNORE_ZRAM, /* ret_devices= */ NULL, /* ret_n_devices= */ NULL); + OPTION_GROUP("El Torito boot catalog"): {} + + OPTION_LONG("el-torito", "BOOL", + "Whether to add a boot catalog to boot the ESP"): + r = parse_boolean_argument("--el-torito=", arg, &arg_eltorito); if (r < 0) return r; - return 0; + break; + + OPTION_LONG("el-torito-system", "STRING", + "Set the system identifier in the ISO9660 descriptor"): + if (!iso9660_system_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-system=.", arg); - case ARG_JOIN_SIGNATURE: - r = parse_join_signature(optarg, &arg_verity_settings); + r = free_and_strdup_warn(&arg_eltorito_system, arg); if (r < 0) return r; + + break; + + OPTION_LONG("el-torito-volume", "STRING", + "Set the volume identifier in the ISO9660 descriptor"): + if (!iso9660_volume_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-volume=.", arg); + + r = free_and_strdup_warn(&arg_eltorito_volume, arg); + if (r < 0) + return r; + break; - case '?': - return -EINVAL; + OPTION_LONG("el-torito-publisher", "STRING", + "Set the publisher identifier in the ISO9660 descriptor"): + if (!iso9660_publisher_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-publisher=.", arg); - default: - assert_not_reached(); + r = free_and_strdup_warn(&arg_eltorito_publisher, arg); + if (r < 0) + return r; + + break; } - if (argc - optind > 1) + if (option_parser_get_n_args(&state) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected at most one argument, the path to the block device or image file."); @@ -9760,11 +10281,12 @@ static int parse_argv(int argc, char *argv[]) { arg_relax_copy_block_security = true; } - if (argc > optind) { - if (empty_or_dash(argv[optind])) + char **args = option_parser_get_args(&state); + if (!strv_isempty(args)) { + if (empty_or_dash(args[0])) arg_node_none = true; else { - arg_node = strdup(argv[optind]); + arg_node = strdup(args[0]); if (!arg_node) return log_oom(); arg_node_none = false; @@ -9989,7 +10511,7 @@ static int find_root(Context *context) { fd = xopenat_full(AT_FDCWD, arg_node, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOFOLLOW, XO_NOCOW, 0666); if (fd < 0) - return log_error_errno(errno, "Failed to create '%s': %m", arg_node); + return log_error_errno(fd, "Failed to create '%s': %m", arg_node); context->node = TAKE_PTR(s); context->node_is_our_file = true; @@ -10058,7 +10580,7 @@ static int resize_pt(int fd, uint64_t sector_size) { if (r < 0) return log_error_errno(r, "Failed to open device '%s': %m", FORMAT_PROC_FD_PATH(fd)); - r = fdisk_has_label(c); + r = sym_fdisk_has_label(c); if (r < 0) return log_error_errno(r, "Failed to determine whether disk '%s' has a disk label: %m", FORMAT_PROC_FD_PATH(fd)); if (r == 0) { @@ -10066,7 +10588,7 @@ static int resize_pt(int fd, uint64_t sector_size) { return 0; } - r = fdisk_write_disklabel(c); + r = sym_fdisk_write_disklabel(c); if (r < 0) return log_error_errno(r, "Failed to write resized partition table: %m"); @@ -10362,7 +10884,7 @@ static int vl_method_list_candidate_devices( if (r < 0) return r; - r = varlink_set_sentinel(link, "io.systemd.Repart.NoCandidateDevices"); + r = sd_varlink_set_sentinel(link, "io.systemd.Repart.NoCandidateDevices"); if (r < 0) return r; @@ -10498,6 +11020,10 @@ static int vl_method_run( SD_JSON_BUILD_PAIR_UNSIGNED("minimalSizeBytes", minimal_size)); } + r = context_verify_eltorito_overlap(context); + if (r < 0) + return r; + r = context_ponder(context); if (r == -ENOSPC) { uint64_t current_size, foreign_size, minimal_size; @@ -10546,6 +11072,10 @@ static int vl_method_run( if (r < 0) return r; + r = context_write_eltorito(context); + if (r < 0) + return r; + context_disarm_auto_removal(context); return sd_varlink_reply(link, NULL); @@ -10595,9 +11125,9 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; -#if HAVE_LIBCRYPTSETUP - cryptsetup_enable_logging(NULL); -#endif + r = dlopen_fdisk(LOG_ERR); + if (r < 0) + return r; if (arg_varlink) return vl_server(); @@ -10812,6 +11342,10 @@ static int run(int argc, char *argv[]) { return r; } + r = context_verify_eltorito_overlap(context); + if (r < 0) + return r; + r = context_ponder(context); if (r == -ENOSPC) { /* When we hit space issues, tell the user the minimal size. */ @@ -10827,6 +11361,10 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = context_write_eltorito(context); + if (r < 0) + return r; + r = context_split(context); if (r < 0) return r; diff --git a/src/report/meson.build b/src/report/meson.build index 2813f9d033b16..5f05382999eb8 100644 --- a/src/report/meson.build +++ b/src/report/meson.build @@ -4,6 +4,25 @@ executables += [ libexec_template + { 'name' : 'systemd-report', 'public' : true, - 'sources' : files('report.c'), + 'sources' : files( + 'report.c', + 'report-upload.c', + ), + }, + + libexec_template + { + 'name' : 'systemd-report-basic', + 'public' : true, + 'sources' : files( + 'report-basic-server.c', + 'report-basic.c', + ), + }, + libexec_template + { + 'name' : 'systemd-report-cgroup', + 'sources' : files( + 'report-cgroup.c', + 'report-cgroup-server.c', + ), }, ] diff --git a/src/report/report-basic-server.c b/src/report/report-basic-server.c new file mode 100644 index 0000000000000..ea82dbd42fe26 --- /dev/null +++ b/src/report/report-basic-server.c @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink.h" + +#include "ansi-color.h" +#include "build.h" +#include "facts.h" +#include "format-table.h" +#include "log.h" +#include "main-func.h" +#include "options.h" +#include "report-basic.h" +#include "varlink-util.h" + +static int vl_server(void) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *vs = NULL; + int r; + + r = varlink_server_new(&vs, /* flags= */ 0, /* userdata= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = facts_add_to_varlink_server(vs, vl_method_list_facts, vl_method_describe_facts); + if (r < 0) + return log_error_errno(r, "Failed to register Facts varlink interface: %m"); + + r = sd_varlink_server_loop_auto(vs); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return 0; +} + +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\n%sGenerate a report describing the current system%s\n" + "\n%sOptions:%s\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + ansi_underline(), + ansi_normal()); + + return table_print_or_warn(options); +} + +static int parse_argv(int argc, char *argv[]) { + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + + FOREACH_OPTION(&state, c, /* ret_a= */ NULL, /* on_error= */ return c) + switch (c) { + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + } + + if (state.optind < argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This program takes no arguments."); + + r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); + if (r < 0) + return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This program can only run as a Varlink service."); + return 1; +} + +static int run(int argc, char *argv[]) { + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + return vl_server(); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/report/report-basic.c b/src/report/report-basic.c new file mode 100644 index 0000000000000..381262dfd4909 --- /dev/null +++ b/src/report/report-basic.c @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-id128.h" +#include "sd-json.h" +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "architecture.h" +#include "facts.h" +#include "hostname-setup.h" +#include "report-basic.h" +#include "virt.h" + +static int architecture_generate(FactFamilyContext *context, void *userdata) { + assert(context); + + return fact_build_send_string( + context, + /* object= */ NULL, + architecture_to_string(uname_architecture())); +} + +static int boot_id_generate(FactFamilyContext *context, void *userdata) { + sd_id128_t id; + int r; + + assert(context); + + r = sd_id128_get_boot(&id); + if (r < 0) + return r; + + return fact_build_send_string( + context, + /* object= */ NULL, + SD_ID128_TO_STRING(id)); +} + +static int hostname_generate(FactFamilyContext *context, void *userdata) { + _cleanup_free_ char *hostname = NULL; + int r; + + assert(context); + + r = gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT, &hostname); + if (r < 0) + return r; + + return fact_build_send_string( + context, + /* object= */ NULL, + hostname); +} + +static int kernel_version_generate(FactFamilyContext *context, void *userdata) { + struct utsname u; + + assert(context); + + assert_se(uname(&u) >= 0); + + return fact_build_send_string( + context, + /* object= */ NULL, + u.release); +} + +static int machine_id_generate(FactFamilyContext *context, void *userdata) { + sd_id128_t id; + int r; + + assert(context); + + r = sd_id128_get_machine(&id); + if (r < 0) + return r; + + return fact_build_send_string( + context, + /* object= */ NULL, + SD_ID128_TO_STRING(id)); +} + +static int virtualization_generate(FactFamilyContext *context, void *userdata) { + Virtualization v; + + assert(context); + + v = detect_virtualization(); + if (v < 0) + return v; + + return fact_build_send_string( + context, + /* object= */ NULL, + virtualization_to_string(v)); +} + +static const FactFamily fact_family_table[] = { + /* Keep facts ordered alphabetically */ + { + .name = FACT_IO_SYSTEMD_BASIC "Architecture", + .description = "CPU architecture", + .generate = architecture_generate, + }, + { + .name = FACT_IO_SYSTEMD_BASIC "BootID", + .description = "Current boot ID", + .generate = boot_id_generate, + }, + { + .name = FACT_IO_SYSTEMD_BASIC "Hostname", + .description = "System hostname", + .generate = hostname_generate, + }, + { + .name = FACT_IO_SYSTEMD_BASIC "KernelVersion", + .description = "Kernel version", + .generate = kernel_version_generate, + }, + { + .name = FACT_IO_SYSTEMD_BASIC "MachineID", + .description = "Machine ID", + .generate = machine_id_generate, + }, + { + .name = FACT_IO_SYSTEMD_BASIC "Virtualization", + .description = "Virtualization type", + .generate = virtualization_generate, + }, + {} +}; + +int vl_method_describe_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return facts_method_describe(fact_family_table, link, parameters, flags, userdata); +} + +int vl_method_list_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return facts_method_list(fact_family_table, link, parameters, flags, userdata); +} diff --git a/src/report/report-basic.h b/src/report/report-basic.h new file mode 100644 index 0000000000000..b24613edb62ff --- /dev/null +++ b/src/report/report-basic.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +#define FACT_IO_SYSTEMD_BASIC "io.systemd.Basic." + +int vl_method_list_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_describe_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/report/report-cgroup-server.c b/src/report/report-cgroup-server.c new file mode 100644 index 0000000000000..eef2ec05fcbfd --- /dev/null +++ b/src/report/report-cgroup-server.c @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "ansi-color.h" +#include "build.h" +#include "log.h" +#include "main-func.h" +#include "pretty-print.h" +#include "report-cgroup.h" +#include "varlink-io.systemd.Metrics.h" +#include "varlink-util.h" + +static int vl_server(void) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *vs = NULL; + _cleanup_(cgroup_context_freep) CGroupContext *ctx = NULL; + int r; + + ctx = new0(CGroupContext, 1); + if (!ctx) + return log_oom(); + + r = varlink_server_new(&vs, SD_VARLINK_SERVER_INHERIT_USERDATA, ctx); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = sd_varlink_server_add_interface(vs, &vl_interface_io_systemd_Metrics); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = sd_varlink_server_bind_method_many( + vs, + "io.systemd.Metrics.List", vl_method_list_metrics, + "io.systemd.Metrics.Describe", vl_method_describe_metrics); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %m"); + + r = sd_varlink_server_loop_auto(vs); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return 0; +} + +static int help(void) { + _cleanup_free_ char *url = NULL; + int r; + + r = terminal_urlify_man("systemd-report-cgroup", "8", &url); + if (r < 0) + return log_oom(); + + printf("%s [OPTIONS...]\n" + "\n%sReport cgroup metrics.%s\n" + "\n%sOptions:%s\n" + " -h --help Show this help\n" + " --version Show package version\n" + "\nSee the %s for details.\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + ansi_underline(), + ansi_normal(), + url); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + if (optind < argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This program takes no arguments."); + + r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); + if (r < 0) + return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This program can only run as a Varlink service."); + + return 1; +} + +static int run(int argc, char *argv[]) { + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + return vl_server(); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/report/report-cgroup.c b/src/report/report-cgroup.c new file mode 100644 index 0000000000000..c3dabe41b1016 --- /dev/null +++ b/src/report/report-cgroup.c @@ -0,0 +1,498 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "extract-word.h" +#include "fd-util.h" +#include "fileio.h" +#include "log.h" +#include "metrics.h" +#include "parse-util.h" +#include "path-util.h" +#include "report-cgroup.h" +#include "string-util.h" +#include "time-util.h" + +typedef struct CGroupInfo { + char *unit; + char *path; + uint64_t io_rbytes; + uint64_t io_rios; + int io_stat_cached; /* 0 = not attempted, > 0 = cached, < 0 = -errno */ +} CGroupInfo; + +static CGroupInfo *cgroup_info_free(CGroupInfo *info) { + if (!info) + return NULL; + free(info->unit); + free(info->path); + return mfree(info); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(CGroupInfo*, cgroup_info_free); + +static void cgroup_info_array_free(CGroupInfo **infos, size_t n) { + FOREACH_ARRAY(i, infos, n) + cgroup_info_free(*i); + free(infos); +} + +static void cgroup_context_flush(CGroupContext *ctx) { + assert(ctx); + cgroup_info_array_free(ctx->cgroups, ctx->n_cgroups); + ctx->cgroups = NULL; + ctx->n_cgroups = 0; + ctx->cache_populated = false; +} + +CGroupContext *cgroup_context_free(CGroupContext *ctx) { + if (!ctx) + return NULL; + cgroup_context_flush(ctx); + return mfree(ctx); +} + +static int walk_cgroups_recursive(const char *path, CGroupInfo ***infos, size_t *n_infos) { + _cleanup_closedir_ DIR *d = NULL; + int r; + + assert(path); + assert(infos); + assert(n_infos); + + /* Collect any unit cgroup we encounter */ + _cleanup_free_ char *name = NULL; + r = cg_path_get_unit(path, &name); + if (r >= 0) { + _cleanup_(cgroup_info_freep) CGroupInfo *info = new(CGroupInfo, 1); + if (!info) + return log_oom(); + + *info = (CGroupInfo) { + .unit = TAKE_PTR(name), + .path = strdup(path), + }; + if (!info->path) + return log_oom(); + + if (!GREEDY_REALLOC(*infos, *n_infos + 1)) + return log_oom(); + + (*infos)[(*n_infos)++] = TAKE_PTR(info); + return 0; /* Unit cgroups are leaf nodes for our purposes */ + } + + /* Stop at delegation boundaries — don't descend into delegated subtrees */ + r = cg_is_delegated(path); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_debug_errno(r, "Failed to check delegation for '%s': %m", path); + if (r > 0) + return 0; + + r = cg_enumerate_subgroups(path, &d); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_debug_errno(r, "Failed to enumerate cgroup '%s': %m", path); + + for (;;) { + _cleanup_free_ char *fn = NULL, *child = NULL; + + r = cg_read_subgroup(d, &fn); + if (r < 0) + return log_debug_errno(r, "Failed to read subgroup from '%s': %m", path); + if (r == 0) + break; + + child = path_join(empty_to_root(path), fn); + if (!child) + return log_oom(); + + path_simplify(child); + + r = walk_cgroups_recursive(child, infos, n_infos); + if (r < 0) + return r; + } + + return 0; +} + +static int walk_cgroups(CGroupContext *ctx, CGroupInfo ***ret, size_t *ret_n) { + int r; + + assert(ctx); + assert(ret); + assert(ret_n); + + /* Return cached result if available */ + if (ctx->cache_populated) { + *ret = ctx->cgroups; + *ret_n = ctx->n_cgroups; + return 0; + } + + CGroupInfo **infos = NULL; + size_t n_infos = 0; + CLEANUP_ARRAY(infos, n_infos, cgroup_info_array_free); + + r = walk_cgroups_recursive("", &infos, &n_infos); + if (r < 0) + return r; + + ctx->cgroups = TAKE_PTR(infos); + ctx->n_cgroups = TAKE_GENERIC(n_infos, size_t, 0); + ctx->cache_populated = true; + + *ret = ctx->cgroups; + *ret_n = ctx->n_cgroups; + return 0; +} + +static int cpu_usage_build_json(MetricFamilyContext *context, void *userdata) { + CGroupContext *ctx = ASSERT_PTR(userdata); + CGroupInfo **cgroups; + size_t n_cgroups; + int r; + + assert(context); + + r = walk_cgroups(ctx, &cgroups, &n_cgroups); + if (r < 0) + return 0; /* Skip metric on failure */ + + FOREACH_ARRAY(c, cgroups, n_cgroups) { + uint64_t us; + + r = cg_get_keyed_attribute_uint64((*c)->path, "cpu.stat", "usage_usec", &us); + if (r < 0) + continue; + + r = metric_build_send_unsigned( + context, + (*c)->unit, + us * NSEC_PER_USEC, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int memory_usage_build_json(MetricFamilyContext *context, void *userdata) { + CGroupContext *ctx = ASSERT_PTR(userdata); + CGroupInfo **cgroups; + size_t n_cgroups; + int r; + + assert(context); + + r = walk_cgroups(ctx, &cgroups, &n_cgroups); + if (r < 0) + return 0; + + FOREACH_ARRAY(c, cgroups, n_cgroups) { + uint64_t current = 0, limit = UINT64_MAX; + + r = cg_get_attribute_as_uint64((*c)->path, "memory.current", ¤t); + if (r >= 0) { + /* Walk up the cgroup tree to find the tightest memory limit */ + _cleanup_free_ char *path_buf = strdup((*c)->path); + if (!path_buf) + return log_oom(); + + for (char *p = path_buf;;) { + uint64_t high, max; + + r = cg_get_attribute_as_uint64(p, "memory.max", &max); + if (r >= 0 && max < limit) + limit = max; + + r = cg_get_attribute_as_uint64(p, "memory.high", &high); + if (r >= 0 && high < limit) + limit = high; + + /* Move to parent */ + const char *e; + r = path_find_last_component(p, /* accept_dot_dot= */ false, &e, NULL); + if (r <= 0) + break; + p[e - p] = '\0'; + } + + if (limit != UINT64_MAX && limit > current) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; + r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("type", "available")); + if (r < 0) + return r; + + r = metric_build_send_unsigned( + context, + (*c)->unit, + limit - current, + fields); + if (r < 0) + return r; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; + r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("type", "current")); + if (r < 0) + return r; + + r = metric_build_send_unsigned( + context, + (*c)->unit, + current, + fields); + if (r < 0) + return r; + } + + uint64_t val; + r = cg_get_attribute_as_uint64((*c)->path, "memory.peak", &val); + if (r >= 0) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; + r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("type", "peak")); + if (r < 0) + return r; + + r = metric_build_send_unsigned( + context, + (*c)->unit, + val, + fields); + if (r < 0) + return r; + } + } + + return 0; +} + +/* Parse io.stat for a cgroup once, summing both rbytes= and rios= fields in a + * single pass to avoid reading the file twice. */ +static int io_stat_parse(const char *cgroup_path, uint64_t *ret_rbytes, uint64_t *ret_rios) { + _cleanup_free_ char *path = NULL; + _cleanup_fclose_ FILE *f = NULL; + uint64_t rbytes = 0, rios = 0; + int r; + + assert(ret_rbytes); + assert(ret_rios); + + r = cg_get_path(cgroup_path, "io.stat", &path); + if (r < 0) + return r; + + f = fopen(path, "re"); + if (!f) + return -errno; + + for (;;) { + _cleanup_free_ char *line = NULL; + const char *p; + + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) + break; + + p = line; + p += strcspn(p, WHITESPACE); + p += strspn(p, WHITESPACE); + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return r; + if (r == 0) + break; + + const char *v; + uint64_t val; + + v = startswith(word, "rbytes="); + if (v && safe_atou64(v, &val) >= 0) { + rbytes += val; + continue; + } + + v = startswith(word, "rios="); + if (v && safe_atou64(v, &val) >= 0) + rios += val; + } + } + + *ret_rbytes = rbytes; + *ret_rios = rios; + return 0; +} + +static int ensure_io_stat_cached(CGroupInfo *info) { + int r; + + assert(info); + + if (info->io_stat_cached > 0) + return 0; + if (info->io_stat_cached < 0) + return info->io_stat_cached; + + r = io_stat_parse(info->path, &info->io_rbytes, &info->io_rios); + if (r < 0) { + if (r != -ENOENT) + log_debug_errno(r, "Failed to parse IO stats for '%s': %m", info->path); + info->io_stat_cached = r; + return r; + } + + info->io_stat_cached = 1; + return 0; +} + +static int io_read_bytes_build_json(MetricFamilyContext *context, void *userdata) { + CGroupContext *ctx = ASSERT_PTR(userdata); + CGroupInfo **cgroups; + size_t n_cgroups; + int r; + + assert(context); + + r = walk_cgroups(ctx, &cgroups, &n_cgroups); + if (r < 0) + return 0; + + FOREACH_ARRAY(c, cgroups, n_cgroups) { + if (ensure_io_stat_cached(*c) < 0) + continue; + + r = metric_build_send_unsigned( + context, + (*c)->unit, + (*c)->io_rbytes, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int io_read_operations_build_json(MetricFamilyContext *context, void *userdata) { + CGroupContext *ctx = ASSERT_PTR(userdata); + CGroupInfo **cgroups; + size_t n_cgroups; + int r; + + assert(context); + + r = walk_cgroups(ctx, &cgroups, &n_cgroups); + if (r < 0) + return 0; + + FOREACH_ARRAY(c, cgroups, n_cgroups) { + if (ensure_io_stat_cached(*c) < 0) + continue; + + r = metric_build_send_unsigned( + context, + (*c)->unit, + (*c)->io_rios, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int tasks_current_build_json(MetricFamilyContext *context, void *userdata) { + CGroupContext *ctx = ASSERT_PTR(userdata); + CGroupInfo **cgroups; + size_t n_cgroups; + int r; + + assert(context); + + r = walk_cgroups(ctx, &cgroups, &n_cgroups); + if (r < 0) + return 0; + + FOREACH_ARRAY(c, cgroups, n_cgroups) { + uint64_t val; + + r = cg_get_attribute_as_uint64((*c)->path, "pids.current", &val); + if (r < 0) + continue; + + r = metric_build_send_unsigned( + context, + (*c)->unit, + val, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static const MetricFamily cgroup_metric_family_table[] = { + /* Keep metrics ordered alphabetically */ + { + .name = METRIC_IO_SYSTEMD_CGROUP_PREFIX "CpuUsage", + .description = "Per unit metric: CPU usage in nanoseconds", + .type = METRIC_FAMILY_TYPE_COUNTER, + .generate = cpu_usage_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_CGROUP_PREFIX "IOReadBytes", + .description = "Per unit metric: IO bytes read", + .type = METRIC_FAMILY_TYPE_COUNTER, + .generate = io_read_bytes_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_CGROUP_PREFIX "IOReadOperations", + .description = "Per unit metric: IO read operations", + .type = METRIC_FAMILY_TYPE_COUNTER, + .generate = io_read_operations_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_CGROUP_PREFIX "MemoryUsage", + .description = "Per unit metric: memory usage in bytes", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = memory_usage_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_CGROUP_PREFIX "TasksCurrent", + .description = "Per unit metric: current number of tasks", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = tasks_current_build_json, + }, + {} +}; + +int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return metrics_method_describe(cgroup_metric_family_table, link, parameters, flags, userdata); +} + +int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + CGroupContext *ctx = ASSERT_PTR(userdata); + int r; + + r = metrics_method_list(cgroup_metric_family_table, link, parameters, flags, userdata); + + cgroup_context_flush(ctx); + + return r; +} diff --git a/src/report/report-cgroup.h b/src/report/report-cgroup.h new file mode 100644 index 0000000000000..dae8411df58b0 --- /dev/null +++ b/src/report/report-cgroup.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +#define METRIC_IO_SYSTEMD_CGROUP_PREFIX "io.systemd.CGroup." + +typedef struct CGroupInfo CGroupInfo; + +typedef struct CGroupContext { + CGroupInfo **cgroups; + size_t n_cgroups; + bool cache_populated; +} CGroupContext; + +CGroupContext *cgroup_context_free(CGroupContext *ctx); +DEFINE_TRIVIAL_CLEANUP_FUNC(CGroupContext*, cgroup_context_free); + +int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/report/report-upload.c b/src/report/report-upload.c new file mode 100644 index 0000000000000..c64bf86e13336 --- /dev/null +++ b/src/report/report-upload.c @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "log.h" +#include "report.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "utf8.h" +#include "version.h" + +#if HAVE_LIBCURL +#include "curl-util.h" + +#define SERVER_ANSWER_MAX (1*1024*1024u) + +static size_t output_callback(char *buf, + size_t size, + size_t nmemb, + void *userp) { + + Context *context = ASSERT_PTR(userp); + int r; + + assert(size == 1); /* The docs say that this is always true. */ + + log_debug("Got an answer from the server (%zu bytes)", nmemb); + + if (nmemb != 0) { + size_t new_size = size_add(iovw_size(&context->upload_answer), nmemb); + + if (new_size > SERVER_ANSWER_MAX) { + log_warning("Server answer too long (%zu > %u), refusing.", new_size, SERVER_ANSWER_MAX); + return 0; + } + + if (memchr(buf, 0, nmemb)) { + log_warning("Server answer contains an embedded NUL, refusing."); + return 0; + } + + r = iovw_extend(&context->upload_answer, buf, nmemb); + if (r < 0) { + log_warning("Failed to store server answer (%zu bytes): out of memory", nmemb); + return 0; /* Returning < nmemb signals failure */ + } + } + + return nmemb; +} + +static int build_json_report(Context *context, sd_json_variant **ret) { + /* Convert the variant array to a JSON report. */ + + assert(context); + assert(ret); + + usec_t ts = now(CLOCK_REALTIME); + int r; + + const char *ident; + if (IN_SET(context->action, ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS)) + ident = "metrics"; + else if (IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS)) + ident = "facts"; + else + assert_not_reached(); + + r = sd_json_buildo(ret, + SD_JSON_BUILD_PAIR("timestamp", + SD_JSON_BUILD_STRING(FORMAT_TIMESTAMP_STYLE(ts, TIMESTAMP_UTC))), + SD_JSON_BUILD_PAIR(ident, + SD_JSON_BUILD_VARIANT_ARRAY(context->metrics, context->n_metrics))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON data: %m"); + return 0; +} +#endif + +int upload_collected(Context *context) { +#if HAVE_LIBCURL + _cleanup_(curl_slist_free_allp) struct curl_slist *header = NULL; + char error[CURL_ERROR_SIZE] = {}; + _cleanup_free_ char *json = NULL; + int r; + + r = dlopen_curl(LOG_DEBUG); + if (r < 0) + return r; + + { + /* Convert our variant array to a JSON report. + * We won't need the JSON structure again, so free it quickly. */ + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *vl = NULL; + r = build_json_report(context, &vl); + if (r < 0) + return r; + + r = sd_json_variant_format(vl, /* flags= */ 0, &json); + if (r < 0) + return log_error_errno(r, "Failed to format JSON data: %m"); + } + + r = curl_append_to_header(&header, + STRV_MAKE("Content-Type: application/json", + "Accept: application/json")); + if (r < 0) + return log_error_errno(r, "Failed to create curl header: %m"); + + r = curl_append_to_header(&header, arg_extra_headers); + if (r < 0) + return log_error_errno(r, "Failed to create curl header: %m"); + + _cleanup_(curl_easy_cleanupp) CURL *curl = sym_curl_easy_init(); + if (!curl) + return log_error_errno(SYNTHETIC_ERRNO(ENOSR), + "Call to curl_easy_init failed."); + + /* If configured, set a timeout for the curl operation. */ + if (arg_network_timeout_usec != USEC_INFINITY && + !easy_setopt(curl, LOG_ERR, CURLOPT_TIMEOUT, + (long) DIV_ROUND_UP(arg_network_timeout_usec, USEC_PER_SEC))) + return -EXFULL; + + /* Tell it to POST to the URL */ + if (!easy_setopt(curl, LOG_ERR, CURLOPT_POST, 1L)) + return -EXFULL; + + if (!easy_setopt(curl, LOG_ERR, CURLOPT_ERRORBUFFER, error)) + return -EXFULL; + + /* Where to write to */ + if (!easy_setopt(curl, LOG_ERR, CURLOPT_WRITEFUNCTION, output_callback)) + return -EXFULL; + + if (!easy_setopt(curl, LOG_ERR, CURLOPT_WRITEDATA, context)) + return -EXFULL; + + if (!easy_setopt(curl, LOG_ERR, CURLOPT_HTTPHEADER, header)) + return -EXFULL; + + if (DEBUG_LOGGING) + /* enable verbose for easier tracing */ + (void) easy_setopt(curl, LOG_WARNING, CURLOPT_VERBOSE, 1L); + + (void) easy_setopt(curl, LOG_WARNING, + CURLOPT_USERAGENT, "systemd-report " GIT_VERSION); + + if (!streq_ptr(arg_key, "-") && (arg_key || startswith(arg_url, "https://"))) { + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSLKEY, arg_key ?: REPORT_PRIV_KEY_FILE)) + return -EXFULL; + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSLCERT, arg_cert ?: REPORT_CERT_FILE)) + return -EXFULL; + } + + if (STRPTR_IN_SET(arg_trust, "-", "all")) { + log_info("Server certificate verification disabled."); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSL_VERIFYPEER, 0L)) + return -EUCLEAN; + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSL_VERIFYHOST, 0L)) + return -EUCLEAN; + } else if (arg_trust || startswith(arg_url, "https://")) { + if (!easy_setopt(curl, LOG_ERR, CURLOPT_CAINFO, arg_trust ?: REPORT_TRUST_FILE)) + return -EXFULL; + } + + if (startswith(arg_url, "https://")) + (void) easy_setopt(curl, LOG_WARNING, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); + + /* Upload to this place */ + if (!easy_setopt(curl, LOG_ERR, CURLOPT_URL, arg_url)) + return -EXFULL; + + if (!easy_setopt(curl, LOG_ERR, CURLOPT_POSTFIELDS, json)) + return -EXFULL; + + CURLcode code = sym_curl_easy_perform(curl); + if (code != CURLE_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Upload to %s failed: %s", arg_url, + empty_to_null(&error[0]) ?: sym_curl_easy_strerror(code)); + + long status; + code = sym_curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + if (code != CURLE_OK) + return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), + "Failed to retrieve response code: %s", + sym_curl_easy_strerror(code)); + + _cleanup_free_ char *ans = iovw_to_cstring(&context->upload_answer); + if (!ans) + return log_oom(); + + if (!utf8_is_valid(ans)) + return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), + "Upload to %s failed with code %ld and an invalid UTF-8 answer.", + arg_url, status); + + if (status >= 300) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Upload to %s failed with code %ld: %s", + arg_url, status, strna(ans)); + if (status < 200) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Upload to %s finished with unexpected code %ld: %s", + arg_url, status, strna(ans)); + log_info("Upload to %s finished successfully with code %ld: %s", + arg_url, status, strna(ans)); + return 0; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Compiled without libcurl."); +#endif +} diff --git a/src/report/report.c b/src/report/report.c index 3c2f89e002909..c45c4da7ea63e 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-event.h" #include "sd-varlink.h" @@ -13,21 +11,25 @@ #include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "path-lookup.h" #include "pretty-print.h" #include "recurse-dir.h" +#include "report.h" #include "runtime-scope.h" #include "set.h" #include "sort-util.h" +#include "string-table.h" #include "string-util.h" #include "strv.h" #include "time-util.h" #include "varlink-idl-util.h" #include "verbs.h" +#include "web-util.h" -#define METRICS_MAX 1024U -#define METRICS_LINKS_MAX 128U +#define METRICS_OR_FACTS_MAX 4096U +#define METRICS_OR_FACTS_LINKS_MAX 128U #define TIMEOUT_USEC (30 * USEC_PER_SEC) /* 30 seconds */ static PagerFlags arg_pager_flags = 0; @@ -35,23 +37,19 @@ static bool arg_legend = true; static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; static char **arg_matches = NULL; +char *arg_url = NULL; +char *arg_key = NULL; +char *arg_cert = NULL; +char *arg_trust = NULL; +char **arg_extra_headers = NULL; +usec_t arg_network_timeout_usec = TIMEOUT_USEC; STATIC_DESTRUCTOR_REGISTER(arg_matches, strv_freep); - -typedef enum Action { - ACTION_LIST, - ACTION_DESCRIBE, - _ACTION_MAX, - _ACTION_INVALID = -EINVAL, -} Action; - -typedef struct Context { - Action action; - sd_event *event; - Set *link_infos; - sd_json_variant **metrics; /* Collected metrics for sorting */ - size_t n_metrics, n_skipped_metrics, n_invalid_metrics; -} Context; +STATIC_DESTRUCTOR_REGISTER(arg_url, freep); +STATIC_DESTRUCTOR_REGISTER(arg_key, freep); +STATIC_DESTRUCTOR_REGISTER(arg_cert, freep); +STATIC_DESTRUCTOR_REGISTER(arg_trust, freep); +STATIC_DESTRUCTOR_REGISTER(arg_extra_headers, strv_freep); typedef struct LinkInfo { Context *context; @@ -72,19 +70,28 @@ static void context_done(Context *context) { if (!context) return; - set_free(context->link_infos); + context->event = sd_event_unref(context->event); + context->link_infos = set_free(context->link_infos); sd_json_variant_unref_many(context->metrics, context->n_metrics); - sd_event_unref(context->event); + context->metrics = NULL; + context->n_metrics = 0; + iovw_done_free(&context->upload_answer); } DEFINE_TRIVIAL_CLEANUP_FUNC(LinkInfo*, link_info_free); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( link_info_hash_ops, - void, - trivial_hash_func, - trivial_compare_func, - LinkInfo, - link_info_free); + void, trivial_hash_func, trivial_compare_func, + LinkInfo, link_info_free); + +static const char* const action_method_table[] = { + [ACTION_LIST_METRICS] = "io.systemd.Metrics.List", + [ACTION_DESCRIBE_METRICS] = "io.systemd.Metrics.Describe", + [ACTION_LIST_FACTS] = "io.systemd.Facts.List", + [ACTION_DESCRIBE_FACTS] = "io.systemd.Facts.Describe", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(action_method, Action); static int metric_compare(sd_json_variant *const *a, sd_json_variant *const *b) { const char *name_a, *name_b, *object_a, *object_b; @@ -216,7 +223,7 @@ static Verdict metrics_verdict(LinkInfo *li, sd_json_variant *metric) { return VERDICT_MATCH; } -static int metrics_on_query_reply( +static int on_query_reply( sd_varlink *link, sd_json_variant *parameters, const char *error_id, @@ -229,7 +236,10 @@ static int metrics_on_query_reply( Context *context = ASSERT_PTR(li->context); if (error_id) { - if (streq(error_id, SD_VARLINK_ERROR_DISCONNECTED)) + if (STR_IN_SET(error_id, SD_VARLINK_ERROR_METHOD_NOT_FOUND, + SD_VARLINK_ERROR_METHOD_NOT_IMPLEMENTED)) + log_debug("Ignoring Varlink endpoint '%s': %s", li->name, error_id); + else if (streq(error_id, SD_VARLINK_ERROR_DISCONNECTED)) log_warning("Varlink connection to '%s' disconnected prematurely, ignoring.", li->name); else if (streq(error_id, SD_VARLINK_ERROR_TIMEOUT)) log_warning("Varlink connection to '%s' timed out, ignoring.", li->name); @@ -239,7 +249,7 @@ static int metrics_on_query_reply( goto finish; } - if (context->n_metrics >= METRICS_MAX) { + if (context->n_metrics >= METRICS_OR_FACTS_MAX) { context->n_skipped_metrics++; goto finish; } @@ -269,7 +279,7 @@ static int metrics_on_query_reply( return 0; } -static int metrics_call(Context *context, const char *name, const char *path) { +static int call_collect(Context *context, const char *name, const char *path) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; int r; @@ -288,14 +298,13 @@ static int metrics_call(Context *context, const char *name, const char *path) { if (r < 0) return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); - r = sd_varlink_bind_reply(vl, metrics_on_query_reply); + r = sd_varlink_bind_reply(vl, on_query_reply); if (r < 0) return log_error_errno(r, "Failed to bind reply callback: %m"); - const char *method = context->action == ACTION_LIST ? "io.systemd.Metrics.List" : "io.systemd.Metrics.Describe"; - r = sd_varlink_observe(vl, - method, - /* parameters= */ NULL); + const char *method = ASSERT_PTR(action_method_to_string(context->action)); + + r = sd_varlink_observe(vl, method, /* parameters= */ NULL); if (r < 0) return log_error_errno(r, "Failed to issue %s() call: %m", method); @@ -308,7 +317,6 @@ static int metrics_call(Context *context, const char *name, const char *path) { .link = sd_varlink_ref(vl), .name = strdup(name), }; - if (!li->name) return log_oom(); @@ -321,10 +329,11 @@ static int metrics_call(Context *context, const char *name, const char *path) { return 0; } -static int metrics_output_list(Context *context, Table **ret) { +static int output_collected_list(Context *context, Table **ret) { int r; assert(context); + assert(ret); _cleanup_(table_unrefp) Table *table = table_new("family", "object", "fields", "value"); if (!table) @@ -375,10 +384,11 @@ static int metrics_output_list(Context *context, Table **ret) { return 0; } -static int metrics_output_describe(Context *context, Table **ret) { +static int output_collected_describe(Context *context, Table **ret) { int r; assert(context); + assert(ret); _cleanup_(table_unrefp) Table *table = table_new("family", "type", "description"); if (!table) @@ -426,7 +436,108 @@ static int metrics_output_describe(Context *context, Table **ret) { return 0; } -static int metrics_output(Context *context) { +static int facts_output_list(Context *context, Table **ret) { + int r; + + assert(context); + assert(ret); + + _cleanup_(table_unrefp) Table *table = table_new("family", "object", "value"); + if (!table) + return log_oom(); + + table_set_ersatz_string(table, TABLE_ERSATZ_DASH); + table_set_sort(table, (size_t) 0, (size_t) 1, (size_t) 2); + + FOREACH_ARRAY(m, context->metrics, context->n_metrics) { + struct { + const char *name; + const char *object; + sd_json_variant *value; + } d = {}; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, name), SD_JSON_MANDATORY }, + { "object", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, object), 0 }, + { "value", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_variant_noref, voffsetof(d, value), SD_JSON_MANDATORY }, + {} + }; + + r = sd_json_dispatch(*m, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &d); + if (r < 0) { + _cleanup_free_ char *t = NULL; + int k = sd_json_variant_format(*m, /* flags= */ 0, &t); + if (k < 0) + return log_error_errno(k, "Failed to format JSON: %m"); + + log_warning_errno(r, "Cannot parse fact, skipping: %s", t); + continue; + } + + r = table_add_many( + table, + TABLE_STRING, d.name, + TABLE_STRING, d.object, + TABLE_JSON, d.value, + TABLE_SET_WEIGHT, 50U); + if (r < 0) + return table_log_add_error(r); + } + + *ret = TAKE_PTR(table); + return 0; +} + +static int facts_output_describe(Context *context, Table **ret) { + int r; + + assert(context); + assert(ret); + + _cleanup_(table_unrefp) Table *table = table_new("family", "description"); + if (!table) + return log_oom(); + + table_set_ersatz_string(table, TABLE_ERSATZ_DASH); + table_set_sort(table, (size_t) 0, (size_t) 1); + + FOREACH_ARRAY(m, context->metrics, context->n_metrics) { + struct { + const char *name; + const char *description; + } d = {}; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, name), SD_JSON_MANDATORY }, + { "description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, description), 0 }, + {} + }; + + r = sd_json_dispatch(*m, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &d); + if (r < 0) { + _cleanup_free_ char *t = NULL; + int k = sd_json_variant_format(*m, /* flags= */ 0, &t); + if (k < 0) + return log_error_errno(k, "Failed to format JSON: %m"); + + log_warning_errno(r, "Cannot parse fact description, skipping: %s", t); + continue; + } + + r = table_add_many( + table, + TABLE_STRING, d.name, + TABLE_STRING, d.description, + TABLE_SET_WEIGHT, 50U); + if (r < 0) + return table_log_add_error(r); + } + + *ret = TAKE_PTR(table); + return 0; +} + +static int output_collected(Context *context) { int r; assert(context); @@ -444,21 +555,34 @@ static int metrics_output(Context *context) { return log_error_errno(r, "Failed to write JSON: %m"); } - if (context->n_metrics == 0 && arg_legend) - log_info("No metrics collected."); + if (context->n_metrics == 0 && arg_legend) { + if (IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS)) + log_info("No facts collected."); + else + log_info("No metrics collected."); + } return 0; } _cleanup_(table_unrefp) Table *table = NULL; + switch(context->action) { - case ACTION_LIST: - r = metrics_output_list(context, &table); + case ACTION_LIST_METRICS: + r = output_collected_list(context, &table); + break; + + case ACTION_DESCRIBE_METRICS: + r = output_collected_describe(context, &table); break; - case ACTION_DESCRIBE: - r = metrics_output_describe(context, &table); + case ACTION_LIST_FACTS: + r = facts_output_list(context, &table); + break; + + case ACTION_DESCRIBE_FACTS: + r = facts_output_describe(context, &table); break; default: @@ -474,10 +598,12 @@ static int metrics_output(Context *context) { } if (arg_legend && !sd_json_format_enabled(arg_json_format_flags)) { + const char *type = IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS) ? "facts" : "metrics"; + if (table_isempty(table)) - printf("No metrics available.\n"); + printf("No %s available.\n", type); else - printf("\n%zu metrics listed.\n", table_get_rows(table) - 1); + printf("\n%zu %s listed.\n", table_get_rows(table) - 1, type); } return 0; @@ -579,23 +705,21 @@ static int readdir_sources(char **ret_directory, DirectoryEntries **ret) { return m > 0; } -static int verb_metrics(int argc, char *argv[], void *userdata) { - Action action; +VERB_FULL(verb_metrics, "metrics", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_LIST_METRICS, + "Acquire list of metrics and their values"); +VERB_FULL(verb_metrics, "describe-metrics", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_DESCRIBE_METRICS, + "Describe available metrics"); +static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) { + Action action = data; int r; assert(argc >= 1); assert(argv); + assert(IN_SET(action, ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS)); /* Enable JSON-SEQ mode here, since we'll dump a large series of JSON objects */ arg_json_format_flags |= SD_JSON_FORMAT_SEQ; - if (streq_ptr(argv[0], "metrics")) - action = ACTION_LIST; - else { - assert(streq_ptr(argv[0], "describe-metrics")); - action = ACTION_DESCRIBE; - } - r = parse_metrics_matches(argv + 1); if (r < 0) return r; @@ -622,7 +746,7 @@ static int verb_metrics(int argc, char *argv[], void *userdata) { FOREACH_ARRAY(i, de->entries, de->n_entries) { struct dirent *d = *i; - if (set_size(context.link_infos) >= METRICS_LINKS_MAX) { + if (set_size(context.link_infos) >= METRICS_OR_FACTS_LINKS_MAX) { n_skipped_sources++; break; } @@ -631,7 +755,7 @@ static int verb_metrics(int argc, char *argv[], void *userdata) { if (!p) return log_oom(); - (void) metrics_call(&context, d->d_name, p); + (void) call_collect(&context, d->d_name, p); } } @@ -645,7 +769,10 @@ static int verb_metrics(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); - r = metrics_output(&context); + if (arg_url) + r = upload_collected(&context); + else + r = output_collected(&context); if (r < 0) return r; } @@ -665,7 +792,95 @@ static int verb_metrics(int argc, char *argv[], void *userdata) { return 0; } -static int verb_list_sources(int argc, char *argv[], void *userdata) { +VERB_FULL(verb_facts, "facts", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_LIST_FACTS, + "Acquire list of facts and their values"); +VERB_FULL(verb_facts, "describe-facts", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_DESCRIBE_FACTS, + "Describe available facts"); +static int verb_facts(int argc, char *argv[], uintptr_t data, void *userdata) { + Action action = data; + int r; + + assert(argc >= 1); + assert(argv); + assert(IN_SET(action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS)); + + /* Enable JSON-SEQ mode here, since we'll dump a large series of JSON objects */ + arg_json_format_flags |= SD_JSON_FORMAT_SEQ; + + r = parse_metrics_matches(argv + 1); + if (r < 0) + return r; + + _cleanup_(context_done) Context context = { + .action = action, + }; + size_t n_skipped_sources = 0; + + _cleanup_free_ DirectoryEntries *de = NULL; + _cleanup_free_ char *sources_path = NULL; + r = readdir_sources(&sources_path, &de); + if (r < 0) + return r; + if (r > 0) { + r = sd_event_default(&context.event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_event_set_signal_exit(context.event, true); + if (r < 0) + return log_error_errno(r, "Failed to enable exit on SIGINT/SIGTERM: %m"); + + FOREACH_ARRAY(i, de->entries, de->n_entries) { + struct dirent *d = *i; + + if (set_size(context.link_infos) >= METRICS_OR_FACTS_LINKS_MAX) { + n_skipped_sources++; + break; + } + + _cleanup_free_ char *p = path_join(sources_path, d->d_name); + if (!p) + return log_oom(); + + (void) call_collect(&context, d->d_name, p); + } + } + + if (set_isempty(context.link_infos)) { + if (arg_legend) + log_info("No facts sources found."); + } else { + assert(context.event); + + r = sd_event_loop(context.event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + if (arg_url) + r = upload_collected(&context); + else + r = output_collected(&context); + if (r < 0) + return r; + } + + if (n_skipped_sources > 0) + return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN), + "Too many facts sources, only %u sources contacted, %zu sources skipped.", + set_size(context.link_infos), n_skipped_sources); + if (context.n_invalid_metrics > 0) + return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN), + "%zu facts are not valid.", + context.n_invalid_metrics); + if (context.n_skipped_metrics > 0) + return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN), + "Too many facts, only %zu facts collected, %zu facts skipped.", + context.n_metrics, context.n_skipped_metrics); + return 0; +} + +VERB_NOARG(verb_list_sources, "list-sources", "Show list of known metrics sources"); +static int verb_list_sources(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(table_unrefp) Table *table = table_new("source", "address"); @@ -720,137 +935,160 @@ static int verb_list_sources(int argc, char *argv[], void *userdata) { return 0; } -static int verb_help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; r = terminal_urlify_man("systemd-report", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sAcquire metrics from local sources.%6$s\n" - "\n%3$sCommands:%4$s\n" - " metrics [MATCH...] Acquire list of metrics and their values\n" - " describe-metrics [MATCH...]\n" - " Describe available metrics\n" - " list-sources Show list of known metrics sources\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --user Connect to user service manager\n" - " --system Connect to system service manager (default)\n" - " --json=pretty|short\n" - " Configure JSON output\n" - " -j Equivalent to --json=pretty (on TTY) or --json=short\n" - " (otherwise)\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options, verbs); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sAcquire metrics and facts from local sources.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), + ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", + ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_USER, - ARG_SYSTEM, - ARG_JSON, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "json", required_argument, NULL, ARG_JSON }, - {} - }; +VERB_COMMON_HELP_HIDDEN(help); - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hj", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return verb_help(/* argc= */ 0, /* argv= */ NULL, /* userdata= */ NULL); + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Connect to user service manager"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Connect to system service manager (default)"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case 'j': + OPTION_COMMON_LOWERCASE_J: arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; - case '?': - return -EINVAL; + OPTION_LONG("url", "URL", + "Upload to this address"): + r = free_and_strdup_warn(&arg_url, arg); + if (r < 0) + return r; + break; - default: - assert_not_reached(); - } + OPTION_LONG("key", "FILENAME", + "Specify key in PEM format (default: \"" REPORT_PRIV_KEY_FILE "\")"): + r = free_and_strdup_warn(&arg_key, arg); + if (r < 0) + return r; + break; - return 1; -} + OPTION_LONG("cert", "FILENAME", + "Specify certificate in PEM format (default: \"" REPORT_CERT_FILE "\")"): + r = free_and_strdup_warn(&arg_cert, arg); + if (r < 0) + return r; + break; -static int report_main(int argc, char *argv[]) { + OPTION_LONG("trust", "FILENAME|all", + "Specify CA certificate or disable checking (default: \"" REPORT_TRUST_FILE "\")"): + r = free_and_strdup_warn(&arg_trust, arg); + if (r < 0) + return r; + break; - static const Verb verbs[] = { - { "help", VERB_ANY, 1, 0, verb_help }, - { "metrics", VERB_ANY, VERB_ANY, 0, verb_metrics }, - { "describe-metrics", VERB_ANY, VERB_ANY, 0, verb_metrics }, - { "list-sources", VERB_ANY, 1, 0, verb_list_sources }, - {} - }; + OPTION_LONG("network-timeout", "SEC", "Specify timeout for network upload operation"): + r = parse_sec(arg, &arg_network_timeout_usec); + if (r < 0) + return log_error_errno(r, "Failed to parse --network-timeout value: %s", arg); + break; + + OPTION_LONG("extra-header", "NAME: VALUE", + "Inject additional header into the upload request"): + if (isempty(arg)) { + arg_extra_headers = strv_free(arg_extra_headers); + break; + } - return dispatch_verb(argc, argv, verbs, NULL); + if (!http_header_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header: %s", arg); + + if (strv_extend(&arg_extra_headers, arg) < 0) + return log_oom(); + break; + } + + if ((arg_url || arg_key || arg_cert || arg_trust || arg_extra_headers) && !HAVE_LIBCURL) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Compiled without libcurl."); + + *ret_args = option_parser_get_args(&state); + return 1; } static int run(int argc, char *argv[]) { + char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return report_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/report/report.h b/src/report/report.h new file mode 100644 index 0000000000000..4d7b5bdd3f0bb --- /dev/null +++ b/src/report/report.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +#include "iovec-wrapper.h" + +#define REPORT_PRIV_KEY_FILE CERTIFICATE_ROOT "/private/systemd-report.pem" +#define REPORT_CERT_FILE CERTIFICATE_ROOT "/certs/systemd-report.pem" +#define REPORT_TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" + +extern char *arg_url, *arg_key, *arg_cert, *arg_trust; +extern char **arg_extra_headers; +extern usec_t arg_network_timeout_usec; + +typedef enum Action { + ACTION_LIST_METRICS, + ACTION_DESCRIBE_METRICS, + ACTION_LIST_FACTS, + ACTION_DESCRIBE_FACTS, + _ACTION_MAX, + _ACTION_INVALID = -EINVAL, +} Action; + +/* The structure for collected "metrics" or "facts". The fields + * are prefixed with just "metrics" for brevity. */ +typedef struct Context { + Action action; + sd_event *event; + Set *link_infos; + sd_json_variant **metrics; /* Collected metrics or facts for sorting */ + size_t n_metrics, n_skipped_metrics, n_invalid_metrics; + struct iovec_wrapper upload_answer; +} Context; + +int upload_collected(Context *context); diff --git a/src/resolve/meson.build b/src/resolve/meson.build index be2979343f3f0..5802889746e97 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -36,6 +36,7 @@ systemd_resolved_extract_sources = files( 'resolved-mdns.c', 'resolved-resolv-conf.c', 'resolved-socket-graveyard.c', + 'resolved-static-records.c', 'resolved-util.c', 'resolved-varlink.c', ) @@ -69,7 +70,7 @@ endif resolve_common_template = { 'dependencies' : [ libidn2_cflags, - libopenssl, + libopenssl_cflags, libm, threads, ], diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 8d887b1fcef06..1452131b59e7d 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -20,6 +20,7 @@ #include "bus-locator.h" #include "bus-message-util.h" #include "bus-util.h" +#include "crypto-util.h" #include "dns-configuration.h" #include "dns-domain.h" #include "dns-packet.h" @@ -34,7 +35,6 @@ #include "main-func.h" #include "missing-network.h" #include "netlink-util.h" -#include "openssl-util.h" #include "ordered-set.h" #include "pager.h" #include "parse-argument.h" @@ -787,7 +787,7 @@ static int resolve_rfc4501(sd_bus *bus, const char *name) { "Invalid DNS URI: %s", name); } -static int verb_query(int argc, char **argv, void *userdata) { +static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int ret = 0, r; @@ -997,7 +997,7 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons return 0; } -static int verb_service(int argc, char **argv, void *userdata) { +static int verb_service(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -1079,7 +1079,7 @@ static int resolve_openpgp(sd_bus *bus, const char *address) { } #endif -static int verb_openpgp(int argc, char **argv, void *userdata) { +static int verb_openpgp(int argc, char *argv[], uintptr_t _data, void *userdata) { #if HAVE_OPENSSL _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -1136,7 +1136,7 @@ static bool service_family_is_valid(const char *s) { return STR_IN_SET(s, "tcp", "udp", "sctp"); } -static int verb_tlsa(int argc, char **argv, void *userdata) { +static int verb_tlsa(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; const char *family = "tcp"; char **args; @@ -1163,7 +1163,7 @@ static int verb_tlsa(int argc, char **argv, void *userdata) { return ret; } -static int show_statistics(int argc, char **argv, void *userdata) { +static int verb_show_statistics(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; sd_json_variant *reply = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; @@ -1321,14 +1321,10 @@ static int show_statistics(int argc, char **argv, void *userdata) { if (r < 0) return table_log_add_error(r); - r = table_print(table, NULL); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } -static int reset_statistics(int argc, char **argv, void *userdata) { +static int verb_reset_statistics(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_json_variant *reply = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; int r; @@ -1353,7 +1349,7 @@ static int reset_statistics(int argc, char **argv, void *userdata) { return 0; } -static int flush_caches(int argc, char **argv, void *userdata) { +static int verb_flush_caches(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -1369,7 +1365,7 @@ static int flush_caches(int argc, char **argv, void *userdata) { return 0; } -static int reset_server_features(int argc, char **argv, void *userdata) { +static int verb_reset_server_features(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -1890,9 +1886,9 @@ static int print_configuration(DNSConfiguration *configuration, StatusMode mode, return table_log_add_error(r); } - r = table_print(table, NULL); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; if (empty_line) *empty_line = true; @@ -1985,7 +1981,7 @@ static int status_ifindex(int ifindex, StatusMode mode) { return status_full(mode, STRV_MAKE(ifname)); } -static int verb_status(int argc, char **argv, void *userdata) { +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { return status_full(STATUS_ALL, strv_skip(argv, 1)); } @@ -2062,7 +2058,7 @@ static int call_dns(sd_bus *bus, char **dns, const BusLocator *locator, sd_bus_e return r; } -static int verb_dns(int argc, char **argv, void *userdata) { +static int verb_dns(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -2147,7 +2143,7 @@ static int call_domain(sd_bus *bus, char **domain, const BusLocator *locator, sd return sd_bus_call(bus, req, 0, error, NULL); } -static int verb_domain(int argc, char **argv, void *userdata) { +static int verb_domain(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -2186,7 +2182,7 @@ static int verb_domain(int argc, char **argv, void *userdata) { return 0; } -static int verb_default_route(int argc, char **argv, void *userdata) { +static int verb_default_route(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r, b; @@ -2230,7 +2226,7 @@ static int verb_default_route(int argc, char **argv, void *userdata) { return 0; } -static int verb_llmnr(int argc, char **argv, void *userdata) { +static int verb_llmnr(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *global_llmnr_support_str = NULL; @@ -2288,7 +2284,7 @@ static int verb_llmnr(int argc, char **argv, void *userdata) { return 0; } -static int verb_mdns(int argc, char **argv, void *userdata) { +static int verb_mdns(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *global_mdns_support_str = NULL; @@ -2352,7 +2348,7 @@ static int verb_mdns(int argc, char **argv, void *userdata) { return 0; } -static int verb_dns_over_tls(int argc, char **argv, void *userdata) { +static int verb_dns_over_tls(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -2398,7 +2394,7 @@ static int verb_dns_over_tls(int argc, char **argv, void *userdata) { return 0; } -static int verb_dnssec(int argc, char **argv, void *userdata) { +static int verb_dnssec(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -2459,7 +2455,7 @@ static int call_nta(sd_bus *bus, char **nta, const BusLocator *locator, sd_bus_ return sd_bus_call(bus, req, 0, error, NULL); } -static int verb_nta(int argc, char **argv, void *userdata) { +static int verb_nta(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; char **args; @@ -2517,7 +2513,7 @@ static int verb_nta(int argc, char **argv, void *userdata) { return 0; } -static int verb_revert_link(int argc, char **argv, void *userdata) { +static int verb_revert_link(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -2554,7 +2550,7 @@ static int verb_revert_link(int argc, char **argv, void *userdata) { return 0; } -static int verb_log_level(int argc, char *argv[], void *userdata) { +static int verb_log_level(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -2750,7 +2746,7 @@ static int monitor_reply( return 0; } -static int verb_monitor(int argc, char *argv[], void *userdata) { +static int verb_monitor(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; int r, c; @@ -2924,7 +2920,7 @@ static int dump_cache_scope(sd_json_variant *scope) { return 0; } -static int verb_show_cache(int argc, char *argv[], void *userdata) { +static int verb_show_cache(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_json_variant *reply = NULL, *d = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; int r; @@ -3097,14 +3093,10 @@ static int dump_server_state(sd_json_variant *server) { if (r < 0) return table_log_add_error(r); - r = table_print(table, NULL); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } -static int verb_show_server_state(int argc, char *argv[], void *userdata) { +static int verb_show_server_state(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_json_variant *reply = NULL, *d = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; int r; @@ -3311,7 +3303,7 @@ static int native_help(void) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return native_help(); } @@ -3911,29 +3903,29 @@ static int native_parse_argv(int argc, char *argv[]) { static int native_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "status", VERB_ANY, VERB_ANY, VERB_DEFAULT, verb_status }, - { "query", 2, VERB_ANY, 0, verb_query }, - { "service", 2, 4, 0, verb_service }, - { "openpgp", 2, VERB_ANY, 0, verb_openpgp }, - { "tlsa", 2, VERB_ANY, 0, verb_tlsa }, - { "statistics", VERB_ANY, 1, 0, show_statistics }, - { "reset-statistics", VERB_ANY, 1, 0, reset_statistics }, - { "flush-caches", VERB_ANY, 1, 0, flush_caches }, - { "reset-server-features", VERB_ANY, 1, 0, reset_server_features }, - { "dns", VERB_ANY, VERB_ANY, 0, verb_dns }, - { "domain", VERB_ANY, VERB_ANY, 0, verb_domain }, - { "default-route", VERB_ANY, 3, 0, verb_default_route }, - { "llmnr", VERB_ANY, 3, 0, verb_llmnr }, - { "mdns", VERB_ANY, 3, 0, verb_mdns }, - { "dnsovertls", VERB_ANY, 3, 0, verb_dns_over_tls }, - { "dnssec", VERB_ANY, 3, 0, verb_dnssec }, - { "nta", VERB_ANY, VERB_ANY, 0, verb_nta }, - { "revert", VERB_ANY, 2, 0, verb_revert_link }, - { "log-level", VERB_ANY, 2, 0, verb_log_level }, - { "monitor", VERB_ANY, 1, 0, verb_monitor }, - { "show-cache", VERB_ANY, 1, 0, verb_show_cache }, - { "show-server-state", VERB_ANY, 1, 0, verb_show_server_state}, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "status", VERB_ANY, VERB_ANY, VERB_DEFAULT, verb_status }, + { "query", 2, VERB_ANY, 0, verb_query }, + { "service", 2, 4, 0, verb_service }, + { "openpgp", 2, VERB_ANY, 0, verb_openpgp }, + { "tlsa", 2, VERB_ANY, 0, verb_tlsa }, + { "statistics", VERB_ANY, 1, 0, verb_show_statistics }, + { "reset-statistics", VERB_ANY, 1, 0, verb_reset_statistics }, + { "flush-caches", VERB_ANY, 1, 0, verb_flush_caches }, + { "reset-server-features", VERB_ANY, 1, 0, verb_reset_server_features }, + { "dns", VERB_ANY, VERB_ANY, 0, verb_dns }, + { "domain", VERB_ANY, VERB_ANY, 0, verb_domain }, + { "default-route", VERB_ANY, 3, 0, verb_default_route }, + { "llmnr", VERB_ANY, 3, 0, verb_llmnr }, + { "mdns", VERB_ANY, 3, 0, verb_mdns }, + { "dnsovertls", VERB_ANY, 3, 0, verb_dns_over_tls }, + { "dnssec", VERB_ANY, 3, 0, verb_dnssec }, + { "nta", VERB_ANY, VERB_ANY, 0, verb_nta }, + { "revert", VERB_ANY, 2, 0, verb_revert_link }, + { "log-level", VERB_ANY, 2, 0, verb_log_level }, + { "monitor", VERB_ANY, 1, 0, verb_monitor }, + { "show-cache", VERB_ANY, 1, 0, verb_show_cache }, + { "show-server-state", VERB_ANY, 1, 0, verb_show_server_state }, {} }; diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 9e075277ec196..e20c975de8b38 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -1722,9 +1722,21 @@ static int bus_property_get_resolv_conf_mode( static int bus_method_reset_statistics(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = ASSERT_PTR(userdata); + int r; assert(message); + r = bus_verify_polkit_async( + message, + "org.freedesktop.resolve1.reset-statistics", + /* details= */ NULL, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + bus_client_log(message, "statistics reset"); dns_manager_reset_statistics(m); diff --git a/src/resolve/resolved-dns-browse-services.c b/src/resolve/resolved-dns-browse-services.c index f08b83cf5dcb4..68dcbee349bb5 100644 --- a/src/resolve/resolved-dns-browse-services.c +++ b/src/resolve/resolved-dns-browse-services.c @@ -441,9 +441,9 @@ int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int ow browse_service_update_event_to_string( BROWSE_SERVICE_UPDATE_REMOVED)), SD_JSON_BUILD_PAIR_INTEGER("family", owner_family), - SD_JSON_BUILD_PAIR_STRING("name", name ?: ""), - SD_JSON_BUILD_PAIR_STRING("type", type ?: ""), - SD_JSON_BUILD_PAIR_STRING("domain", domain ?: ""), + SD_JSON_BUILD_PAIR_STRING("name", strempty(name)), + SD_JSON_BUILD_PAIR_STRING("type", strempty(type)), + SD_JSON_BUILD_PAIR_STRING("domain", strempty(domain)), SD_JSON_BUILD_PAIR_INTEGER("ifindex", ifindex)); if (r < 0) { log_error_errno(r, "Failed to build JSON for removed service: %m"); diff --git a/src/resolve/resolved-dns-delegate.c b/src/resolve/resolved-dns-delegate.c index eee2daab94b94..db34916a29771 100644 --- a/src/resolve/resolved-dns-delegate.c +++ b/src/resolve/resolved-dns-delegate.c @@ -172,8 +172,8 @@ static int dns_delegate_load(Manager *m, const char *path) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "DNS delegate file name does not end in .dns-delegate, refusing: %s", fn); _cleanup_free_ char *id = strndup(fn, e - fn); - if (!string_is_safe(id)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "DNS delegate file name contains weird characters, refusing: %s", fn); + if (!string_is_safe(id, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "DNS delegate file name is invalid, refusing: %s", fn); _cleanup_free_ char *dropin_dirname = strjoin(id, ".dns-delegate.d"); if (!dropin_dirname) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index ced874e2ba9f2..017c370b0eb52 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -2,6 +2,7 @@ #include "alloc-util.h" #include "bitmap.h" +#include "crypto-util.h" #include "dns-answer.h" #include "dns-domain.h" #include "dns-rr.h" @@ -10,19 +11,12 @@ #include "log.h" #include "memory-util.h" #include "memstream-util.h" -#include "openssl-util.h" #include "resolved-dns-dnssec.h" #include "sort-util.h" #include "string-table.h" #include "string-util.h" #include "time-util.h" -#if HAVE_OPENSSL -DISABLE_WARNING_DEPRECATED_DECLARATIONS; -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(RSA*, RSA_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_KEY*, EC_KEY_free, NULL); -REENABLE_WARNING; -#endif #define VERIFY_RRS_MAX 256 #define MAX_KEY_SIZE (32*1024) @@ -48,7 +42,7 @@ REENABLE_WARNING; #if HAVE_OPENSSL static int rr_compare(DnsResourceRecord * const *a, DnsResourceRecord * const *b) { - const DnsResourceRecord *x = *a, *y = *b; + const DnsResourceRecord *x = *ASSERT_PTR(a), *y = *ASSERT_PTR(b); size_t m; int r; @@ -84,48 +78,49 @@ static int dnssec_rsa_verify_raw( assert(hash_algorithm); - e = BN_bin2bn(exponent, exponent_size, NULL); + e = sym_BN_bin2bn(exponent, exponent_size, NULL); if (!e) return -EIO; - m = BN_bin2bn(modulus, modulus_size, NULL); + m = sym_BN_bin2bn(modulus, modulus_size, NULL); if (!m) return -EIO; - rpubkey = RSA_new(); + rpubkey = sym_RSA_new(); if (!rpubkey) return -ENOMEM; - if (RSA_set0_key(rpubkey, m, e, NULL) <= 0) + if (sym_RSA_set0_key(rpubkey, m, e, NULL) <= 0) return -EIO; e = m = NULL; - assert((size_t) RSA_size(rpubkey) == signature_size); + if ((size_t) sym_RSA_size(rpubkey) != signature_size) + return -EINVAL; - epubkey = EVP_PKEY_new(); + epubkey = sym_EVP_PKEY_new(); if (!epubkey) return -ENOMEM; - if (EVP_PKEY_assign_RSA(epubkey, RSAPublicKey_dup(rpubkey)) <= 0) + if (sym_EVP_PKEY_assign_RSA(epubkey, sym_RSAPublicKey_dup(rpubkey)) <= 0) return -EIO; - ctx = EVP_PKEY_CTX_new(epubkey, NULL); + ctx = sym_EVP_PKEY_CTX_new(epubkey, NULL); if (!ctx) return -ENOMEM; - if (EVP_PKEY_verify_init(ctx) <= 0) + if (sym_EVP_PKEY_verify_init(ctx) <= 0) return -EIO; - if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) + if (sym_EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) return -EIO; - if (EVP_PKEY_CTX_set_signature_md(ctx, hash_algorithm) <= 0) + if (sym_EVP_PKEY_CTX_set_signature_md(ctx, hash_algorithm) <= 0) return -EIO; - r = EVP_PKEY_verify(ctx, signature, signature_size, data, data_size); + r = sym_EVP_PKEY_verify(ctx, signature, signature_size, data, data_size); if (r < 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "Signature verification failed: 0x%lx", ERR_get_error()); + "Signature verification failed: 0x%lx", sym_ERR_get_error()); REENABLE_WARNING; return r; @@ -206,56 +201,58 @@ static int dnssec_ecdsa_verify_raw( assert(hash_algorithm); - ec_group = EC_GROUP_new_by_curve_name(curve); + ec_group = sym_EC_GROUP_new_by_curve_name(curve); if (!ec_group) return -ENOMEM; - p = EC_POINT_new(ec_group); + p = sym_EC_POINT_new(ec_group); if (!p) return -ENOMEM; - bctx = BN_CTX_new(); + bctx = sym_BN_CTX_new(); if (!bctx) return -ENOMEM; - if (EC_POINT_oct2point(ec_group, p, key, key_size, bctx) <= 0) + if (sym_EC_POINT_oct2point(ec_group, p, key, key_size, bctx) <= 0) return -EIO; - eckey = EC_KEY_new(); + eckey = sym_EC_KEY_new(); if (!eckey) return -ENOMEM; - if (EC_KEY_set_group(eckey, ec_group) <= 0) + if (sym_EC_KEY_set_group(eckey, ec_group) <= 0) return -EIO; - if (EC_KEY_set_public_key(eckey, p) <= 0) + if (sym_EC_KEY_set_public_key(eckey, p) <= 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "EC_POINT_bn2point failed: 0x%lx", ERR_get_error()); + "EC_KEY_set_public_key failed: 0x%lx", sym_ERR_get_error()); - assert(EC_KEY_check_key(eckey) == 1); + if (sym_EC_KEY_check_key(eckey) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "EC_KEY_check_key failed: 0x%lx", sym_ERR_get_error()); - r = BN_bin2bn(signature_r, signature_r_size, NULL); + r = sym_BN_bin2bn(signature_r, signature_r_size, NULL); if (!r) return -EIO; - s = BN_bin2bn(signature_s, signature_s_size, NULL); + s = sym_BN_bin2bn(signature_s, signature_s_size, NULL); if (!s) return -EIO; /* TODO: We should eventually use the EVP API once it supports ECDSA signature verification */ - sig = ECDSA_SIG_new(); + sig = sym_ECDSA_SIG_new(); if (!sig) return -ENOMEM; - if (ECDSA_SIG_set0(sig, r, s) <= 0) + if (sym_ECDSA_SIG_set0(sig, r, s) <= 0) return -EIO; r = s = NULL; - k = ECDSA_do_verify(data, data_size, sig, eckey); + k = sym_ECDSA_do_verify(data, data_size, sig, eckey); if (k < 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "Signature verification failed: 0x%lx", ERR_get_error()); + "Signature verification failed: 0x%lx", sym_ERR_get_error()); REENABLE_WARNING; return k; @@ -323,30 +320,30 @@ static int dnssec_eddsa_verify_raw( q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */ memcpy(q+1, signature, signature_size); - evkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, key, key_size); + evkey = sym_EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, key, key_size); if (!evkey) return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "EVP_PKEY_new_raw_public_key failed: 0x%lx", ERR_get_error()); + "EVP_PKEY_new_raw_public_key failed: 0x%lx", sym_ERR_get_error()); - pctx = EVP_PKEY_CTX_new(evkey, NULL); + pctx = sym_EVP_PKEY_CTX_new(evkey, NULL); if (!pctx) return -ENOMEM; - ctx = EVP_MD_CTX_new(); + ctx = sym_EVP_MD_CTX_new(); if (!ctx) return -ENOMEM; /* This prevents EVP_DigestVerifyInit from managing pctx and complicating our free logic. */ - EVP_MD_CTX_set_pkey_ctx(ctx, pctx); + sym_EVP_MD_CTX_set_pkey_ctx(ctx, pctx); /* One might be tempted to use EVP_PKEY_verify_init, but see Ed25519(7ssl). */ - if (EVP_DigestVerifyInit(ctx, &pctx, NULL, NULL, evkey) <= 0) + if (sym_EVP_DigestVerifyInit(ctx, &pctx, NULL, NULL, evkey) <= 0) return -EIO; - r = EVP_DigestVerify(ctx, signature, signature_size, data, data_size); + r = sym_EVP_DigestVerify(ctx, signature, signature_size, data, data_size); if (r < 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "Signature verification failed: 0x%lx", ERR_get_error()); + "Signature verification failed: 0x%lx", sym_ERR_get_error()); return r; } @@ -379,12 +376,12 @@ static int dnssec_eddsa_verify( } static int md_add_uint8(EVP_MD_CTX *ctx, uint8_t v) { - return EVP_DigestUpdate(ctx, &v, sizeof(v)); + return sym_EVP_DigestUpdate(ctx, &v, sizeof(v)); } static int md_add_uint16(EVP_MD_CTX *ctx, uint16_t v) { v = htobe16(v); - return EVP_DigestUpdate(ctx, &v, sizeof(v)); + return sym_EVP_DigestUpdate(ctx, &v, sizeof(v)); } static void fwrite_uint8(FILE *fp, uint8_t v) { @@ -501,17 +498,17 @@ static const EVP_MD* algorithm_to_implementation_id(uint8_t algorithm) { case DNSSEC_ALGORITHM_RSASHA1: case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: - return EVP_sha1(); + return sym_EVP_sha1(); case DNSSEC_ALGORITHM_RSASHA256: case DNSSEC_ALGORITHM_ECDSAP256SHA256: - return EVP_sha256(); + return sym_EVP_sha256(); case DNSSEC_ALGORITHM_ECDSAP384SHA384: - return EVP_sha384(); + return sym_EVP_sha384(); case DNSSEC_ALGORITHM_RSASHA512: - return EVP_sha512(); + return sym_EVP_sha512(); default: return NULL; @@ -642,17 +639,19 @@ static int dnssec_rrset_verify_sig( if (!md_algorithm) return -EOPNOTSUPP; - _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = sym_EVP_MD_CTX_new(); if (!ctx) return -ENOMEM; - if (EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0) - return -EIO; + /* If the signature algorithm is supported by systemd-resolved but disabled by host policy, + * also return -EOPNOTSUPP. */ + if (sym_EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0) + return -EOPNOTSUPP; - if (EVP_DigestUpdate(ctx, sig_data, sig_size) <= 0) + if (sym_EVP_DigestUpdate(ctx, sig_data, sig_size) <= 0) return -EIO; - if (EVP_DigestFinal_ex(ctx, hash, &hash_size) <= 0) + if (sym_EVP_DigestFinal_ex(ctx, hash, &hash_size) <= 0) return -EIO; assert(hash_size > 0); @@ -704,6 +703,11 @@ int dnssec_verify_rrset( assert(rrsig); assert(dnskey); assert(result); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + assert(rrsig->key->type == DNS_TYPE_RRSIG); assert(dnskey->key->type == DNS_TYPE_DNSKEY); @@ -912,9 +916,6 @@ int dnssec_verify_rrset_search( DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) { DnssecResult one_result; - if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) - continue; - /* Is this a DNSKEY RR that matches they key of our RRSIG? */ r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false); if (r < 0) @@ -922,6 +923,14 @@ int dnssec_verify_rrset_search( if (r == 0) continue; + if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) { + /* An unauthenticated DNSKEY in validated_dnskeys is a key we are not able to + * authenticate, but might still be valid. Record this as an unsupported + * algorithm so we can still at least report an insecure answer. */ + found_unsupported_algorithm = true; + continue; + } + /* Take the time here, if it isn't set yet, so * that we do all validations with the same * time. */ @@ -1031,13 +1040,13 @@ static const EVP_MD* digest_to_hash_md(uint8_t algorithm) { switch (algorithm) { case DNSSEC_DIGEST_SHA1: - return EVP_sha1(); + return sym_EVP_sha1(); case DNSSEC_DIGEST_SHA256: - return EVP_sha256(); + return sym_EVP_sha256(); case DNSSEC_DIGEST_SHA384: - return EVP_sha384(); + return sym_EVP_sha384(); default: return NULL; @@ -1052,6 +1061,10 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, assert(dnskey); assert(ds); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */ if (dnskey->key->type != DNS_TYPE_DNSKEY) @@ -1082,20 +1095,22 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = NULL; uint8_t result[EVP_MAX_MD_SIZE]; - unsigned hash_size = EVP_MD_size(md_algorithm); + unsigned hash_size = sym_EVP_MD_get_size(md_algorithm); assert(hash_size > 0); if (ds->ds.digest_size != hash_size) return 0; - ctx = EVP_MD_CTX_new(); + ctx = sym_EVP_MD_CTX_new(); if (!ctx) return -ENOMEM; - if (EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0) - return -EIO; + /* If the digest is supported by systemd-resolved but disabled by host policy, also return -EOPNOTSUPP + */ + if (sym_EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0) + return -EOPNOTSUPP; - if (EVP_DigestUpdate(ctx, wire_format, encoded_length) <= 0) + if (sym_EVP_DigestUpdate(ctx, wire_format, encoded_length) <= 0) return -EIO; if (mask_revoke) @@ -1109,10 +1124,10 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, r = md_add_uint8(ctx, dnskey->dnskey.algorithm); if (r <= 0) return r; - if (EVP_DigestUpdate(ctx, dnskey->dnskey.key, dnskey->dnskey.key_size) <= 0) + if (sym_EVP_DigestUpdate(ctx, dnskey->dnskey.key, dnskey->dnskey.key_size) <= 0) return -EIO; - if (EVP_DigestFinal_ex(ctx, result, NULL) <= 0) + if (sym_EVP_DigestFinal_ex(ctx, result, NULL) <= 0) return -EIO; return memcmp(result, ds->ds.digest, ds->ds.digest_size) == 0; @@ -1121,6 +1136,7 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { DnsResourceRecord *ds; DnsAnswerFlags flags; + bool found_unsupported_algorithm = false; int r; assert(dnskey); @@ -1145,14 +1161,21 @@ int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *vali continue; r = dnssec_verify_dnskey_by_ds(dnskey, ds, false); - if (IN_SET(r, -EKEYREJECTED, -EOPNOTSUPP)) - continue; /* The DNSKEY is revoked or otherwise invalid, or we don't support the digest algorithm */ + if (r == -EKEYREJECTED) + continue; /* The DNSKEY is revoked or otherwise invalid. */ + if (r == -EOPNOTSUPP) { + found_unsupported_algorithm = true; + continue; + } if (r < 0) return r; if (r > 0) return 1; } + if (found_unsupported_algorithm) + return -EOPNOTSUPP; + return 0; } @@ -1163,7 +1186,7 @@ static const EVP_MD* nsec3_hash_to_hash_md(uint8_t algorithm) { switch (algorithm) { case NSEC3_ALGORITHM_SHA1: - return EVP_sha1(); + return sym_EVP_sha1(); default: return NULL; @@ -1178,6 +1201,10 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { assert(name); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + if (nsec3->key->type != DNS_TYPE_NSEC3) return -EINVAL; @@ -1190,41 +1217,41 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { if (!algorithm) return -EOPNOTSUPP; - size_t hash_size = EVP_MD_size(algorithm); + size_t hash_size = sym_EVP_MD_get_size(algorithm); assert(hash_size > 0); if (nsec3->nsec3.next_hashed_name_size != hash_size) return -EINVAL; - _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = sym_EVP_MD_CTX_new(); if (!ctx) return -ENOMEM; - if (EVP_DigestInit_ex(ctx, algorithm, NULL) <= 0) - return -EIO; + if (sym_EVP_DigestInit_ex(ctx, algorithm, NULL) <= 0) + return -EOPNOTSUPP; r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true); if (r < 0) return r; - if (EVP_DigestUpdate(ctx, wire_format, r) <= 0) + if (sym_EVP_DigestUpdate(ctx, wire_format, r) <= 0) return -EIO; - if (EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0) + if (sym_EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0) return -EIO; uint8_t result[EVP_MAX_MD_SIZE]; - if (EVP_DigestFinal_ex(ctx, result, NULL) <= 0) + if (sym_EVP_DigestFinal_ex(ctx, result, NULL) <= 0) return -EIO; for (unsigned k = 0; k < nsec3->nsec3.iterations; k++) { - if (EVP_DigestInit_ex(ctx, algorithm, NULL) <= 0) - return -EIO; - if (EVP_DigestUpdate(ctx, result, hash_size) <= 0) + if (sym_EVP_DigestInit_ex(ctx, algorithm, NULL) <= 0) + return -EOPNOTSUPP; + if (sym_EVP_DigestUpdate(ctx, result, hash_size) <= 0) return -EIO; - if (EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0) + if (sym_EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0) return -EIO; - if (EVP_DigestFinal_ex(ctx, result, NULL) <= 0) + if (sym_EVP_DigestFinal_ex(ctx, result, NULL) <= 0) return -EIO; } @@ -2037,6 +2064,8 @@ static int dnssec_test_positive_wildcard_nsec( bool authenticated = true; int r; + assert(_authenticated); + /* Run a positive NSEC wildcard proof. Specifically: * * A proof that there's neither a wildcard name nor a non-wildcard name that is a suffix of the name "name" and diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index a0ef750447179..6ec6569ae7639 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -21,6 +21,7 @@ #include "resolved-etc-hosts.h" #include "resolved-hook.h" #include "resolved-manager.h" +#include "resolved-static-records.h" #include "resolved-timeouts.h" #include "set.h" #include "string-util.h" @@ -910,6 +911,33 @@ static int dns_query_try_etc_hosts(DnsQuery *q) { return 1; } +static int dns_query_try_static_records(DnsQuery *q) { + int r; + + assert(q); + + if (FLAGS_SET(q->flags, SD_RESOLVED_NO_SYNTHESIZE)) + return 0; + + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + r = manager_static_records_lookup( + q->manager, + q->question_bypass ? q->question_bypass->question : q->question_utf8, + &answer); + if (r <= 0) + return r; + + dns_query_reset_answer(q); + + q->answer = TAKE_PTR(answer); + q->answer_rcode = DNS_RCODE_SUCCESS; + q->answer_protocol = dns_synthesize_protocol(q->flags); + q->answer_family = dns_synthesize_family(q->flags); + q->answer_query_flags = SD_RESOLVED_AUTHENTICATED|SD_RESOLVED_CONFIDENTIAL|SD_RESOLVED_SYNTHETIC; + + return 1; +} + static int dns_query_go_scopes(DnsQuery *q) { int r; @@ -1038,6 +1066,14 @@ int dns_query_go(DnsQuery *q) { q->state != DNS_TRANSACTION_NULL) return 0; + r = dns_query_try_static_records(q); + if (r < 0) + return r; + if (r > 0) { + dns_query_complete(q, DNS_TRANSACTION_SUCCESS); + return 1; + } + r = dns_query_try_etc_hosts(q); if (r < 0) return r; diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 2e360a8513e91..89b13b0f1d619 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -16,6 +16,7 @@ #include "errno-util.h" #include "fd-util.h" #include "hostname-util.h" +#include "json-util.h" #include "log.h" #include "random-util.h" #include "resolved-dns-browse-services.h" @@ -1814,7 +1815,7 @@ int dns_scope_to_json(DnsScope *scope, bool with_cache, sd_json_variant **ret) { return sd_json_buildo( ret, - SD_JSON_BUILD_PAIR_STRING("protocol", dns_protocol_to_string(scope->protocol)), + JSON_BUILD_PAIR_ENUM("protocol", dns_protocol_to_string(scope->protocol)), SD_JSON_BUILD_PAIR_CONDITION(scope->family != AF_UNSPEC, "family", SD_JSON_BUILD_INTEGER(scope->family)), SD_JSON_BUILD_PAIR_CONDITION(!!scope->link, "ifindex", SD_JSON_BUILD_INTEGER(dns_scope_ifindex(scope))), SD_JSON_BUILD_PAIR_CONDITION(!!scope->link, "ifname", SD_JSON_BUILD_STRING(dns_scope_ifname(scope))), diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 4f04476f2d4ac..6d1b349c4900a 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -1036,7 +1036,8 @@ DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { dns_server_unref(m->current_dns_server); m->current_dns_server = dns_server_ref(s); - if (m->unicast_scope) + /* Skip flushing the cache if server stale feature is enabled. */ + if (m->unicast_scope && m->stale_retention_usec == 0) dns_cache_flush(&m->unicast_scope->cache); (void) manager_send_changed(m, "CurrentDNSServer"); @@ -1155,6 +1156,10 @@ void dns_server_flush_cache(DnsServer *s) { if (!scope) return; + /* Skip flushing the cache if server stale feature is enabled. */ + if (s->manager->stale_retention_usec > 0) + return; + dns_cache_flush(&scope->cache); } diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index e10605538a124..b1d1b0069c028 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -347,7 +347,7 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use IOVEC_MAKE(DNS_PACKET_DATA(s->write_packet), s->write_packet->size), }; - iovec_increment(iov, ELEMENTSOF(iov), s->n_written); + iovec_inc_many(iov, ELEMENTSOF(iov), s->n_written); ssize_t ss = dns_stream_writev(s, iov, ELEMENTSOF(iov), 0); if (ss < 0) { diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index 05e5bf424d312..91ea30e0e2f4c 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -81,9 +81,9 @@ int dns_stub_listener_extra_new( Manager *m, DnsStubListenerExtra **ret) { - DnsStubListenerExtra *l; + assert(ret); - l = new(DnsStubListenerExtra, 1); + DnsStubListenerExtra *l = new(DnsStubListenerExtra, 1); if (!l) return -ENOMEM; @@ -146,7 +146,7 @@ static int stub_packet_compare_func(const DnsPacket *x, const DnsPacket *y) { return memcmp(DNS_PACKET_HEADER(x), DNS_PACKET_HEADER(y), sizeof(DnsPacketHeader)); } -DEFINE_HASH_OPS(stub_packet_hash_ops, DnsPacket, stub_packet_hash_func, stub_packet_compare_func); +DEFINE_PRIVATE_HASH_OPS(stub_packet_hash_ops, DnsPacket, stub_packet_hash_func, stub_packet_compare_func); static int reply_add_with_rrsig( DnsAnswer **reply, diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c index 8ce9204e0c6e4..52da6068cd023 100644 --- a/src/resolve/resolved-dns-synthesize.c +++ b/src/resolve/resolved-dns-synthesize.c @@ -98,6 +98,8 @@ static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, DnsAns static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex, DnsAnswerFlags flags) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + assert(answer); + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, from); if (!rr) return -ENOMEM; diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index d4a5dd8e17f02..a14aa0de7dbf6 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -2621,7 +2621,10 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { continue; /* If we were looking for the DS RR, don't request it again. */ - if (dns_transaction_key(t)->type == DNS_TYPE_DS) + r = dns_name_equal(dns_resource_key_name(dns_transaction_key(t)), dns_resource_key_name(rr->key)); + if (r < 0) + return r; + if (r > 0 && dns_transaction_key(t)->type == DNS_TYPE_DS) continue; } @@ -2836,13 +2839,18 @@ static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) { DNS_ANSWER_FOREACH_ITEM(item, t->answer) { r = dnssec_verify_dnskey_by_ds_search(item->rr, t->validated_keys); - if (r < 0) + if (r < 0 && r != -EOPNOTSUPP) return r; if (r == 0) continue; - /* If so, the DNSKEY is validated too. */ - r = dns_answer_add_extend(&t->validated_keys, item->rr, item->ifindex, item->flags|DNS_ANSWER_AUTHENTICATED, item->rrsig); + /* If so, the DNSKEY is validated too, but only mark it authenticated if the DS verification + * succeeded with a known algorithm. */ + if (r == -EOPNOTSUPP) + r = dns_answer_add_extend(&t->validated_keys, item->rr, item->ifindex, item->flags, NULL); + else + r = dns_answer_add_extend(&t->validated_keys, item->rr, item->ifindex, item->flags|DNS_ANSWER_AUTHENTICATED, item->rrsig); + if (r < 0) return r; } @@ -3259,6 +3267,12 @@ static int dns_transaction_copy_validated(DnsTransaction *t) { if (DNS_TRANSACTION_IS_LIVE(dt->state)) continue; + /* Some of the validated keys may not be authenticated, but are still useful to report + * insecure answers when the domain is signed only by unsupported algorithms. */ + r = dns_answer_extend(&t->validated_keys, dt->validated_keys); + if (r < 0) + return r; + if (!FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED)) continue; @@ -3286,7 +3300,9 @@ static int dnssec_validate_records( DnsResourceRecord *rr; int r; + assert(have_nsec); assert(nvalidations); + assert(validated); /* Returns negative on error, 0 if validation failed, 1 to restart validation, 2 when finished. */ @@ -3478,6 +3494,23 @@ static int dnssec_validate_records( /* https://datatracker.ietf.org/doc/html/rfc6840#section-5.2 */ if (result == DNSSEC_UNSUPPORTED_ALGORITHM) { + if (rr->key->type == DNS_TYPE_DNSKEY) { + /* This is a DNSKEY we cannot authenticate, but it might still be the best + * offer from the resolver. Add it to the validated keys in case it's the + * best we can find, but do not mark it as authenticated. + */ + + r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, 0, NULL); + if (r < 0) + return r; + + /* Some of the DNSKEYs we just added might already have been revoked, + * remove them again in that case. */ + r = dns_transaction_invalidate_revoked_keys(t); + if (r < 0) + return r; + } + r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0, NULL); if (r < 0) return r; diff --git a/src/resolve/resolved-dnssd.c b/src/resolve/resolved-dnssd.c index 6cc0f86796a52..498f975f39ee4 100644 --- a/src/resolve/resolved-dnssd.c +++ b/src/resolve/resolved-dnssd.c @@ -332,6 +332,8 @@ int dnssd_txt_item_new_from_string(const char *key, const char *value, DnsTxtIte size_t length; DnsTxtItem *i; + assert(ret_item); + length = strlen(key); if (!isempty(value)) @@ -357,6 +359,8 @@ int dnssd_txt_item_new_from_data(const char *key, const void *data, const size_t size_t length; DnsTxtItem *i; + assert(ret_item); + length = strlen(key); if (size > 0) diff --git a/src/resolve/resolved-dnstls.c b/src/resolve/resolved-dnstls.c index 09dda4508b81f..f59c2fa348f67 100644 --- a/src/resolve/resolved-dnstls.c +++ b/src/resolve/resolved-dnstls.c @@ -4,22 +4,21 @@ #error This source file requires DNS-over-TLS to be enabled and OpenSSL to be available. #endif -#include -#include #include #include "alloc-util.h" -#include "openssl-util.h" +#include "crypto-util.h" #include "log.h" #include "resolved-dns-server.h" #include "resolved-dns-stream.h" #include "resolved-dnstls.h" #include "resolved-manager.h" +#include "ssl-util.h" static char *dnstls_error_string(int ssl_error, char *buf, size_t count) { assert(buf || count == 0); if (ssl_error == SSL_ERROR_SSL) - ERR_error_string_n(ERR_get_error(), buf, count); + sym_ERR_error_string_n(sym_ERR_get_error(), buf, count); else snprintf(buf, count, "SSL_get_error()=%d", ssl_error); return buf; @@ -54,7 +53,7 @@ static int dnstls_flush_write_buffer(DnsStream *stream) { stream->dnstls_events |= EPOLLOUT; return -EAGAIN; } else { - BIO_reset(SSL_get_wbio(stream->dnstls_data.ssl)); + sym_BIO_reset(sym_SSL_get_wbio(stream->dnstls_data.ssl)); stream->dnstls_data.buffer_offset = 0; } } @@ -72,55 +71,55 @@ int dnstls_stream_connect_tls(DnsStream *stream, DnsServer *server) { assert(stream->manager); assert(server); - rb = BIO_new_socket(stream->fd, 0); + rb = sym_BIO_new_socket(stream->fd, 0); if (!rb) return -ENOMEM; - wb = BIO_new(BIO_s_mem()); + wb = sym_BIO_new(sym_BIO_s_mem()); if (!wb) return -ENOMEM; - BIO_get_mem_ptr(wb, &stream->dnstls_data.write_buffer); + sym_BIO_get_mem_ptr(wb, &stream->dnstls_data.write_buffer); stream->dnstls_data.buffer_offset = 0; - s = SSL_new(stream->manager->dnstls_data.ctx); + s = sym_SSL_new(stream->manager->dnstls_data.ctx); if (!s) return -ENOMEM; - SSL_set_connect_state(s); - r = SSL_set_session(s, server->dnstls_data.session); + sym_SSL_set_connect_state(s); + r = sym_SSL_set_session(s, server->dnstls_data.session); if (r == 0) return -EIO; - SSL_set_bio(s, TAKE_PTR(rb), TAKE_PTR(wb)); + sym_SSL_set_bio(s, TAKE_PTR(rb), TAKE_PTR(wb)); if (server->manager->dns_over_tls_mode == DNS_OVER_TLS_YES) { X509_VERIFY_PARAM *v; - SSL_set_verify(s, SSL_VERIFY_PEER, NULL); - v = SSL_get0_param(s); + sym_SSL_set_verify(s, SSL_VERIFY_PEER, NULL); + v = sym_SSL_get0_param(s); if (server->server_name) { - X509_VERIFY_PARAM_set_hostflags(v, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); - if (X509_VERIFY_PARAM_set1_host(v, server->server_name, 0) == 0) + sym_X509_VERIFY_PARAM_set_hostflags(v, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + if (sym_X509_VERIFY_PARAM_set1_host(v, server->server_name, 0) == 0) return -ECONNREFUSED; } else { const unsigned char *ip; ip = server->family == AF_INET ? (const unsigned char*) &server->address.in.s_addr : server->address.in6.s6_addr; - if (X509_VERIFY_PARAM_set1_ip(v, ip, FAMILY_ADDRESS_SIZE(server->family)) == 0) + if (sym_X509_VERIFY_PARAM_set1_ip(v, ip, FAMILY_ADDRESS_SIZE(server->family)) == 0) return -ECONNREFUSED; } } if (server->server_name) { - r = SSL_set_tlsext_host_name(s, server->server_name); + r = sym_SSL_set_tlsext_host_name(s, server->server_name); if (r <= 0) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set server name: %s", DNSTLS_ERROR_STRING(SSL_ERROR_SSL)); } - ERR_clear_error(); - stream->dnstls_data.handshake = SSL_do_handshake(s); + sym_ERR_clear_error(); + stream->dnstls_data.handshake = sym_SSL_do_handshake(s); if (stream->dnstls_data.handshake <= 0) { - error = SSL_get_error(s, stream->dnstls_data.handshake); + error = sym_SSL_get_error(s, stream->dnstls_data.handshake); if (!IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) return log_debug_errno(SYNTHETIC_ERRNO(ECONNREFUSED), "Failed to invoke SSL_do_handshake: %s", DNSTLS_ERROR_STRING(error)); @@ -131,7 +130,7 @@ int dnstls_stream_connect_tls(DnsStream *stream, DnsServer *server) { r = dnstls_flush_write_buffer(stream); if (r < 0 && r != -EAGAIN) { - SSL_free(TAKE_PTR(stream->dnstls_data.ssl)); + sym_SSL_free(TAKE_PTR(stream->dnstls_data.ssl)); return r; } @@ -143,7 +142,7 @@ void dnstls_stream_free(DnsStream *stream) { assert(stream->encrypted); if (stream->dnstls_data.ssl) - SSL_free(stream->dnstls_data.ssl); + sym_SSL_free(stream->dnstls_data.ssl); } int dnstls_stream_on_io(DnsStream *stream, uint32_t revents) { @@ -161,8 +160,8 @@ int dnstls_stream_on_io(DnsStream *stream, uint32_t revents) { } if (stream->dnstls_data.shutdown) { - ERR_clear_error(); - r = SSL_shutdown(stream->dnstls_data.ssl); + sym_ERR_clear_error(); + r = sym_SSL_shutdown(stream->dnstls_data.ssl); if (r == 0) { stream->dnstls_events = 0; @@ -172,7 +171,7 @@ int dnstls_stream_on_io(DnsStream *stream, uint32_t revents) { return -EAGAIN; } else if (r < 0) { - error = SSL_get_error(stream->dnstls_data.ssl, r); + error = sym_SSL_get_error(stream->dnstls_data.ssl, r); if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; @@ -198,10 +197,10 @@ int dnstls_stream_on_io(DnsStream *stream, uint32_t revents) { dns_stream_unref(stream); return DNSTLS_STREAM_CLOSED; } else if (stream->dnstls_data.handshake <= 0) { - ERR_clear_error(); - stream->dnstls_data.handshake = SSL_do_handshake(stream->dnstls_data.ssl); + sym_ERR_clear_error(); + stream->dnstls_data.handshake = sym_SSL_do_handshake(stream->dnstls_data.ssl); if (stream->dnstls_data.handshake <= 0) { - error = SSL_get_error(stream->dnstls_data.ssl, stream->dnstls_data.handshake); + error = sym_SSL_get_error(stream->dnstls_data.ssl, stream->dnstls_data.handshake); if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; r = dnstls_flush_write_buffer(stream); @@ -233,18 +232,18 @@ int dnstls_stream_shutdown(DnsStream *stream, int error) { assert(stream->dnstls_data.ssl); if (stream->server) { - s = SSL_get1_session(stream->dnstls_data.ssl); + s = sym_SSL_get1_session(stream->dnstls_data.ssl); if (s) { if (stream->server->dnstls_data.session) - SSL_SESSION_free(stream->server->dnstls_data.session); + sym_SSL_SESSION_free(stream->server->dnstls_data.session); stream->server->dnstls_data.session = s; } } if (error == ETIMEDOUT) { - ERR_clear_error(); - r = SSL_shutdown(stream->dnstls_data.ssl); + sym_ERR_clear_error(); + r = sym_SSL_shutdown(stream->dnstls_data.ssl); if (r == 0) { if (!stream->dnstls_data.shutdown) { stream->dnstls_data.shutdown = true; @@ -259,7 +258,7 @@ int dnstls_stream_shutdown(DnsStream *stream, int error) { return -EAGAIN; } else if (r < 0) { - ssl_error = SSL_get_error(stream->dnstls_data.ssl, r); + ssl_error = sym_SSL_get_error(stream->dnstls_data.ssl, r); if (IN_SET(ssl_error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { stream->dnstls_events = ssl_error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; r = dnstls_flush_write_buffer(stream); @@ -291,10 +290,10 @@ static ssize_t dnstls_stream_write(DnsStream *stream, const char *buf, size_t co int error, r; ssize_t ss; - ERR_clear_error(); - ss = r = SSL_write(stream->dnstls_data.ssl, buf, count); + sym_ERR_clear_error(); + ss = r = sym_SSL_write(stream->dnstls_data.ssl, buf, count); if (r <= 0) { - error = SSL_get_error(stream->dnstls_data.ssl, r); + error = sym_SSL_get_error(stream->dnstls_data.ssl, r); if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; ss = -EAGAIN; @@ -317,26 +316,30 @@ static ssize_t dnstls_stream_write(DnsStream *stream, const char *buf, size_t co } ssize_t dnstls_stream_writev(DnsStream *stream, const struct iovec *iov, size_t iovcnt) { - _cleanup_free_ char *buf = NULL; - size_t count; - assert(stream); assert(stream->encrypted); assert(stream->dnstls_data.ssl); assert(iov); - assert(iovec_total_size(iov, iovcnt) > 0); + + size_t size = iovec_total_size(iov, iovcnt); + if (size == 0) + return -EINVAL; + if (size == SIZE_MAX) + return -ENOBUFS; if (iovcnt == 1) return dnstls_stream_write(stream, iov[0].iov_base, iov[0].iov_len); /* As of now, OpenSSL cannot accumulate multiple writes, so join into a single buffer. Suboptimal, but better than multiple SSL_write calls. */ - count = iovec_total_size(iov, iovcnt); - buf = new(char, count); + _cleanup_free_ char *buf = new(char, size); + if (!buf) + return -ENOMEM; + for (size_t i = 0, pos = 0; i < iovcnt; pos += iov[i].iov_len, i++) memcpy(buf + pos, iov[i].iov_base, iov[i].iov_len); - return dnstls_stream_write(stream, buf, count); + return dnstls_stream_write(stream, buf, size); } ssize_t dnstls_stream_read(DnsStream *stream, void *buf, size_t count) { @@ -348,10 +351,10 @@ ssize_t dnstls_stream_read(DnsStream *stream, void *buf, size_t count) { assert(stream->dnstls_data.ssl); assert(buf); - ERR_clear_error(); - ss = r = SSL_read(stream->dnstls_data.ssl, buf, count); + sym_ERR_clear_error(); + ss = r = sym_SSL_read(stream->dnstls_data.ssl, buf, count); if (r <= 0) { - error = SSL_get_error(stream->dnstls_data.ssl, r); + error = sym_SSL_get_error(stream->dnstls_data.ssl, r); if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { /* If we receive SSL_ERROR_WANT_READ here, there are two possible scenarios: * OpenSSL needs to renegotiate (so we want to get an EPOLLIN event), or @@ -386,7 +389,7 @@ void dnstls_server_free(DnsServer *server) { assert(server); if (server->dnstls_data.session) - SSL_SESSION_free(server->dnstls_data.session); + sym_SSL_SESSION_free(server->dnstls_data.session); } int dnstls_manager_init(Manager *manager) { @@ -394,25 +397,33 @@ int dnstls_manager_init(Manager *manager) { assert(manager); - manager->dnstls_data.ctx = SSL_CTX_new(TLS_client_method()); + r = dlopen_libcrypto(LOG_WARNING); + if (r < 0) + return r; + + r = dlopen_libssl(LOG_WARNING); + if (r < 0) + return r; + + manager->dnstls_data.ctx = sym_SSL_CTX_new(sym_TLS_client_method()); if (!manager->dnstls_data.ctx) return log_warning_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to create SSL context: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - r = SSL_CTX_set_min_proto_version(manager->dnstls_data.ctx, TLS1_2_VERSION); + r = sym_SSL_CTX_set_min_proto_version(manager->dnstls_data.ctx, TLS1_2_VERSION); if (r == 0) return log_warning_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set protocol version on SSL context: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - (void) SSL_CTX_set_options(manager->dnstls_data.ctx, SSL_OP_NO_COMPRESSION); + (void) sym_SSL_CTX_set_options(manager->dnstls_data.ctx, SSL_OP_NO_COMPRESSION); - r = SSL_CTX_set_default_verify_paths(manager->dnstls_data.ctx); + r = sym_SSL_CTX_set_default_verify_paths(manager->dnstls_data.ctx); if (r == 0) return log_warning_errno(SYNTHETIC_ERRNO(EIO), "Failed to load system trust store: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); return 0; } @@ -420,5 +431,5 @@ void dnstls_manager_free(Manager *manager) { assert(manager); if (manager->dnstls_data.ctx) - SSL_CTX_free(manager->dnstls_data.ctx); + sym_SSL_CTX_free(manager->dnstls_data.ctx); } diff --git a/src/resolve/resolved-dnstls.h b/src/resolve/resolved-dnstls.h index 35a8d785defa1..b87b84a0d5cad 100644 --- a/src/resolve/resolved-dnstls.h +++ b/src/resolve/resolved-dnstls.h @@ -7,8 +7,6 @@ #error This source file requires OpenSSL to be available. #endif -#include - #include "resolved-forward.h" typedef struct DnsTlsManagerData { diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c index e9100de5229d0..b38c011e7c6e7 100644 --- a/src/resolve/resolved-etc-hosts.c +++ b/src/resolve/resolved-etc-hosts.c @@ -72,6 +72,8 @@ void etc_hosts_clear(EtcHosts *hosts) { void manager_etc_hosts_flush(Manager *m) { etc_hosts_clear(&m->etc_hosts); m->etc_hosts_stat = (struct stat) {}; + /* NB: We do not reset m->etc_hosts_last here, because manager_etc_hosts_read() calls us and needs it + * to stay in effect for the reload suppression to work */ } static int parse_line(EtcHosts *hosts, unsigned nr, const char *line) { diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index c548320449b6f..8b8a66d0369bf 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -31,6 +31,7 @@ Resolve.DNSOverTLS, config_parse_dns_over_tls_mode, 0, Resolve.Cache, config_parse_dns_cache_mode, DNS_CACHE_MODE_YES, offsetof(Manager, enable_cache) Resolve.DNSStubListener, config_parse_dns_stub_listener_mode, 0, offsetof(Manager, dns_stub_listener_mode) Resolve.ReadEtcHosts, config_parse_bool, 0, offsetof(Manager, read_etc_hosts) +Resolve.ReadStaticRecords, config_parse_bool, 0, offsetof(Manager, read_static_records) Resolve.ResolveUnicastSingleLabel, config_parse_bool, 0, offsetof(Manager, resolve_unicast_single_label) Resolve.DNSStubListenerExtra, config_parse_dns_stub_listener_extra, 0, offsetof(Manager, dns_extra_stub_listeners) Resolve.CacheFromLocalhost, config_parse_bool, 0, offsetof(Manager, cache_from_localhost) diff --git a/src/resolve/resolved-hook.c b/src/resolve/resolved-hook.c index 6940b7ef1450b..9625e64fe25c7 100644 --- a/src/resolve/resolved-hook.c +++ b/src/resolve/resolved-hook.c @@ -49,7 +49,7 @@ static Hook* hook_free(Hook *h) { if (!h) return NULL; - mfree(h->socket_path); + free(h->socket_path); sd_varlink_unref(h->filter_link); set_free(h->idle_links); @@ -328,7 +328,7 @@ static void manager_gc_hooks(Manager *m, usec_t seen_usec) { } } -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( hook_hash_ops, char, string_hash_func, string_compare_func, Hook, hook_unlink); @@ -391,19 +391,6 @@ static int manager_hook_discover(Manager *m) { usec_t seen_usec = now(CLOCK_MONOTONIC); - struct stat st; - if (stat(dp, &st) < 0) { - if (errno == ENOENT) - r = 0; - else - r = log_warning_errno(errno, "Failed to stat %s/: %m", dp); - - goto finish; - } - - if (stat_inode_unmodified(&st, &m->hook_stat)) - return 0; - d = opendir(dp); if (!d) { if (errno == ENOENT) @@ -414,6 +401,15 @@ static int manager_hook_discover(Manager *m) { goto finish; } + struct stat st; + if (fstat(dirfd(d), &st) < 0) { + r = log_warning_errno(errno, "Failed to fstat %s/: %m", dp); + goto finish; + } + + if (stat_inode_unmodified(&st, &m->hook_stat)) + return 0; + for (;;) { errno = 0; struct dirent *de = readdir_no_dot(d); diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 59b6dc942b98e..ea089c1e100c7 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -890,6 +890,7 @@ int link_address_new(Link *l, assert(l); assert(in_addr); + assert(in_addr_broadcast); a = new(LinkAddress, 1); if (!a) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index a0fb74ec3567a..0a52e922c4ff5 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -49,6 +49,7 @@ #include "resolved-mdns.h" #include "resolved-resolv-conf.h" #include "resolved-socket-graveyard.h" +#include "resolved-static-records.h" #include "resolved-util.h" #include "resolved-varlink.h" #include "set.h" @@ -637,6 +638,7 @@ static void manager_set_defaults(Manager *m) { m->enable_cache = DNS_CACHE_MODE_YES; m->dns_stub_listener_mode = DNS_STUB_LISTENER_YES; m->read_etc_hosts = true; + m->read_static_records = true; m->resolve_unicast_single_label = false; m->cache_from_localhost = false; m->stale_retention_usec = 0; @@ -659,6 +661,11 @@ static int manager_dispatch_reload_signal(sd_event_source *s, const struct signa m->unicast_scope = dns_scope_free(m->unicast_scope); m->delegates = hashmap_free(m->delegates); dns_trust_anchor_flush(&m->trust_anchor); + manager_etc_hosts_flush(m); + manager_static_records_flush(m); + + m->etc_hosts_last = USEC_INFINITY; + m->static_records_last = USEC_INFINITY; manager_set_defaults(m); @@ -729,6 +736,7 @@ int manager_new(Manager **ret) { .read_resolv_conf = true, .need_builtin_fallbacks = true, .etc_hosts_last = USEC_INFINITY, + .static_records_last = USEC_INFINITY, .sigrtmin18_info.memory_pressure_handler = manager_memory_pressure, .sigrtmin18_info.memory_pressure_userdata = m, @@ -917,6 +925,7 @@ Manager* manager_free(Manager *m) { dns_trust_anchor_flush(&m->trust_anchor); manager_etc_hosts_flush(m); + manager_static_records_flush(m); while ((sb = hashmap_first(m->dns_service_browsers))) dns_service_browser_free(sb); @@ -1455,6 +1464,8 @@ static int manager_next_random_name(const char *old, char **ret_new) { uint64_t u, a; char *n; + assert(ret_new); + p = strchr(old, 0); assert(p); @@ -2145,9 +2156,9 @@ static int dns_configuration_json_append( JSON_BUILD_PAIR_CONDITION_BOOLEAN(dnssec_mode >= 0, "dnssecSupported", dnssec_supported), JSON_BUILD_PAIR_STRING_NON_EMPTY("dnssec", dnssec_mode_to_string(dnssec_mode)), JSON_BUILD_PAIR_STRING_NON_EMPTY("dnsOverTLS", dns_over_tls_mode_to_string(dns_over_tls_mode)), - JSON_BUILD_PAIR_STRING_NON_EMPTY("llmnr", resolve_support_to_string(llmnr_support)), - JSON_BUILD_PAIR_STRING_NON_EMPTY("mDNS", resolve_support_to_string(mdns_support)), - JSON_BUILD_PAIR_STRING_NON_EMPTY("resolvConfMode", resolv_conf_mode_to_string(resolv_conf_mode)), + JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY("llmnr", resolve_support_to_string(llmnr_support)), + JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY("mDNS", resolve_support_to_string(mdns_support)), + JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY("resolvConfMode", resolv_conf_mode_to_string(resolv_conf_mode)), JSON_BUILD_PAIR_VARIANT_NON_NULL("scopes", scopes_json)); } diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 4f595e6d04c24..d72e9104d79d0 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -123,6 +123,12 @@ typedef struct Manager { struct stat etc_hosts_stat; bool read_etc_hosts; + /* Data from {/etc,/run,/usr/local/lib,/usr/lib}/systemd/resolve/static.d/ */ + Hashmap *static_records; + usec_t static_records_last; + Set *static_records_stat; + bool read_static_records; + /* List of refused DNS Record Types */ Set *refuse_record_types; diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index 5026b10ff4c8b..fb20ba9cd0286 100644 --- a/src/resolve/resolved-mdns.c +++ b/src/resolve/resolved-mdns.c @@ -413,6 +413,14 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us if (r <= 0) return r; + /* Refuse traffic from the local host, to avoid query loops. However, allow legacy mDNS + * unicast queries through anyway (we never send those ourselves, hence no risk). + * i.e. check for the source port nr. */ + if (p->sender_port == MDNS_PORT && manager_packet_from_local_address(m, p)) { + log_debug("Got mDNS UDP packet from local host, ignoring."); + return 0; + } + scope = manager_find_scope(m, p); if (!scope) { log_debug("Got mDNS UDP packet on unknown scope. Ignoring."); @@ -529,14 +537,6 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us if (unsolicited_packet) mdns_notify_browsers_unsolicited_updates(m, p->answer, p->family); } else if (dns_packet_validate_query(p) > 0) { - /* Refuse traffic from the local host, to avoid query loops. However, allow legacy mDNS - * unicast queries through anyway (we never send those ourselves, hence no risk). - * i.e. check for the source port nr. */ - if (p->sender_port == MDNS_PORT && manager_packet_from_local_address(m, p)) { - log_debug("Got mDNS UDP packet from local host, ignoring."); - return 0; - } - log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); r = mdns_scope_process_query(scope, p); diff --git a/src/resolve/resolved-static-records.c b/src/resolve/resolved-static-records.c new file mode 100644 index 0000000000000..efcd91c0907d5 --- /dev/null +++ b/src/resolve/resolved-static-records.c @@ -0,0 +1,227 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "alloc-util.h" +#include "conf-files.h" +#include "constants.h" +#include "dns-answer.h" +#include "dns-domain.h" +#include "dns-question.h" +#include "dns-rr.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "hashmap.h" +#include "json-util.h" +#include "log.h" +#include "resolved-manager.h" +#include "resolved-static-records.h" +#include "set.h" +#include "stat-util.h" + +/* This implements a mechanism to extend what systemd-resolved resolves locally, via .rr drop-ins in + * {/etc,/run,/usr/local/lib,/usr/lib}/systemd/resolve/static.d/. These files are in JSON format, and are RR + * serializations, that match the usual way we serialize RRs to JSON. + * + * Note that this deliberately doesn't use the (probably more user-friendly) classic DNS zone file format, + * to keep things a bit simpler, and symmetric to the places we currently already generate JSON + * serializations of DNS RRs. Also note the semantics are different from DNS zone file format, for example + * regarding delegation (i.e. the RRs defined here have no effect on subdomains), which is probably nicer for + * one-off mappings of domains to specific resources. Or in other words, this is supposed to be a drop-in + * based alternative to /etc/hosts, not one to DNS zone files. (The JSON format is also a lot more + * extensible to us, for example we could teach it to map certain lookups to specific DNS errors, or extend + * it so that subdomains always get NXDOMAIN or similar). + * + * (That said, if there's a good reason, we can also support *.zone files too one day). + */ + +/* Recheck static records at most once every 2s */ +#define STATIC_RECORDS_RECHECK_USEC (2*USEC_PER_SEC) + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + answer_by_name_hash_ops, + char, + dns_name_hash_func, + dns_name_compare_func, + DnsAnswer, + dns_answer_unref); + +static int load_static_record_file_item(sd_json_variant *rj, Hashmap **records) { + int r; + + assert(records); + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + r = dns_resource_record_from_json(rj, &rr); + if (r < 0) + return log_error_errno(r, "Failed to parse DNS record from JSON: %m"); + + _cleanup_(dns_answer_unrefp) DnsAnswer *a = + hashmap_remove(*records, dns_resource_key_name(rr->key)); + + r = dns_answer_add_extend_full(&a, rr, /* ifindex= */ 0, DNS_ANSWER_AUTHENTICATED, /* rrsig= */ NULL, /* until= */ USEC_INFINITY); + if (r < 0) + return log_error_errno(r, "Failed to append RR to DNS answer: %m"); + + DnsAnswerItem *item = ASSERT_PTR(ordered_set_first(a->items)); + + r = hashmap_ensure_put(records, &answer_by_name_hash_ops, dns_resource_key_name(item->rr->key), a); + if (r < 0) + return log_error_errno(r, "Failed to add RR to static record set: %m"); + + TAKE_PTR(a); + + log_debug("Added static resource record: %s", dns_resource_record_to_string(rr)); + return 1; +} + +static int load_static_record_file(const ConfFile *cf, Hashmap **records, Set **stats) { + int r; + + assert(cf); + assert(records); + assert(stats); + + /* Have we seen this file before? Then we might as well skip loading it again, it wouldn't have any + * additional effect anyway. (Note: masking/overriding has already been applied before we reach this + * point, here everything is purely additive.) */ + if (set_contains(*stats, &cf->st)) + return 0; + + _cleanup_free_ struct stat *st_copy = memdup(&cf->st, sizeof(cf->st)); + if (!st_copy) + return log_oom(); + + if (set_ensure_consume(stats, &inode_unmodified_hash_ops, TAKE_PTR(st_copy)) < 0) + return log_oom(); + + _cleanup_fclose_ FILE *f = NULL; + r = xfopenat(cf->fd, /* path= */ NULL, "re", /* open_flags= */ 0, &f); + if (r < 0) { + log_warning_errno(r, "Failed to open '%s', skipping: %m", cf->result); + return 0; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + unsigned line = 0, column = 0; + r = sd_json_parse_file(f, cf->result, /* flags= */ 0, &j, &line, &column); + if (r < 0) { + if (line > 0) + log_syntax(/* unit= */ NULL, LOG_WARNING, cf->result, line, r, "Failed to parse JSON, skipping: %m"); + else + log_warning_errno(r, "Failed to parse JSON file '%s', skipping: %m", cf->result); + return 0; + } + + if (sd_json_variant_is_array(j)) { + sd_json_variant *i; + int ret = 0; + JSON_VARIANT_ARRAY_FOREACH(i, j) + RET_GATHER(ret, load_static_record_file_item(i, records)); + if (ret < 0) + return ret; + } else if (sd_json_variant_is_object(j)) { + r = load_static_record_file_item(j, records); + if (r < 0) + return r; + } else { + log_warning("JSON file '%s' contains neither array nor object, skipping.", cf->result); + return 0; + } + + return 1; +} + +static int manager_static_records_read(Manager *m) { + int r; + + usec_t ts; + assert_se(sd_event_now(m->event, CLOCK_BOOTTIME, &ts) >= 0); + + /* See if we checked the static records db recently already */ + if (m->static_records_last != USEC_INFINITY && usec_add(m->static_records_last, STATIC_RECORDS_RECHECK_USEC) > ts) + return 0; + + m->static_records_last = ts; + + ConfFile **files = NULL; + size_t n_files = 0; + CLEANUP_ARRAY(files, n_files, conf_file_free_array); + + r = conf_files_list_nulstr_full( + ".rr", + /* root= */ NULL, + CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN, + CONF_PATHS_NULSTR("systemd/resolve/static.d/"), + &files, + &n_files); + if (r < 0) + return log_error_errno(r, "Failed to enumerate static record drop-ins: %m"); + + /* Let's suppress reloads if nothing changed. For that keep the set of inodes from the previous + * reload around, and see if there are any changes on them. */ + bool reload; + if (set_size(m->static_records_stat) != n_files) + reload = true; + else { + reload = false; + FOREACH_ARRAY(f, files, n_files) + if (!set_contains(m->static_records_stat, &(*f)->st)) { + reload = true; + break; + } + } + + if (!reload) { + log_debug("No static record files changed, not re-reading."); + return 0; + } + + _cleanup_(hashmap_freep) Hashmap *records = NULL; + _cleanup_(set_freep) Set *stats = NULL; + FOREACH_ARRAY(f, files, n_files) + (void) load_static_record_file(*f, &records, &stats); + + hashmap_free(m->static_records); + m->static_records = TAKE_PTR(records); + + set_free(m->static_records_stat); + m->static_records_stat = TAKE_PTR(stats); + + return 0; +} + +int manager_static_records_lookup(Manager *m, DnsQuestion *q, DnsAnswer **answer) { + int r; + + assert(m); + assert(q); + assert(answer); + + if (!m->read_static_records) + return 0; + + (void) manager_static_records_read(m); + + const char *n = dns_question_first_name(q); + if (!n) + return 0; + + DnsAnswer *f = hashmap_get(m->static_records, n); + if (!f) + return 0; + + r = dns_answer_extend(answer, f); + if (r < 0) + return r; + + return 1; +} + +void manager_static_records_flush(Manager *m) { + assert(m); + + m->static_records = hashmap_free(m->static_records); + m->static_records_stat = set_free(m->static_records_stat); +} diff --git a/src/resolve/resolved-static-records.h b/src/resolve/resolved-static-records.h new file mode 100644 index 0000000000000..f50c70ef459a6 --- /dev/null +++ b/src/resolve/resolved-static-records.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "resolved-forward.h" + +void manager_static_records_flush(Manager *m); +int manager_static_records_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer); diff --git a/src/resolve/resolved-util.c b/src/resolve/resolved-util.c index eec28197676c9..094ef00698513 100644 --- a/src/resolve/resolved-util.c +++ b/src/resolve/resolved-util.c @@ -36,7 +36,7 @@ int resolve_system_hostname(char **full_hostname, char **first_label) { #if HAVE_LIBIDN2 _cleanup_free_ char *utf8 = NULL; - if (dlopen_idn() >= 0) { + if (dlopen_idn(LOG_DEBUG) >= 0) { r = sym_idn2_to_unicode_8z8z(label, &utf8, 0); if (r != IDN2_OK) return log_debug_errno(SYNTHETIC_ERRNO(EUCLEAN), diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index bb0100b04688d..2ac4ee9555d01 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -211,6 +211,9 @@ static int find_addr_records( DnsResourceRecord *rr; int ifindex, r; + assert(q); + POINTER_MAY_BE_NULL(canonical); + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL; int family; diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in index 656bc7c0eb7ea..147d30845b129 100644 --- a/src/resolve/resolved.conf.in +++ b/src/resolve/resolved.conf.in @@ -39,6 +39,7 @@ #DNSStubListener=yes #DNSStubListenerExtra= #ReadEtcHosts=yes +#ReadStaticRecords=yes #ResolveUnicastSingleLabel=no #StaleRetentionSec=0 #RefuseRecordTypes= diff --git a/src/resolve/test-dns-packet-append.c b/src/resolve/test-dns-packet-append.c index eaffc57184e96..862f3bea69692 100644 --- a/src/resolve/test-dns-packet-append.c +++ b/src/resolve/test-dns-packet-append.c @@ -1180,7 +1180,7 @@ static DnsSvcParam* add_svcb_param(DnsResourceRecord *rr, uint16_t key, const ch param->key = key; param->length = len; - if (value != NULL) + if (value) memcpy(param->value, value, len); LIST_APPEND(params, rr->svcb.params, param); diff --git a/src/resolve/test-dns-packet-extract.c b/src/resolve/test-dns-packet-extract.c index dfbfd832f2b86..ca90afb7eb1dc 100644 --- a/src/resolve/test-dns-packet-extract.c +++ b/src/resolve/test-dns-packet-extract.c @@ -3902,7 +3902,7 @@ static DnsSvcParam* add_svcb_param(DnsResourceRecord *rr, uint16_t key, const ch param->key = key; param->length = len; - if (value != NULL) + if (value) memcpy(param->value, value, len); LIST_APPEND(params, rr->svcb.params, param); diff --git a/src/resolve/test-dns-query.c b/src/resolve/test-dns-query.c index 3fcc93d9e4fab..9c1e2a73a55a9 100644 --- a/src/resolve/test-dns-query.c +++ b/src/resolve/test-dns-query.c @@ -858,7 +858,7 @@ static void exercise_dns_query_go(GoConfig *cfg, void (*check_query)(DnsQuery *q ASSERT_NOT_NULL(query); ASSERT_TRUE(dns_query_go(query)); - if (check_query != NULL) + if (check_query) check_query(query); } diff --git a/src/resolve/test-dns-rr.c b/src/resolve/test-dns-rr.c index c679e45beeb94..2ded6b0ab96f9 100644 --- a/src/resolve/test-dns-rr.c +++ b/src/resolve/test-dns-rr.c @@ -7,6 +7,16 @@ #include "dns-type.h" #include "tests.h" +static void test_to_json_from_json(DnsResourceRecord *rr) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + ASSERT_OK(dns_resource_record_to_json(rr, &j)); + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr2 = NULL; + ASSERT_OK(dns_resource_record_from_json(j, &rr2)); + + ASSERT_TRUE(dns_resource_record_equal(rr, rr2)); +} + /* ================================================================ * DNS_RESOURCE_RECORD_RDATA() * ================================================================ */ @@ -802,6 +812,8 @@ TEST(dns_resource_record_new_address_ipv4) { ASSERT_EQ(rr->key->type, DNS_TYPE_A); ASSERT_STREQ(dns_resource_key_name(rr->key), "www.example.com"); ASSERT_EQ(rr->a.in_addr.s_addr, addr.in.s_addr); + + test_to_json_from_json(rr); } TEST(dns_resource_record_new_address_ipv6) { @@ -818,6 +830,8 @@ TEST(dns_resource_record_new_address_ipv6) { ASSERT_EQ(rr->key->type, DNS_TYPE_AAAA); ASSERT_STREQ(dns_resource_key_name(rr->key), "www.example.com"); ASSERT_EQ(memcmp(&rr->aaaa.in6_addr, &addr.in6, sizeof(struct in6_addr)), 0); + + test_to_json_from_json(rr); } /* ================================================================ @@ -1003,11 +1017,13 @@ TEST(dns_resource_record_equal_cname_copy) { a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_CNAME, "www.example.com"); ASSERT_NOT_NULL(a); - a->cname.name = strdup("example.com"); + a->cname.name = ASSERT_PTR(strdup("example.com")); b = dns_resource_record_copy(a); ASSERT_NOT_NULL(b); ASSERT_TRUE(dns_resource_record_equal(a, b)); + + test_to_json_from_json(a); } TEST(dns_resource_record_equal_cname_fail) { @@ -1220,11 +1236,13 @@ TEST(dns_resource_record_equal_ptr_copy) { a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, "127.1.168.192.in-addr-arpa"); ASSERT_NOT_NULL(a); - a->ptr.name = strdup("example.com"); + a->ptr.name = ASSERT_PTR(strdup("example.com")); b = dns_resource_record_copy(a); ASSERT_NOT_NULL(b); ASSERT_TRUE(dns_resource_record_equal(a, b)); + + test_to_json_from_json(a); } TEST(dns_resource_record_equal_ptr_fail) { @@ -2043,7 +2061,7 @@ static DnsSvcParam* add_svcb_param(DnsResourceRecord *rr, uint16_t key, const ch param->key = key; param->length = len; - if (value != NULL) + if (value) memcpy(param->value, value, len); LIST_APPEND(params, rr->svcb.params, param); @@ -2461,4 +2479,25 @@ TEST(dns_resource_record_clamp_ttl_copy) { ASSERT_EQ(orig->ttl, 3600u); } +static void test_from_json(const char *text, int expected) { + log_notice("Trying to parse as JSON RR: %s", text); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + ASSERT_OK(sd_json_parse(text, /* flags= */ 0, &j, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_EQ(dns_resource_record_from_json(j, NULL), expected); +} + +TEST(from_bad_json) { + test_from_json("{}", -EBADMSG); + test_from_json("{\"key\":{}}", -ENXIO); + test_from_json("{\"key\":{\"name\":\"foobar\",\"type\":9}}", -EOPNOTSUPP); + test_from_json("{\"key\":{\"name\":\"foobar\"}}", -ENXIO); + test_from_json("{\"key\":{\"type\":9}}", -ENXIO); + test_from_json("{\"key\":{\"name\":\"foobar\",\"type\":1}}", -ENXIO); + test_from_json("{\"key\":{\"name\":\"foobar\",\"type\":1},\"address\":[1,2,3,4]}", 0); + test_from_json("{\"key\":{\"name\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"type\":1},\"address\":[1,2,3,4]}", 0); + test_from_json("{\"key\":{\"name\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"type\":1},\"address\":[1,2,3,4]}", -EBADMSG); + test_from_json("{\"key\":{\"name\":\"a.a\",\"type\":1},\"address\":[1,2,3,4]}", 0); + test_from_json("{\"key\":{\"name\":\"a..a\",\"type\":1},\"address\":[1,2,3,4]}", -EBADMSG); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/resolve/test-dns-zone.c b/src/resolve/test-dns-zone.c index b9ee18fd22af0..4cdb98aee5202 100644 --- a/src/resolve/test-dns-zone.c +++ b/src/resolve/test-dns-zone.c @@ -10,6 +10,8 @@ #include "tests.h" static void dns_scope_freep(DnsScope **s) { + POINTER_MAY_BE_NULL(s); + if (s != NULL && *s != NULL) dns_scope_free(*s); } diff --git a/src/resolve/test-dnssec-complex.c b/src/resolve/test-dnssec-complex.c index f486cf09a32ad..8b4e376284c16 100644 --- a/src/resolve/test-dnssec-complex.c +++ b/src/resolve/test-dnssec-complex.c @@ -18,6 +18,8 @@ static void prefix_random(const char *name, char **ret) { uint64_t i, u; char *m = NULL; + assert(ret); + u = 1 + (random_u64() & 3); for (i = 0; i < u; i++) { diff --git a/src/resolve/test-resolved-etc-hosts.c b/src/resolve/test-resolved-etc-hosts.c index 0528a411dc347..76c20b37e9c6e 100644 --- a/src/resolve/test-resolved-etc-hosts.c +++ b/src/resolve/test-resolved-etc-hosts.c @@ -92,7 +92,7 @@ TEST(parse_etc_hosts) { /* See https://tools.ietf.org/html/rfc1035#section-2.3.1 */ FOREACH_STRING(s, "bad-dash-", "-bad-dash", "-bad-dash.bad-") - assert_se(!hashmap_get(hosts.by_name, s)); + ASSERT_FALSE(hashmap_contains(hosts.by_name, s)); assert_se(bn = hashmap_get(hosts.by_name, "before.comment")); assert_se(set_size(bn->addresses) == 4); @@ -101,17 +101,17 @@ TEST(parse_etc_hosts) { assert_se(has_4(bn->addresses, "1.2.3.11")); assert_se(has_4(bn->addresses, "1.2.3.12")); - assert_se(!hashmap_get(hosts.by_name, "within.comment")); - assert_se(!hashmap_get(hosts.by_name, "within.comment2")); - assert_se(!hashmap_get(hosts.by_name, "within.comment3")); - assert_se(!hashmap_get(hosts.by_name, "#")); - - assert_se(!hashmap_get(hosts.by_name, "short.address")); - assert_se(!hashmap_get(hosts.by_name, "long.address")); - assert_se(!hashmap_get(hosts.by_name, "multi.colon")); - assert_se(!set_contains(hosts.no_address, "short.address")); - assert_se(!set_contains(hosts.no_address, "long.address")); - assert_se(!set_contains(hosts.no_address, "multi.colon")); + ASSERT_FALSE(hashmap_contains(hosts.by_name, "within.comment")); + ASSERT_FALSE(hashmap_contains(hosts.by_name, "within.comment2")); + ASSERT_FALSE(hashmap_contains(hosts.by_name, "within.comment3")); + ASSERT_FALSE(hashmap_contains(hosts.by_name, "#")); + + ASSERT_FALSE(hashmap_contains(hosts.by_name, "short.address")); + ASSERT_FALSE(hashmap_contains(hosts.by_name, "long.address")); + ASSERT_FALSE(hashmap_contains(hosts.by_name, "multi.colon")); + ASSERT_FALSE(set_contains(hosts.no_address, "short.address")); + ASSERT_FALSE(set_contains(hosts.no_address, "long.address")); + ASSERT_FALSE(set_contains(hosts.no_address, "multi.colon")); assert_se(bn = hashmap_get(hosts.by_name, "some.other")); assert_se(set_size(bn->addresses) == 1); diff --git a/src/resolve/test-resolved-stream.c b/src/resolve/test-resolved-stream.c index 9e6e7bc05081f..75c2868285f37 100644 --- a/src/resolve/test-resolved-stream.c +++ b/src/resolve/test-resolved-stream.c @@ -111,7 +111,7 @@ static void *tcp_dns_server(void *p) { assert_se(setsockopt(bindfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) >= 0); assert_se(bind(bindfd, &server_address.sa, sockaddr_len(&server_address)) >= 0); assert_se(listen(bindfd, 1) >= 0); - assert_se((acceptfd = accept(bindfd, NULL, NULL)) >= 0); + assert_se((acceptfd = accept4(bindfd, NULL, NULL, SOCK_CLOEXEC)) >= 0); server_handle(acceptfd); return NULL; } diff --git a/src/rpm/systemd-update-helper.in b/src/rpm/systemd-update-helper.in index 9063a2cc3bdab..e8bc1e5921520 100755 --- a/src/rpm/systemd-update-helper.in +++ b/src/rpm/systemd-update-helper.in @@ -99,7 +99,7 @@ case "$command" in fi if [[ "$command" =~ restart ]]; then - systemctl enqueue-marked-jobs + systemctl enqueue-marked fi ;; @@ -120,7 +120,7 @@ case "$command" in for user in $users; do SYSTEMD_BUS_TIMEOUT={{UPDATE_HELPER_USER_TIMEOUT_SEC}}s \ - systemctl --user -M "$user@" enqueue-marked-jobs & + systemctl --user -M "$user@" enqueue-marked & done wait fi diff --git a/src/run/run.c b/src/run/run.c index f1bbbe1f393c2..596e12826753c 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -105,6 +105,7 @@ static char **arg_timer_property = NULL; static bool arg_with_timer = false; static bool arg_quiet = false; static bool arg_verbose = false; +static OutputMode arg_output = _OUTPUT_MODE_INVALID; static bool arg_aggressive_gc = false; static char *arg_working_directory = NULL; static char *arg_root_directory = NULL; @@ -182,6 +183,8 @@ static int help(void) { " -P --pipe Pass STDIN/STDOUT/STDERR directly to service\n" " -q --quiet Suppress information messages during runtime\n" " -v --verbose Show unit logs while executing operation\n" + " --output=STRING Controls formatting of verbose logs, see\n" + " journalctl for valid values\n" " --json=pretty|short|off Print unit name and invocation id as JSON\n" " -G --collect Unload unit after it ran, even when failed\n" " -S --shell Invoke a $SHELL interactively\n" @@ -318,6 +321,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_EXEC_USER, ARG_EXEC_GROUP, ARG_NICE, + ARG_OUTPUT, ARG_ON_ACTIVE, ARG_ON_BOOT, ARG_ON_STARTUP, @@ -370,6 +374,7 @@ static int parse_argv(int argc, char *argv[]) { { "pipe", no_argument, NULL, 'P' }, { "quiet", no_argument, NULL, 'q' }, { "verbose", no_argument, NULL, 'v' }, + { "output", required_argument, NULL, ARG_OUTPUT }, { "on-active", required_argument, NULL, ARG_ON_ACTIVE }, { "on-boot", required_argument, NULL, ARG_ON_BOOT }, { "on-startup", required_argument, NULL, ARG_ON_STARTUP }, @@ -542,6 +547,15 @@ static int parse_argv(int argc, char *argv[]) { arg_verbose = true; break; + case ARG_OUTPUT: + if (streq(optarg, "help")) + return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); + + arg_output = output_mode_from_string(optarg); + if (arg_output < 0) + return log_error_errno(arg_output, "Unknown output format '%s'.", optarg); + break; + case ARG_ON_ACTIVE: r = add_timer_property("OnActiveSec", optarg); if (r < 0) @@ -2437,7 +2451,7 @@ static int run_context_show_result(RunContext *c) { return table_log_add_error(r); } - r = table_print(t, stderr); + r = table_print_full(t, stderr, /* flush= */ true); if (r < 0) return table_log_print_error(r); @@ -2569,7 +2583,7 @@ static int start_transient_service(sd_bus *bus) { _cleanup_(fork_notify_terminate) PidRef journal_pid = PIDREF_NULL; if (arg_verbose) - (void) journal_fork(arg_runtime_scope, STRV_MAKE(c.unit), &journal_pid); + (void) journal_fork(arg_runtime_scope, STRV_MAKE(c.unit), arg_output, &journal_pid); r = bus_call_with_hint(bus, m, "service", &reply); if (r < 0) diff --git a/src/sbsign/authenticode.c b/src/sbsign/authenticode.c new file mode 100644 index 0000000000000..9971cf9f26f04 --- /dev/null +++ b/src/sbsign/authenticode.c @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "authenticode.h" +#include "crypto-util.h" + +/* OpenSSL's ASN1_SEQUENCE/ASN1_CHOICE/IMPLEMENT_ASN1_FUNCTIONS macros expand to code that references + * libcrypto symbols directly, which would force us to link this object against libcrypto and defeat the + * dlopen approach used everywhere else. We work around that in two different ways depending on where the + * reference appears in the macro expansion: + * + * - ASN1_item_new/ASN1_item_free/ASN1_item_d2i/ASN1_item_i2d are called from the bodies of the functions + * generated by IMPLEMENT_ASN1_FUNCTIONS. We can simply #define them to the matching sym_* variants, so + * the generated bodies end up calling our dlopen'd pointers. + * + * - ASN1_ANY_it/ASN1_BIT_STRING_it/... (the "_it" getters) are embedded as function pointers in the + * static const ASN1_ADB / ASN1_TEMPLATE tables emitted by the ASN1_SEQUENCE/ASN1_CHOICE macros. Static + * initializers can only contain constant expressions, so we can't point them at the sym_* variables + * (which are non-const globals filled in at dlopen time). Instead we define static trampoline + * functions (openssl_ASN1_*_it) whose addresses are constant, each forwarding to the corresponding + * sym_* pointer at runtime, and #define the OpenSSL names to our trampolines before the tables are + * emitted. + * + * Note that this file must only call these redefined macros indirectly, via the IMPLEMENT_ASN1_FUNCTIONS + * expansions, and callers of those generated wrappers (e.g. sbsign.c) must have dlopen_libcrypto() + * before invoking them, otherwise the sym_* pointers will still be NULL. */ + +static const ASN1_ITEM* openssl_ASN1_ANY_it(void) { + assert(sym_ASN1_ANY_it); + return sym_ASN1_ANY_it(); +} + +static const ASN1_ITEM* openssl_ASN1_BIT_STRING_it(void) { + assert(sym_ASN1_BIT_STRING_it); + return sym_ASN1_BIT_STRING_it(); +} + +static const ASN1_ITEM* openssl_ASN1_BMPSTRING_it(void) { + assert(sym_ASN1_BMPSTRING_it); + return sym_ASN1_BMPSTRING_it(); +} + +static const ASN1_ITEM* openssl_ASN1_IA5STRING_it(void) { + assert(sym_ASN1_IA5STRING_it); + return sym_ASN1_IA5STRING_it(); +} + +static const ASN1_ITEM* openssl_ASN1_OBJECT_it(void) { + assert(sym_ASN1_OBJECT_it); + return sym_ASN1_OBJECT_it(); +} + +static const ASN1_ITEM* openssl_ASN1_OCTET_STRING_it(void) { + assert(sym_ASN1_OCTET_STRING_it); + return sym_ASN1_OCTET_STRING_it(); +} + +#define ASN1_item_new sym_ASN1_item_new +#define ASN1_item_free sym_ASN1_item_free +#define ASN1_item_d2i sym_ASN1_item_d2i +#define ASN1_item_i2d sym_ASN1_item_i2d +#define ASN1_ANY_it openssl_ASN1_ANY_it +#define ASN1_BIT_STRING_it openssl_ASN1_BIT_STRING_it +#define ASN1_BMPSTRING_it openssl_ASN1_BMPSTRING_it +#define ASN1_IA5STRING_it openssl_ASN1_IA5STRING_it +#define ASN1_OBJECT_it openssl_ASN1_OBJECT_it +#define ASN1_OCTET_STRING_it openssl_ASN1_OCTET_STRING_it + +ASN1_SEQUENCE(SpcAttributeTypeAndOptionalValue) = { + ASN1_SIMPLE(SpcAttributeTypeAndOptionalValue, type, ASN1_OBJECT), + ASN1_OPT(SpcAttributeTypeAndOptionalValue, value, ASN1_ANY) +} ASN1_SEQUENCE_END(SpcAttributeTypeAndOptionalValue); + +IMPLEMENT_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue); + +ASN1_SEQUENCE(AlgorithmIdentifier) = { + ASN1_SIMPLE(AlgorithmIdentifier, algorithm, ASN1_OBJECT), + ASN1_OPT(AlgorithmIdentifier, parameters, ASN1_ANY) +} ASN1_SEQUENCE_END(AlgorithmIdentifier) + +IMPLEMENT_ASN1_FUNCTIONS(AlgorithmIdentifier); + +ASN1_SEQUENCE(DigestInfo) = { + ASN1_SIMPLE(DigestInfo, digestAlgorithm, AlgorithmIdentifier), + ASN1_SIMPLE(DigestInfo, digest, ASN1_OCTET_STRING) +} ASN1_SEQUENCE_END(DigestInfo); + +IMPLEMENT_ASN1_FUNCTIONS(DigestInfo); + +ASN1_SEQUENCE(SpcIndirectDataContent) = { + ASN1_SIMPLE(SpcIndirectDataContent, data, SpcAttributeTypeAndOptionalValue), + ASN1_SIMPLE(SpcIndirectDataContent, messageDigest, DigestInfo) +} ASN1_SEQUENCE_END(SpcIndirectDataContent); + +IMPLEMENT_ASN1_FUNCTIONS(SpcIndirectDataContent); + +ASN1_CHOICE(SpcString) = { + ASN1_IMP_OPT(SpcString, value.unicode, ASN1_BMPSTRING, 0), + ASN1_IMP_OPT(SpcString, value.ascii, ASN1_IA5STRING, 1) +} ASN1_CHOICE_END(SpcString); + +IMPLEMENT_ASN1_FUNCTIONS(SpcString); + +ASN1_SEQUENCE(SpcSerializedObject) = { + ASN1_SIMPLE(SpcSerializedObject, classId, ASN1_OCTET_STRING), + ASN1_SIMPLE(SpcSerializedObject, serializedData, ASN1_OCTET_STRING) +} ASN1_SEQUENCE_END(SpcSerializedObject); + +IMPLEMENT_ASN1_FUNCTIONS(SpcSerializedObject); + +ASN1_CHOICE(SpcLink) = { + ASN1_IMP_OPT(SpcLink, value.url, ASN1_IA5STRING, 0), + ASN1_IMP_OPT(SpcLink, value.moniker, SpcSerializedObject, 1), + ASN1_EXP_OPT(SpcLink, value.file, SpcString, 2) +} ASN1_CHOICE_END(SpcLink); + +IMPLEMENT_ASN1_FUNCTIONS(SpcLink); + +ASN1_SEQUENCE(SpcPeImageData) = { + ASN1_SIMPLE(SpcPeImageData, flags, ASN1_BIT_STRING), + ASN1_EXP_OPT(SpcPeImageData, file, SpcLink, 0) +} ASN1_SEQUENCE_END(SpcPeImageData) + +IMPLEMENT_ASN1_FUNCTIONS(SpcPeImageData); diff --git a/src/sbsign/authenticode.h b/src/sbsign/authenticode.h index 95d5499a8e061..c076fe36241d8 100644 --- a/src/sbsign/authenticode.h +++ b/src/sbsign/authenticode.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include +#include #include "shared-forward.h" @@ -15,13 +15,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue); -ASN1_SEQUENCE(SpcAttributeTypeAndOptionalValue) = { - ASN1_SIMPLE(SpcAttributeTypeAndOptionalValue, type, ASN1_OBJECT), - ASN1_OPT(SpcAttributeTypeAndOptionalValue, value, ASN1_ANY) -} ASN1_SEQUENCE_END(SpcAttributeTypeAndOptionalValue); - -IMPLEMENT_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue); - typedef struct { ASN1_OBJECT *algorithm; ASN1_TYPE *parameters; @@ -29,13 +22,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(AlgorithmIdentifier); -ASN1_SEQUENCE(AlgorithmIdentifier) = { - ASN1_SIMPLE(AlgorithmIdentifier, algorithm, ASN1_OBJECT), - ASN1_OPT(AlgorithmIdentifier, parameters, ASN1_ANY) -} ASN1_SEQUENCE_END(AlgorithmIdentifier) - -IMPLEMENT_ASN1_FUNCTIONS(AlgorithmIdentifier); - typedef struct { AlgorithmIdentifier *digestAlgorithm; ASN1_OCTET_STRING *digest; @@ -43,13 +29,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(DigestInfo); -ASN1_SEQUENCE(DigestInfo) = { - ASN1_SIMPLE(DigestInfo, digestAlgorithm, AlgorithmIdentifier), - ASN1_SIMPLE(DigestInfo, digest, ASN1_OCTET_STRING) -} ASN1_SEQUENCE_END(DigestInfo); - -IMPLEMENT_ASN1_FUNCTIONS(DigestInfo); - typedef struct { SpcAttributeTypeAndOptionalValue *data; DigestInfo *messageDigest; @@ -57,15 +36,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(SpcIndirectDataContent); -ASN1_SEQUENCE(SpcIndirectDataContent) = { - ASN1_SIMPLE(SpcIndirectDataContent, data, SpcAttributeTypeAndOptionalValue), - ASN1_SIMPLE(SpcIndirectDataContent, messageDigest, DigestInfo) -} ASN1_SEQUENCE_END(SpcIndirectDataContent); - -IMPLEMENT_ASN1_FUNCTIONS(SpcIndirectDataContent); - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcIndirectDataContent*, SpcIndirectDataContent_free, NULL); - typedef struct { int type; union { @@ -76,13 +46,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(SpcString); -ASN1_CHOICE(SpcString) = { - ASN1_IMP_OPT(SpcString, value.unicode, ASN1_BMPSTRING, 0), - ASN1_IMP_OPT(SpcString, value.ascii, ASN1_IA5STRING, 1) -} ASN1_CHOICE_END(SpcString); - -IMPLEMENT_ASN1_FUNCTIONS(SpcString); - typedef struct { ASN1_OCTET_STRING *classId; ASN1_OCTET_STRING *serializedData; @@ -90,13 +53,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(SpcSerializedObject); -ASN1_SEQUENCE(SpcSerializedObject) = { - ASN1_SIMPLE(SpcSerializedObject, classId, ASN1_OCTET_STRING), - ASN1_SIMPLE(SpcSerializedObject, serializedData, ASN1_OCTET_STRING) -} ASN1_SEQUENCE_END(SpcSerializedObject); - -IMPLEMENT_ASN1_FUNCTIONS(SpcSerializedObject); - typedef struct { int type; union { @@ -108,16 +64,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(SpcLink); -ASN1_CHOICE(SpcLink) = { - ASN1_IMP_OPT(SpcLink, value.url, ASN1_IA5STRING, 0), - ASN1_IMP_OPT(SpcLink, value.moniker, SpcSerializedObject, 1), - ASN1_EXP_OPT(SpcLink, value.file, SpcString, 2) -} ASN1_CHOICE_END(SpcLink); - -IMPLEMENT_ASN1_FUNCTIONS(SpcLink); - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcLink*, SpcLink_free, NULL); - typedef struct { ASN1_BIT_STRING *flags; SpcLink *file; @@ -125,11 +71,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(SpcPeImageData); -ASN1_SEQUENCE(SpcPeImageData) = { - ASN1_SIMPLE(SpcPeImageData, flags, ASN1_BIT_STRING), - ASN1_EXP_OPT(SpcPeImageData, file, SpcLink, 0) -} ASN1_SEQUENCE_END(SpcPeImageData) - -IMPLEMENT_ASN1_FUNCTIONS(SpcPeImageData); - +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcIndirectDataContent*, SpcIndirectDataContent_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcLink*, SpcLink_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcPeImageData*, SpcPeImageData_free, NULL); diff --git a/src/sbsign/meson.build b/src/sbsign/meson.build index b6e0dbcde9c03..f28d4648f94a0 100644 --- a/src/sbsign/meson.build +++ b/src/sbsign/meson.build @@ -6,7 +6,10 @@ executables += [ 'conditions' : [ 'HAVE_OPENSSL', ], - 'sources' : files('sbsign.c'), - 'dependencies' : libopenssl, + 'sources' : files( + 'sbsign.c', + 'authenticode.c', + ), + 'dependencies' : libopenssl_cflags, }, ] diff --git a/src/sbsign/sbsign.c b/src/sbsign/sbsign.c index d13dc5e326a17..012f39dcc3094 100644 --- a/src/sbsign/sbsign.c +++ b/src/sbsign/sbsign.c @@ -1,22 +1,24 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" #include "ansi-color.h" +#include "ask-password-api.h" #include "authenticode.h" #include "build.h" #include "copy.h" +#include "crypto-util.h" #include "efi-fundamental.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "install-file.h" #include "io-util.h" #include "log.h" #include "main-func.h" -#include "openssl-util.h" +#include "options.h" #include "parse-argument.h" #include "pe-binary.h" #include "pretty-print.h" @@ -45,118 +47,98 @@ STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_signed_data, freep); STATIC_DESTRUCTOR_REGISTER(arg_signed_data_signature, freep); -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-sbsign", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sSign binaries for EFI Secure Boot%6$s\n" - "\n%3$sCommands:%4$s\n" - " sign EXEFILE Sign the given binary for EFI Secure Boot\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --output Where to write the signed PE binary\n" - " --certificate=PATH|URI\n" - " PEM certificate to use for signing, or a provider\n" - " specific designation if --certificate-source= is used\n" - " --certificate-source=file|provider:PROVIDER\n" - " Specify how to interpret the certificate from\n" - " --certificate=. Allows the certificate to be loaded\n" - " from an OpenSSL provider\n" - " --private-key=KEY Private key (PEM) to sign with\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used for signing\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sSign binaries for EFI Secure Boot%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + ansi_highlight(), ansi_normal(), + ansi_underline(), ansi_normal()); - return 0; -} + r = table_print_or_warn(verbs); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_OUTPUT, - ARG_CERTIFICATE, - ARG_CERTIFICATE_SOURCE, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - ARG_PREPARE_OFFLINE_SIGNING, - ARG_SIGNED_DATA, - ARG_SIGNED_DATA_SIGNATURE, - }; + printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "output", required_argument, NULL, ARG_OUTPUT }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - { "prepare-offline-signing", no_argument, NULL, ARG_PREPARE_OFFLINE_SIGNING }, - { "signed-data", required_argument, NULL, ARG_SIGNED_DATA }, - { "signed-data-signature", required_argument, NULL, ARG_SIGNED_DATA_SIGNATURE }, - {} - }; + printf("\nSee the %s for details.\n", link); + return 0; +} - int c, r; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - help(0, NULL, NULL); - return 0; + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_OUTPUT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_output); + OPTION_LONG("output", "PATH", + "Where to write the signed PE binary"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_output); if (r < 0) return r; break; - case ARG_CERTIFICATE: - r = free_and_strdup_warn(&arg_certificate, optarg); + OPTION_COMMON_CERTIFICATE("PEM certificate to use for signing"): + r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - optarg, + arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) return r; break; - case ARG_PRIVATE_KEY: - r = free_and_strdup_warn(&arg_private_key, optarg); + OPTION_COMMON_PRIVATE_KEY("Private key (PEM) to sign with"): + r = free_and_strdup_warn(&arg_private_key, arg); if (r < 0) return r; break; - case ARG_PRIVATE_KEY_SOURCE: + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - optarg, + arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) @@ -164,29 +146,23 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_PREPARE_OFFLINE_SIGNING: + OPTION_LONG("prepare-offline-signing", NULL, /* help= */ NULL): arg_prepare_offline_signing = true; break; - case ARG_SIGNED_DATA: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_signed_data); + OPTION_LONG("signed-data", "PATH", /* help= */ NULL): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_signed_data); if (r < 0) return r; break; - case ARG_SIGNED_DATA_SIGNATURE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_signed_data_signature); + OPTION_LONG("signed-data-signature", "PATH", /* help= */ NULL): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_signed_data_signature); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_private_key_source && !arg_certificate) @@ -198,6 +174,7 @@ static int parse_argv(int argc, char *argv[]) { if (arg_prepare_offline_signing && (arg_private_key || arg_signed_data || arg_signed_data_signature)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--prepare-offline-signing cannot be used with --private-key=, --signed-data= or --signed-data-signature="); + *ret_args = option_parser_get_args(&state); return 1; } @@ -227,13 +204,13 @@ static int spc_indirect_data_content_new(const void *digest, size_t digestsz, ui return log_oom(); link->value.file->type = 0; - link->value.file->value.unicode = ASN1_BMPSTRING_new(); + link->value.file->value.unicode = sym_ASN1_BMPSTRING_new(); if (!link->value.file->value.unicode) return log_oom(); - if (ASN1_STRING_set(link->value.file->value.unicode, obsolete, sizeof(obsolete)) == 0) + if (sym_ASN1_STRING_set(link->value.file->value.unicode, obsolete, sizeof(obsolete)) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ASN1 string: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); _cleanup_(SpcPeImageData_freep) SpcPeImageData *peid = SpcPeImageData_new(); if (!peid) @@ -245,45 +222,46 @@ static int spc_indirect_data_content_new(const void *digest, size_t digestsz, ui int peidrawsz = i2d_SpcPeImageData(peid, &peidraw); if (peidrawsz < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert SpcPeImageData to BER: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); _cleanup_(SpcIndirectDataContent_freep) SpcIndirectDataContent *idc = SpcIndirectDataContent_new(); - idc->data->value = ASN1_TYPE_new(); + idc->data->value = sym_ASN1_TYPE_new(); if (!idc->data->value) return log_oom(); idc->data->value->type = V_ASN1_SEQUENCE; - idc->data->value->value.sequence = ASN1_STRING_new(); + idc->data->value->value.sequence = sym_ASN1_STRING_new(); if (!idc->data->value->value.sequence) return log_oom(); - idc->data->type = OBJ_txt2obj(SPC_PE_IMAGE_DATA_OBJID, /* no_name= */ 1); + idc->data->type = sym_OBJ_txt2obj(SPC_PE_IMAGE_DATA_OBJID, /* no_name= */ 1); if (!idc->data->type) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcPeImageData object: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); + + if (!sym_ASN1_STRING_set(idc->data->value->value.sequence, peidraw, peidrawsz)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ASN1_STRING data."); - idc->data->value->value.sequence->data = TAKE_PTR(peidraw); - idc->data->value->value.sequence->length = peidrawsz; - idc->messageDigest->digestAlgorithm->algorithm = OBJ_nid2obj(NID_sha256); + idc->messageDigest->digestAlgorithm->algorithm = sym_OBJ_nid2obj(NID_sha256); if (!idc->messageDigest->digestAlgorithm->algorithm) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SHA256 object: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - idc->messageDigest->digestAlgorithm->parameters = ASN1_TYPE_new(); + idc->messageDigest->digestAlgorithm->parameters = sym_ASN1_TYPE_new(); if (!idc->messageDigest->digestAlgorithm->parameters) return log_oom(); idc->messageDigest->digestAlgorithm->parameters->type = V_ASN1_NULL; - if (ASN1_OCTET_STRING_set(idc->messageDigest->digest, digest, digestsz) == 0) + if (sym_ASN1_OCTET_STRING_set(idc->messageDigest->digest, digest, digestsz) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set digest: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); _cleanup_free_ uint8_t *idcraw = NULL; int idcrawsz = i2d_SpcIndirectDataContent(idc, &idcraw); if (idcrawsz < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert SpcIndirectDataContent to BER: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); *ret_idc = TAKE_PTR(idcraw); *ret_idcsz = (size_t) idcrawsz; @@ -299,12 +277,12 @@ static int asn1_timestamp(ASN1_TIME **ret) { usec_t epoch = parse_source_date_epoch(); if (epoch == USEC_INFINITY) { - time = X509_gmtime_adj(NULL, 0); + time = sym_X509_gmtime_adj(NULL, 0); if (!time) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get current time: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); } else { - time = ASN1_TIME_set(NULL, (time_t) (epoch / USEC_PER_SEC)); + time = sym_ASN1_TIME_set(NULL, (time_t) (epoch / USEC_PER_SEC)); if (!time) return log_oom(); } @@ -346,37 +324,37 @@ static int pkcs7_new_with_attributes( } /* Add an empty SMIMECAP attribute to indicate we don't have any SMIME capabilities. */ - _cleanup_(x509_algor_free_manyp) STACK_OF(X509_ALGOR) *smcap = sk_X509_ALGOR_new_null(); + _cleanup_(x509_algor_free_manyp) STACK_OF(X509_ALGOR) *smcap = (STACK_OF(X509_ALGOR)*) sym_OPENSSL_sk_new_null(); if (!smcap) return log_oom(); - if (PKCS7_add_attrib_smimecap(si, smcap) == 0) + if (sym_PKCS7_add_attrib_smimecap(si, smcap) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add smimecap signed attribute to signer info: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (PKCS7_add_attrib_content_type(si, NULL) == 0) + if (sym_PKCS7_add_attrib_content_type(si, NULL) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add content type signed attribute to signer info: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); _cleanup_(ASN1_TIME_freep) ASN1_TIME *time = NULL; r = asn1_timestamp(&time); if (r < 0) return r; - if (PKCS7_add0_attrib_signing_time(si, time) == 0) + if (sym_PKCS7_add0_attrib_signing_time(si, time) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add signing time signed attribute to signer info: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); TAKE_PTR(time); - ASN1_OBJECT *idc = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true); + ASN1_OBJECT *idc = sym_OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true); if (!idc) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcIndirectDataContent object: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, idc) == 0) + if (sym_PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, idc) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add signed attribute to pkcs7 signer info: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); *ret_p7 = TAKE_PTR(p7); *ret_si = TAKE_PTR(si); @@ -386,23 +364,23 @@ static int pkcs7_new_with_attributes( static int pkcs7_populate_data_bio(PKCS7* p7, const void *data, size_t size, BIO **ret) { assert(ret); - _cleanup_(BIO_free_allp) BIO *bio = PKCS7_dataInit(p7, NULL); + _cleanup_(BIO_free_allp) BIO *bio = sym_PKCS7_dataInit(p7, NULL); if (!bio) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to create PKCS7 data bio: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); int tag, class; long psz; const uint8_t *p = data; /* This function weirdly enough reports errors by setting the 0x80 bit in its return value. */ - if (ASN1_get_object(&p, &psz, &tag, &class, size) & 0x80) + if (sym_ASN1_get_object(&p, &psz, &tag, &class, size) & 0x80) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse ASN.1 object: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (BIO_write(bio, p, psz) < 0) + if (sym_BIO_write(bio, p, psz) < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write to PKCS7 data bio: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); *ret = TAKE_PTR(bio); @@ -414,31 +392,33 @@ static int pkcs7_add_digest_attribute(PKCS7 *p7, BIO *data, PKCS7_SIGNER_INFO *s assert(data); assert(si); - BIO *mdbio = BIO_find_type(data, BIO_TYPE_MD); + BIO *mdbio = sym_BIO_find_type(data, BIO_TYPE_MD); if (!mdbio) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to find digest bio: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); EVP_MD_CTX *mdc; - if (BIO_get_md_ctx(mdbio, &mdc) <= 0) + if (sym_BIO_get_md_ctx(mdbio, &mdc) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get digest context from bio: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); unsigned char digest[EVP_MAX_MD_SIZE]; unsigned digestsz; - if (EVP_DigestFinal_ex(mdc, digest, &digestsz) == 0) + if (sym_EVP_DigestFinal_ex(mdc, digest, &digestsz) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get digest: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (PKCS7_add1_attrib_digest(si, digest, digestsz) == 0) + if (sym_PKCS7_add1_attrib_digest(si, digest, digestsz) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add PKCS9 message digest signed attribute to signer info: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); return 0; } -static int verb_sign(int argc, char *argv[], void *userdata) { +VERB(verb_sign, "sign", "EXEFILE", 2, 2, 0, + "Sign the given binary for EFI Secure Boot"); +static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL; _cleanup_(X509_freep) X509 *certificate = NULL; @@ -446,6 +426,10 @@ static int verb_sign(int argc, char *argv[], void *userdata) { _cleanup_(iovec_done) struct iovec signed_attributes_signature = {}; int r; + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + if (argc < 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No input file specified"); @@ -508,9 +492,9 @@ static int verb_sign(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Failed to read signed attributes file '%s': %m", arg_signed_data); const uint8_t *p = content; - if (!ASN1_item_d2i((ASN1_VALUE **) &signed_attributes, &p, contentsz, ASN1_ITEM_rptr(PKCS7_ATTR_SIGN))) + if (!sym_ASN1_item_d2i((ASN1_VALUE **) &signed_attributes, &p, contentsz, sym_PKCS7_ATTR_SIGN_it())) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse signed attributes: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); } if (arg_signed_data_signature) { @@ -547,7 +531,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { _cleanup_free_ void *pehash = NULL; size_t pehashsz; - r = pe_hash(srcfd, EVP_sha256(), &pehash, &pehashsz); + r = pe_hash(srcfd, sym_EVP_sha256(), &pehash, &pehashsz); if (r < 0) return log_error_errno(r, "Failed to hash PE binary %s: %m", argv[0]); @@ -576,10 +560,10 @@ static int verb_sign(int argc, char *argv[], void *userdata) { return r; _cleanup_free_ unsigned char *abuf = NULL; - int alen = ASN1_item_i2d((ASN1_VALUE *)si->auth_attr, &abuf, ASN1_ITEM_rptr(PKCS7_ATTR_SIGN)); + int alen = sym_ASN1_item_i2d((ASN1_VALUE *)si->auth_attr, &abuf, sym_PKCS7_ATTR_SIGN_it()); if (alen < 0 || !abuf) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert signed attributes ASN.1 to DER: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); r = loop_write(dstfd, abuf, alen); if (r < 0) @@ -594,51 +578,51 @@ static int verb_sign(int argc, char *argv[], void *userdata) { } if (iovec_is_set(&signed_attributes_signature)) - ASN1_STRING_set0(si->enc_digest, TAKE_PTR(signed_attributes_signature.iov_base), signed_attributes_signature.iov_len); + sym_ASN1_STRING_set0(si->enc_digest, TAKE_PTR(signed_attributes_signature.iov_base), signed_attributes_signature.iov_len); else { _cleanup_(BIO_free_allp) BIO *bio = NULL; r = pkcs7_populate_data_bio(p7, idcraw, idcrawsz, &bio); if (r < 0) return r; - if (PKCS7_dataFinal(p7, bio) == 0) + if (sym_PKCS7_dataFinal(p7, bio) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to sign data: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); } - _cleanup_(PKCS7_freep) PKCS7 *p7c = PKCS7_new(); + _cleanup_(PKCS7_freep) PKCS7 *p7c = sym_PKCS7_new(); if (!p7c) return log_oom(); - p7c->type = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true); + p7c->type = sym_OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true); if (!p7c->type) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcIndirectDataContent object: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - p7c->d.other = ASN1_TYPE_new(); + p7c->d.other = sym_ASN1_TYPE_new(); if (!p7c->d.other) return log_oom(); p7c->d.other->type = V_ASN1_SEQUENCE; - p7c->d.other->value.sequence = ASN1_STRING_new(); + p7c->d.other->value.sequence = sym_ASN1_STRING_new(); if (!p7c->d.other->value.sequence) return log_oom(); - if (ASN1_STRING_set(p7c->d.other->value.sequence, idcraw, idcrawsz) == 0) + if (sym_ASN1_STRING_set(p7c->d.other->value.sequence, idcraw, idcrawsz) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ASN1 string: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (PKCS7_set_content(p7, p7c) == 0) + if (sym_PKCS7_set_content(p7, p7c) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 data: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); TAKE_PTR(p7c); _cleanup_free_ uint8_t *sig = NULL; - int sigsz = i2d_PKCS7(p7, &sig); + int sigsz = sym_i2d_PKCS7(p7, &sig); if (sigsz < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL; _cleanup_free_ PeHeader *pe_header = NULL; @@ -738,20 +722,16 @@ static int verb_sign(int argc, char *argv[], void *userdata) { } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "sign", 2, 2, 0, verb_sign }, - {} - }; int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/shared/acl-util.c b/src/shared/acl-util.c index ae4684414ca8e..92d920e171a72 100644 --- a/src/shared/acl-util.c +++ b/src/shared/acl-util.c @@ -3,11 +3,14 @@ #include #include +#include "sd-dlopen.h" + #include "acl-util.h" #include "alloc-util.h" #include "errno-util.h" #include "extract-word.h" #include "fd-util.h" +#include "log.h" /* IWYU pragma: keep */ #include "set.h" #include "string-util.h" #include "strv.h" @@ -42,17 +45,20 @@ DLSYM_PROTOTYPE(acl_set_permset); DLSYM_PROTOTYPE(acl_set_qualifier); DLSYM_PROTOTYPE(acl_set_tag_type); DLSYM_PROTOTYPE(acl_to_any_text); +#endif -int dlopen_libacl(void) { - ELF_NOTE_DLOPEN("acl", +int dlopen_libacl(int log_level) { +#if HAVE_ACL + SD_ELF_NOTE_DLOPEN( + "acl", "Support for file Access Control Lists (ACLs)", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "libacl.so.1"); return dlopen_many_sym_or_warn( &libacl_dl, "libacl.so.1", - LOG_DEBUG, + log_level, DLSYM_ARG(acl_add_perm), DLSYM_ARG(acl_calc_mask), DLSYM_ARG(acl_copy_entry), @@ -79,8 +85,13 @@ int dlopen_libacl(void) { DLSYM_ARG(acl_set_qualifier), DLSYM_ARG(acl_set_tag_type), DLSYM_ARG(acl_to_any_text)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libacl support is not compiled in."); +#endif } +#if HAVE_ACL int devnode_acl(int fd, const Set *uids) { _cleanup_set_free_ Set *found = NULL; bool changed = false; @@ -88,7 +99,7 @@ int devnode_acl(int fd, const Set *uids) { assert(fd >= 0); - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) return r; @@ -333,7 +344,7 @@ int acl_search_groups(const char *path, char ***ret_groups) { assert(path); - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) return r; @@ -410,7 +421,7 @@ int parse_acl( if (!split) return -ENOMEM; - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) return r; @@ -588,7 +599,7 @@ int acls_for_file(const char *path, acl_type_t type, acl_t acl, acl_t *ret) { assert(path); - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) return r; @@ -647,7 +658,7 @@ int fd_add_uid_acl_permission( assert(fd >= 0); assert(uid_is_valid(uid)); - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) return r; @@ -713,7 +724,7 @@ int fd_acl_make_read_only(int fd) { /* Safely drops all W bits from all relevant ACL entries of the file, without changing entries which * are masked by the ACL mask */ - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) goto maybe_fallback; @@ -801,7 +812,7 @@ int fd_acl_make_writable(int fd) { /* Safely adds the writable bit to the owner's ACL entry of this inode. (And only the owner's! – This * not the obvious inverse of fd_acl_make_read_only() hence!) */ - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) goto maybe_fallback; diff --git a/src/shared/acl-util.h b/src/shared/acl-util.h index 1b74101ae44a6..5e2aeb94a9f63 100644 --- a/src/shared/acl-util.h +++ b/src/shared/acl-util.h @@ -36,8 +36,6 @@ extern DLSYM_PROTOTYPE(acl_set_qualifier); extern DLSYM_PROTOTYPE(acl_set_tag_type); extern DLSYM_PROTOTYPE(acl_to_any_text); -int dlopen_libacl(void); - int devnode_acl(int fd, const Set *uids); int calc_acl_mask_if_needed(acl_t *acl_p); @@ -85,10 +83,6 @@ typedef unsigned acl_type_t; #define ACL_TYPE_ACCESS (0x8000) #define ACL_TYPE_DEFAULT (0x4000) -static inline int dlopen_libacl(void) { - return -EOPNOTSUPP; -} - static inline int devnode_acl(int fd, const Set *uids) { return -EOPNOTSUPP; } @@ -98,6 +92,8 @@ static inline int fd_add_uid_acl_permission(int fd, uid_t uid, unsigned mask) { } #endif +int dlopen_libacl(int log_level); + int fd_acl_make_read_only(int fd); int fd_acl_make_writable(int fd); diff --git a/src/shared/acpi-fpdt.c b/src/shared/acpi-fpdt.c index d1551acbc91ac..c94c0ed4ab820 100644 --- a/src/shared/acpi-fpdt.c +++ b/src/shared/acpi-fpdt.c @@ -56,7 +56,7 @@ struct acpi_fpdt_boot { uint64_t startup_start; uint64_t exit_services_entry; uint64_t exit_services_exit; -} _packed; +} _packed_; /* /dev/mem is deprecated on many systems, try using /sys/firmware/acpi/fpdt parsing instead. * This code requires kernel version 5.12 on x86 based machines or 6.2 for arm64 */ diff --git a/src/shared/apparmor-util.c b/src/shared/apparmor-util.c index 10b57c60901d8..5f01bfae01651 100644 --- a/src/shared/apparmor-util.c +++ b/src/shared/apparmor-util.c @@ -1,11 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "apparmor-util.h" +#include "log.h" + +#if HAVE_APPARMOR + #include +#include "sd-dlopen.h" + #include "alloc-util.h" -#include "apparmor-util.h" #include "fileio.h" -#include "log.h" #include "parse-util.h" static void *libapparmor_dl = NULL; @@ -19,26 +24,6 @@ DLSYM_PROTOTYPE(aa_policy_cache_new) = NULL; DLSYM_PROTOTYPE(aa_policy_cache_replace_all) = NULL; DLSYM_PROTOTYPE(aa_policy_cache_unref) = NULL; -int dlopen_libapparmor(void) { - ELF_NOTE_DLOPEN("apparmor", - "Support for AppArmor policies", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libapparmor.so.1"); - - return dlopen_many_sym_or_warn( - &libapparmor_dl, - "libapparmor.so.1", - LOG_DEBUG, - DLSYM_ARG(aa_change_onexec), - DLSYM_ARG(aa_change_profile), - DLSYM_ARG(aa_features_new_from_kernel), - DLSYM_ARG(aa_features_unref), - DLSYM_ARG(aa_policy_cache_dir_path_preview), - DLSYM_ARG(aa_policy_cache_new), - DLSYM_ARG(aa_policy_cache_replace_all), - DLSYM_ARG(aa_policy_cache_unref)); -} - bool mac_apparmor_use(void) { static int cached_use = -1; int r; @@ -60,8 +45,36 @@ bool mac_apparmor_use(void) { if (r <= 0) return (cached_use = false); - if (dlopen_libapparmor() < 0) + if (dlopen_libapparmor(LOG_DEBUG) < 0) return (cached_use = false); return (cached_use = true); } + +#endif + +int dlopen_libapparmor(int log_level) { +#if HAVE_APPARMOR + SD_ELF_NOTE_DLOPEN( + "apparmor", + "Support for AppArmor policies", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libapparmor.so.1"); + + return dlopen_many_sym_or_warn( + &libapparmor_dl, + "libapparmor.so.1", + log_level, + DLSYM_ARG(aa_change_onexec), + DLSYM_ARG(aa_change_profile), + DLSYM_ARG(aa_features_new_from_kernel), + DLSYM_ARG(aa_features_unref), + DLSYM_ARG(aa_policy_cache_dir_path_preview), + DLSYM_ARG(aa_policy_cache_new), + DLSYM_ARG(aa_policy_cache_replace_all), + DLSYM_ARG(aa_policy_cache_unref)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libapparmor support is not compiled in."); +#endif +} diff --git a/src/shared/apparmor-util.h b/src/shared/apparmor-util.h index 06d6bf30e27c2..e87ba84504d7d 100644 --- a/src/shared/apparmor-util.h +++ b/src/shared/apparmor-util.h @@ -19,14 +19,11 @@ extern DLSYM_PROTOTYPE(aa_policy_cache_unref); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(aa_features*, sym_aa_features_unref, aa_features_unrefp, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(aa_policy_cache*, sym_aa_policy_cache_unref, aa_policy_cache_unrefp, NULL); - -int dlopen_libapparmor(void); bool mac_apparmor_use(void); #else -static inline int dlopen_libapparmor(void) { - return -EOPNOTSUPP; -} static inline bool mac_apparmor_use(void) { return false; } #endif + +int dlopen_libapparmor(int log_level); diff --git a/src/shared/base-filesystem.c b/src/shared/base-filesystem.c index bad3b46f3ad3a..9e8856ba48ce6 100644 --- a/src/shared/base-filesystem.c +++ b/src/shared/base-filesystem.c @@ -5,7 +5,7 @@ #include #include -#ifdef ARCH_MIPS +#ifdef __mips__ #include #endif diff --git a/src/shared/blkid-util.c b/src/shared/blkid-util.c index ae20b47d9ef12..18bf100d064d1 100644 --- a/src/shared/blkid-util.c +++ b/src/shared/blkid-util.c @@ -1,10 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - +#include "sd-dlopen.h" #include "sd-id128.h" #include "blkid-util.h" +#include "log.h" /* IWYU pragma: keep */ #include "parse-util.h" #include "string-util.h" @@ -48,54 +48,6 @@ DLSYM_PROTOTYPE(blkid_probe_set_sectorsize) = NULL; DLSYM_PROTOTYPE(blkid_probe_set_superblocks_flags) = NULL; DLSYM_PROTOTYPE(blkid_safe_string) = NULL; -int dlopen_libblkid(void) { - ELF_NOTE_DLOPEN("blkid", - "Support for block device identification", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libblkid.so.1"); - - return dlopen_many_sym_or_warn( - &libblkid_dl, - "libblkid.so.1", - LOG_DEBUG, - DLSYM_ARG(blkid_do_fullprobe), - DLSYM_ARG(blkid_do_probe), - DLSYM_ARG(blkid_do_safeprobe), - DLSYM_ARG(blkid_do_wipe), - DLSYM_ARG(blkid_encode_string), - DLSYM_ARG(blkid_free_probe), - DLSYM_ARG(blkid_new_probe), - DLSYM_ARG(blkid_new_probe_from_filename), - DLSYM_ARG(blkid_partition_get_flags), - DLSYM_ARG(blkid_partition_get_name), - DLSYM_ARG(blkid_partition_get_partno), - DLSYM_ARG(blkid_partition_get_size), - DLSYM_ARG(blkid_partition_get_start), - DLSYM_ARG(blkid_partition_get_type), - DLSYM_ARG(blkid_partition_get_type_string), - DLSYM_ARG(blkid_partition_get_uuid), - DLSYM_ARG(blkid_partlist_devno_to_partition), - DLSYM_ARG(blkid_partlist_get_partition), - DLSYM_ARG(blkid_partlist_numof_partitions), - DLSYM_ARG(blkid_probe_enable_partitions), - DLSYM_ARG(blkid_probe_enable_superblocks), - DLSYM_ARG(blkid_probe_filter_superblocks_type), - DLSYM_ARG(blkid_probe_filter_superblocks_usage), - DLSYM_ARG(blkid_probe_get_fd), - DLSYM_ARG(blkid_probe_get_partitions), - DLSYM_ARG(blkid_probe_get_size), - DLSYM_ARG(blkid_probe_get_value), - DLSYM_ARG(blkid_probe_is_wholedisk), - DLSYM_ARG(blkid_probe_lookup_value), - DLSYM_ARG(blkid_probe_numof_values), - DLSYM_ARG(blkid_probe_set_device), - DLSYM_ARG(blkid_probe_set_hint), - DLSYM_ARG(blkid_probe_set_partitions_flags), - DLSYM_ARG(blkid_probe_set_sectorsize), - DLSYM_ARG(blkid_probe_set_superblocks_flags), - DLSYM_ARG(blkid_safe_string)); -} - int blkid_partition_get_uuid_id128(blkid_partition p, sd_id128_t *ret) { const char *s; @@ -144,3 +96,57 @@ int blkid_probe_lookup_value_u64(blkid_probe b, const char *field, uint64_t *ret return safe_atou64(u, ret); } #endif + +int dlopen_libblkid(int log_level) { +#if HAVE_BLKID + SD_ELF_NOTE_DLOPEN( + "blkid", + "Support for block device identification", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libblkid.so.1"); + + return dlopen_many_sym_or_warn( + &libblkid_dl, + "libblkid.so.1", + log_level, + DLSYM_ARG(blkid_do_fullprobe), + DLSYM_ARG(blkid_do_probe), + DLSYM_ARG(blkid_do_safeprobe), + DLSYM_ARG(blkid_do_wipe), + DLSYM_ARG(blkid_encode_string), + DLSYM_ARG(blkid_free_probe), + DLSYM_ARG(blkid_new_probe), + DLSYM_ARG(blkid_new_probe_from_filename), + DLSYM_ARG(blkid_partition_get_flags), + DLSYM_ARG(blkid_partition_get_name), + DLSYM_ARG(blkid_partition_get_partno), + DLSYM_ARG(blkid_partition_get_size), + DLSYM_ARG(blkid_partition_get_start), + DLSYM_ARG(blkid_partition_get_type), + DLSYM_ARG(blkid_partition_get_type_string), + DLSYM_ARG(blkid_partition_get_uuid), + DLSYM_ARG(blkid_partlist_devno_to_partition), + DLSYM_ARG(blkid_partlist_get_partition), + DLSYM_ARG(blkid_partlist_numof_partitions), + DLSYM_ARG(blkid_probe_enable_partitions), + DLSYM_ARG(blkid_probe_enable_superblocks), + DLSYM_ARG(blkid_probe_filter_superblocks_type), + DLSYM_ARG(blkid_probe_filter_superblocks_usage), + DLSYM_ARG(blkid_probe_get_fd), + DLSYM_ARG(blkid_probe_get_partitions), + DLSYM_ARG(blkid_probe_get_size), + DLSYM_ARG(blkid_probe_get_value), + DLSYM_ARG(blkid_probe_is_wholedisk), + DLSYM_ARG(blkid_probe_lookup_value), + DLSYM_ARG(blkid_probe_numof_values), + DLSYM_ARG(blkid_probe_set_device), + DLSYM_ARG(blkid_probe_set_hint), + DLSYM_ARG(blkid_probe_set_partitions_flags), + DLSYM_ARG(blkid_probe_set_sectorsize), + DLSYM_ARG(blkid_probe_set_superblocks_flags), + DLSYM_ARG(blkid_safe_string)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libblkid support is not compiled in."); +#endif +} diff --git a/src/shared/blkid-util.h b/src/shared/blkid-util.h index 09502eadc4e9a..718a0f15a917f 100644 --- a/src/shared/blkid-util.h +++ b/src/shared/blkid-util.h @@ -46,8 +46,6 @@ extern DLSYM_PROTOTYPE(blkid_probe_set_sectorsize); extern DLSYM_PROTOTYPE(blkid_probe_set_superblocks_flags); extern DLSYM_PROTOTYPE(blkid_safe_string); -int dlopen_libblkid(void); - DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(blkid_probe, sym_blkid_free_probe, blkid_free_probep, NULL); int blkid_partition_get_uuid_id128(blkid_partition p, sd_id128_t *ret); @@ -65,8 +63,6 @@ enum { int blkid_probe_lookup_value_id128(blkid_probe b, const char *field, sd_id128_t *ret); int blkid_probe_lookup_value_u64(blkid_probe b, const char *field, uint64_t *ret); -#else -static inline int dlopen_libblkid(void) { - return -EOPNOTSUPP; -} #endif + +int dlopen_libblkid(int log_level); diff --git a/src/shared/blockdev-list.c b/src/shared/blockdev-list.c index d856b1cb48cac..5b11c8169477f 100644 --- a/src/shared/blockdev-list.c +++ b/src/shared/blockdev-list.c @@ -8,6 +8,7 @@ #include "blockdev-util.h" #include "device-private.h" #include "device-util.h" +#include "devnum-util.h" #include "errno-util.h" #include "string-util.h" #include "strv.h" @@ -99,14 +100,21 @@ int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *re size_t n = 0; CLEANUP_ARRAY(l, n, block_device_array_free); - dev_t root_devno = 0; - if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_ROOT)) - if (blockdev_get_root(LOG_DEBUG, &root_devno) > 0) { - r = block_get_whole_disk(root_devno, &root_devno); + dev_t root_devno = 0, whole_root_devno = 0; + if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_ROOT)) { + r = blockdev_get_root(LOG_DEBUG, &root_devno); + if (r < 0) + log_debug_errno(r, "Failed to get block device of root device, ignoring: %m"); + else if (r > 0) { + r = block_get_whole_disk(root_devno, &whole_root_devno); if (r < 0) - log_debug_errno(r, "Failed to get whole block device of root device: %m"); + log_debug_errno(r, "Failed to get whole block device of root device, ignoring: %m"); } + /* It's fine if root_devno/whole_root_devno are zero here as devnum_set_and_equal() will + * happily take that into account – it is in fact its primary raison d'etre. */ + } + if (sd_device_enumerator_new(&e) < 0) return log_oom(); @@ -138,7 +146,8 @@ int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *re continue; } - if (devno == root_devno) + if (devnum_set_and_equal(devno, root_devno) || + devnum_set_and_equal(devno, whole_root_devno)) continue; } @@ -189,10 +198,17 @@ int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *re } _cleanup_free_ char *model = NULL, *vendor = NULL, *subsystem = NULL; + int ro = -1; if (FLAGS_SET(flags, BLOCKDEV_LIST_METADATA)) { (void) blockdev_get_prop(dev, "ID_MODEL_FROM_DATABASE", "ID_MODEL", &model); (void) blockdev_get_prop(dev, "ID_VENDOR_FROM_DATABASE", "ID_VENDOR", &vendor); (void) blockdev_get_subsystem(dev, &subsystem); + + r = device_get_sysattr_bool(dev, "ro"); + if (r < 0) + log_device_debug_errno(dev, r, "Failed to acquire read-only flag of device '%s', ignoring: %m", node); + else + ro = r; } if (ret_devices) { @@ -216,6 +232,7 @@ int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *re .model = TAKE_PTR(model), .vendor = TAKE_PTR(vendor), .subsystem = TAKE_PTR(subsystem), + .read_only = ro, }; } else { diff --git a/src/shared/blockdev-list.h b/src/shared/blockdev-list.h index 845f336be5b65..d82345435f7e2 100644 --- a/src/shared/blockdev-list.h +++ b/src/shared/blockdev-list.h @@ -10,7 +10,7 @@ typedef enum BlockDevListFlags { BLOCKDEV_LIST_REQUIRE_LUKS = 1 << 3, /* Only consider block devices with LUKS superblocks */ BLOCKDEV_LIST_IGNORE_ROOT = 1 << 4, /* Ignore the block device we are currently booted from */ BLOCKDEV_LIST_IGNORE_EMPTY = 1 << 5, /* Ignore disks of zero size (usually drives without a medium) */ - BLOCKDEV_LIST_METADATA = 1 << 6, /* Fill in model, vendor, subsystem */ + BLOCKDEV_LIST_METADATA = 1 << 6, /* Fill in model, vendor, subsystem, read_only */ } BlockDevListFlags; typedef struct BlockDevice { @@ -21,11 +21,13 @@ typedef struct BlockDevice { char *subsystem; uint64_t diskseq; uint64_t size; /* in bytes */ + int read_only; } BlockDevice; #define BLOCK_DEVICE_NULL (BlockDevice) { \ .diskseq = UINT64_MAX, \ .size = UINT64_MAX, \ + .read_only = -1, \ } void block_device_done(BlockDevice *d); diff --git a/src/shared/boot-entry.c b/src/shared/boot-entry.c index 0f1d8090247a6..b8665d7abbaff 100644 --- a/src/shared/boot-entry.c +++ b/src/shared/boot-entry.c @@ -11,10 +11,9 @@ #include "string-table.h" #include "string-util.h" #include "strv.h" -#include "utf8.h" bool boot_entry_token_valid(const char *p) { - return utf8_is_valid(p) && string_is_safe(p) && filename_is_valid(p); + return string_is_safe(p, STRING_FILENAME); } static int entry_token_load_one(int rfd, const char *dir, BootEntryTokenType *type, char **token) { @@ -32,7 +31,7 @@ static int entry_token_load_one(int rfd, const char *dir, BootEntryTokenType *ty if (!p) return log_oom(); - r = chase_and_fopenat_unlocked(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f); + r = chase_and_fopenat_unlocked(rfd, rfd, p, /* chase_flags= */ 0, "re", NULL, &f); if (r == -ENOENT) return 0; if (r < 0) @@ -253,6 +252,12 @@ int parse_boot_entry_token_type(const char *s, BootEntryTokenType *type, char ** * Hence, do not pass in uninitialized pointers. */ + if (streq(s, "auto")) { + *type = BOOT_ENTRY_TOKEN_AUTO; + *token = mfree(*token); + return 0; + } + if (streq(s, "machine-id")) { *type = BOOT_ENTRY_TOKEN_MACHINE_ID; *token = mfree(*token); diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 5901729e8845d..3338d75f660df 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -231,13 +231,13 @@ static int parse_tries(const char *fname, const char **p, unsigned *ret) { d = strndup(*p, n); if (!d) - return log_oom(); + return -ENOMEM; r = safe_atou_full(d, 10, &tries); - if (r >= 0 && tries > INT_MAX) /* sd-boot allows INT_MAX, let's use the same limit */ - r = -ERANGE; if (r < 0) - return log_error_errno(r, "Failed to parse tries counter of filename '%s': %m", fname); + return r; + if (tries > INT_MAX) /* sd-boot allows INT_MAX, let's use the same limit */ + return -ERANGE; *p = *p + n; *ret = tries; @@ -257,8 +257,6 @@ int boot_filename_extract_tries( assert(fname); assert(ret_stripped); - assert(ret_tries_left); - assert(ret_tries_done); /* Be liberal with suffix, only insist on a dot. After all we want to cover any capitalization here * (vfat is case insensitive after all), and at least .efi and .conf as suffix. */ @@ -292,24 +290,29 @@ int boot_filename_extract_tries( stripped = strndup(fname, m - fname); if (!stripped) - return log_oom(); + return -ENOMEM; if (!strextend(&stripped, suffix)) - return log_oom(); + return -ENOMEM; *ret_stripped = TAKE_PTR(stripped); - *ret_tries_left = tries_left; - *ret_tries_done = tries_done; + if (ret_tries_left) + *ret_tries_left = tries_left; + if (ret_tries_done) + *ret_tries_done = tries_done; return 0; nothing: stripped = strdup(fname); if (!stripped) - return log_oom(); + return -ENOMEM; *ret_stripped = TAKE_PTR(stripped); - *ret_tries_left = *ret_tries_done = UINT_MAX; + if (ret_tries_left) + *ret_tries_left = UINT_MAX; + if (ret_tries_done) + *ret_tries_done = UINT_MAX; return 0; } @@ -335,7 +338,7 @@ static int boot_entry_load_type1( r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done); if (r < 0) - return r; + return log_error_errno(r, "Failed to extract tries counters from '%s': %m", fname); if (!efi_loader_entry_name_valid(tmp.id)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", fname); @@ -555,6 +558,12 @@ static int boot_loader_read_conf_path(BootConfig *config, const char *root, cons return boot_loader_read_conf(config, f, full); } +static unsigned boot_entry_profile(const BootEntry *a) { + assert(a); + + return a->profile == UINT_MAX ? 0 : a->profile; +} + static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { int r; @@ -583,6 +592,10 @@ static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { r = -strverscmp_improved(a->version, b->version); if (r != 0) return r; + + r = CMP(boot_entry_profile(a), boot_entry_profile(b)); + if (r != 0) + return r; } r = -strverscmp_improved(a->id_without_profile ?: a->id, b->id_without_profile ?: b->id); @@ -592,7 +605,7 @@ static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { if (a->id_without_profile && b->id_without_profile) { /* The strverscmp_improved() call above already established that we are talking about the * same image here, hence order by profile, if there is one */ - r = CMP(a->profile, b->profile); + r = CMP(boot_entry_profile(a), boot_entry_profile(b)); if (r != 0) return r; } @@ -711,7 +724,6 @@ static int boot_entry_load_unified( _cleanup_free_ char *fname = NULL, *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL, *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL; const char *k, *good_name, *good_version, *good_sort_key; - _cleanup_fclose_ FILE *f = NULL; int r; assert(root); @@ -723,11 +735,8 @@ static int boot_entry_load_unified( if (!k) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path); - f = fmemopen_unlocked((void*) osrelease_text, strlen(osrelease_text), "r"); - if (!f) - return log_oom(); - - r = parse_env_file(f, "os-release", + r = parse_env_data(osrelease_text, /* size= */ SIZE_MAX, + ".osrel", "PRETTY_NAME", &os_pretty_name, "IMAGE_ID", &os_image_id, "NAME", &os_name, @@ -755,14 +764,9 @@ static int boot_entry_load_unified( _cleanup_free_ char *profile_id = NULL, *profile_title = NULL; if (profile_text) { - fclose(f); - - f = fmemopen_unlocked((void*) profile_text, strlen(profile_text), "r"); - if (!f) - return log_oom(); - - r = parse_env_file( - f, "profile", + r = parse_env_data( + profile_text, /* size= */ SIZE_MAX, + ".profile", "ID", &profile_id, "TITLE", &profile_title); if (r < 0) @@ -777,7 +781,7 @@ static int boot_entry_load_unified( r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done); if (r < 0) - return r; + return log_error_errno(r, "Failed to extract tries counters from '%s': %m", fname); if (!efi_loader_entry_name_valid(tmp.id)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id); @@ -1055,6 +1059,7 @@ static int pe_find_addon_sections( assert(fd >= 0); assert(path); + assert(ret_cmdline); r = pe_load_headers_and_sections(fd, path, §ions, &pe_header); if (r < 0) @@ -1121,7 +1126,7 @@ static int boot_entries_find_unified_addons( assert(ret_addons); assert(config); - r = chase_and_opendirat(d_fd, addon_dir, CHASE_AT_RESOLVE_IN_ROOT, &full, &d); + r = chase_and_opendirat(d_fd, d_fd, addon_dir, /* chase_flags= */ 0, &full, &d); if (r == -ENOENT) return 0; if (r < 0) @@ -1587,11 +1592,28 @@ int boot_config_load_auto( "Failed to determine whether /run/boot-loader-entries/ exists: %m"); } - r = find_esp_and_warn(NULL, override_esp_path, /* unprivileged_mode= */ false, &esp_where, NULL, NULL, NULL, NULL, &esp_devid); + r = find_esp_and_warn_full( + /* root= */ NULL, + override_esp_path, + /* unprivileged_mode= */ false, + &esp_where, + /* ret_fd= */ NULL, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid= */ NULL, + &esp_devid); if (r < 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */ return r; - r = find_xbootldr_and_warn(NULL, override_xbootldr_path, /* unprivileged_mode= */ false, &xbootldr_where, NULL, &xbootldr_devid); + r = find_xbootldr_and_warn_full( + /* root= */ NULL, + override_xbootldr_path, + /* unprivileged_mode= */ false, + &xbootldr_where, + /* ret_fd= */ NULL, + /* ret_uuid= */ NULL, + &xbootldr_devid); if (r < 0 && r != -ENOKEY) return r; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */ diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index f325dcae25154..951a81f08c6c4 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -34,11 +34,11 @@ typedef struct BootEntry { BootEntryType type; BootEntrySource source; bool reported_by_loader; - char *id; /* This is the file basename (including extension!) */ - char *id_old; /* Old-style ID, for deduplication purposes. */ - char *id_without_profile; /* id without profile suffixed */ - char *path; /* This is the full path to the drop-in file */ - char *root; /* The root path in which the drop-in was found, i.e. to which 'kernel', 'efi' and 'initrd' are relative */ + char *id; /* This is the file basename (including extension, but with tries counters stripped) */ + char *id_old; /* Old-style ID, for deduplication purposes (for type1: same as the regular id, but with extension stripped too). */ + char *id_without_profile; /* ID without profile suffixed */ + char *path; /* This is the full path to the drop-in file (i.e. prefixed with 'root' field below) */ + char *root; /* The root path in which the drop-in was found, i.e. to which 'kernel', 'efi' and 'initrd' are relative */ char *title; char *show_title; char *sort_key; diff --git a/src/shared/bpf-dlopen.c b/src/shared/bpf-dlopen.c index 0e7632eb343e8..1d2fdef781eea 100644 --- a/src/shared/bpf-dlopen.c +++ b/src/shared/bpf-dlopen.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dlopen.h" + #include "bpf-dlopen.h" #include "dlfcn-util.h" #include "errno-util.h" @@ -19,6 +21,10 @@ #define MODERN_LIBBPF 0 #endif +static void *bpf_dl = NULL; + +static DLSYM_PROTOTYPE(libbpf_get_error) = NULL; + DLSYM_PROTOTYPE(bpf_link__destroy) = NULL; DLSYM_PROTOTYPE(bpf_link__fd) = NULL; DLSYM_PROTOTYPE(bpf_link__open) = NULL; @@ -43,15 +49,12 @@ DLSYM_PROTOTYPE(bpf_program__attach) = NULL; DLSYM_PROTOTYPE(bpf_program__attach_cgroup) = NULL; DLSYM_PROTOTYPE(bpf_program__attach_lsm) = NULL; DLSYM_PROTOTYPE(bpf_program__name) = NULL; -DLSYM_PROTOTYPE(libbpf_get_error) = NULL; DLSYM_PROTOTYPE(libbpf_set_print) = NULL; DLSYM_PROTOTYPE(ring_buffer__epoll_fd) = NULL; DLSYM_PROTOTYPE(ring_buffer__free) = NULL; DLSYM_PROTOTYPE(ring_buffer__new) = NULL; DLSYM_PROTOTYPE(ring_buffer__poll) = NULL; -static void* bpf_dl = NULL; - /* new symbols available from libbpf 0.7.0 */ int (*sym_bpf_map_create)(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *); struct bpf_map* (*sym_bpf_object__next_map)(const struct bpf_object *obj, const struct bpf_map *map); @@ -73,16 +76,17 @@ static int bpf_print_func(enum libbpf_print_level level, const char *fmt, va_lis return log_internalv(LOG_DEBUG, errno, NULL, 0, NULL, fmt, ap); } -int dlopen_bpf_full(int log_level) { +int dlopen_bpf(int log_level) { static int cached = 0; int r; if (cached != 0) return cached; - ELF_NOTE_DLOPEN("bpf", + SD_ELF_NOTE_DLOPEN( + "bpf", "Support firewalling and sandboxing with BPF", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libbpf.so.1", "libbpf.so.0"); DISABLE_WARNING_DEPRECATED_DECLARATIONS; @@ -206,7 +210,7 @@ int bpf_get_error_translated(const void *ptr) { #else -int dlopen_bpf_full(int log_level) { +int dlopen_bpf(int log_level) { return log_once_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), "libbpf support is not compiled in, cgroup BPF features disabled."); } diff --git a/src/shared/bpf-dlopen.h b/src/shared/bpf-dlopen.h index ecae890035436..b3d14f9b5f437 100644 --- a/src/shared/bpf-dlopen.h +++ b/src/shared/bpf-dlopen.h @@ -51,7 +51,4 @@ int bpf_get_error_translated(const void *ptr); #endif -int dlopen_bpf_full(int log_level); -static inline int dlopen_bpf(void) { - return dlopen_bpf_full(LOG_DEBUG); -} +int dlopen_bpf(int log_level); diff --git a/src/shared/bpf-link.c b/src/shared/bpf-link.c index 72d374c235997..95f7256a56795 100644 --- a/src/shared/bpf-link.c +++ b/src/shared/bpf-link.c @@ -9,7 +9,7 @@ bool bpf_can_link_program(struct bpf_program *prog) { assert(prog); - if (dlopen_bpf() < 0) + if (dlopen_bpf(LOG_DEBUG) < 0) return false; /* Pass invalid cgroup fd intentionally. */ diff --git a/src/shared/bpf-program.c b/src/shared/bpf-program.c index 55b0fc284de8f..3cef280dc50d7 100644 --- a/src/shared/bpf-program.c +++ b/src/shared/bpf-program.c @@ -152,6 +152,8 @@ int bpf_program_new(uint32_t prog_type, const char *prog_name, BPFProgram **ret) _cleanup_(bpf_program_freep) BPFProgram *p = NULL; _cleanup_free_ char *name = NULL; + assert(ret); + if (prog_name) { if (strlen(prog_name) >= BPF_OBJ_NAME_LEN) return -ENAMETOOLONG; diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index 9bd0a74c5fe83..bb3e28f6bbba4 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -101,8 +101,7 @@ int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { uint64_t id; int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - assert(path); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert(ret); fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); @@ -149,8 +148,9 @@ int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { if (stat((char*) di.path, &st) < 0) return -errno; - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; + r = stat_verify_block(&st); + if (r < 0) + return r; if (major(st.st_rdev) == 0) return -ENODEV; @@ -878,18 +878,15 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol struct btrfs_ioctl_vol_args vol_args = {}; _cleanup_close_ int subvol_fd = -EBADF; - struct stat st; bool made_writable = false; int r; assert(fd >= 0); assert(subvolume); - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode)) - return -EINVAL; + r = fd_verify_directory(fd); + if (r < 0) + return r; subvol_fd = openat(fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); if (subvol_fd < 0) @@ -1017,7 +1014,7 @@ int btrfs_subvol_remove_at(int dir_fd, const char *path, BtrfsRemoveFlags flags) assert(path); - fd = chase_and_openat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_CLOEXEC, &subvolume); + fd = chase_and_openat(XAT_FDROOT, dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_CLOEXEC, &subvolume); if (fd < 0) return fd; @@ -1430,7 +1427,7 @@ int btrfs_subvol_snapshot_at_full( if (old_fd < 0) return old_fd; - new_fd = chase_and_openat(dir_fdt, to, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_CLOEXEC, &subvolume); + new_fd = chase_and_openat(XAT_FDROOT, dir_fdt, to, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_CLOEXEC, &subvolume); if (new_fd < 0) return new_fd; diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h index 55fb07656a59d..afa54cde4c434 100644 --- a/src/shared/btrfs-util.h +++ b/src/shared/btrfs-util.h @@ -54,7 +54,7 @@ static inline int btrfs_get_block_device(const char *path, dev_t *ret) { return btrfs_get_block_device_at(AT_FDCWD, path, ret); } static inline int btrfs_get_block_device_fd(int fd, dev_t *ret) { - return btrfs_get_block_device_at(fd, "", ret); + return btrfs_get_block_device_at(fd, NULL, ret); } int btrfs_defrag_fd(int fd); diff --git a/src/shared/bus-polkit.c b/src/shared/bus-polkit.c index f19db2a6eb008..78e9ed377f384 100644 --- a/src/shared/bus-polkit.c +++ b/src/shared/bus-polkit.c @@ -250,13 +250,10 @@ static AsyncPolkitQuery* async_polkit_query_free(AsyncPolkitQuery *q) { DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(AsyncPolkitQuery, async_polkit_query, async_polkit_query_free); DEFINE_TRIVIAL_CLEANUP_FUNC(AsyncPolkitQuery*, async_polkit_query_unref); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( async_polkit_query_hash_ops, - void, - trivial_hash_func, - trivial_compare_func, - AsyncPolkitQuery, - async_polkit_query_unref); + void, trivial_hash_func, trivial_compare_func, + AsyncPolkitQuery, async_polkit_query_unref); static int async_polkit_defer(sd_event_source *s, void *userdata) { AsyncPolkitQuery *q = ASSERT_PTR(userdata); diff --git a/src/shared/bus-unit-procs.c b/src/shared/bus-unit-procs.c index bd510efebd356..97a800b4a0fab 100644 --- a/src/shared/bus-unit-procs.c +++ b/src/shared/bus-unit-procs.c @@ -134,6 +134,9 @@ static void remove_cgroup(Hashmap *cgroups, struct CGroupInfo *cg) { } static int cgroup_info_compare_func(struct CGroupInfo * const *a, struct CGroupInfo * const *b) { + assert(a); + assert(b); + return strcmp((*a)->cgroup_path, (*b)->cgroup_path); } diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 84de3478a7f9e..440c6ced290ea 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -1175,6 +1175,8 @@ static int bus_append_import_credential(sd_bus_message *m, const char *field, co static int bus_append_refresh_on_reload(sd_bus_message *m, const char *field, const char *eq) { int r; + assert(eq); + r = sd_bus_message_open_container(m, 'r', "sv"); if (r < 0) return bus_log_create_error(r); @@ -2383,6 +2385,8 @@ static const BusProperty cgroup_properties[] = { { "ManagedOOMMemoryPressure", bus_append_string }, { "ManagedOOMPreference", bus_append_string }, { "MemoryPressureWatch", bus_append_string }, + { "CPUPressureWatch", bus_append_string }, + { "IOPressureWatch", bus_append_string }, { "DelegateSubgroup", bus_append_string }, { "ManagedOOMMemoryPressureLimit", bus_append_parse_permyriad }, { "MemoryAccounting", bus_append_parse_boolean }, @@ -2421,6 +2425,8 @@ static const BusProperty cgroup_properties[] = { { "SocketBindAllow", bus_append_socket_filter }, { "SocketBindDeny", bus_append_socket_filter }, { "MemoryPressureThresholdSec", bus_append_parse_sec_rename }, + { "CPUPressureThresholdSec", bus_append_parse_sec_rename }, + { "IOPressureThresholdSec", bus_append_parse_sec_rename }, { "NFTSet", bus_append_nft_set }, { "BindNetworkInterface", bus_append_string }, diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c index dc13ae0f77994..771363517b39e 100644 --- a/src/shared/calendarspec.c +++ b/src/shared/calendarspec.c @@ -39,6 +39,7 @@ static CalendarComponent* chain_free(CalendarComponent *c) { DEFINE_TRIVIAL_CLEANUP_FUNC(CalendarComponent*, chain_free); CalendarSpec* calendar_spec_free(CalendarSpec *c) { + POINTER_MAY_BE_NULL(c); if (!c) return NULL; @@ -57,6 +58,9 @@ CalendarSpec* calendar_spec_free(CalendarSpec *c) { static int component_compare(CalendarComponent * const *a, CalendarComponent * const *b) { int r; + assert(a); + assert(b); + r = CMP((*a)->start, (*b)->start); if (r != 0) return r; @@ -487,6 +491,10 @@ static int parse_one_number(const char *p, const char **e, unsigned long *ret) { char *ee = NULL; unsigned long value; + assert(p); + assert(e); + assert(ret); + errno = 0; value = strtoul(p, &ee, 10); if (errno > 0) @@ -504,6 +512,9 @@ static int parse_component_decimal(const char **p, bool usec, int *res) { const char *e = NULL; int r; + assert(p); + assert(res); + if (!ascii_isdigit(**p)) return -EINVAL; @@ -569,6 +580,8 @@ static int calendarspec_from_time_t(CalendarSpec *c, time_t time) { struct tm tm; int r; + assert(c); + if ((usec_t) time > USEC_INFINITY / USEC_PER_SEC) return -ERANGE; @@ -616,9 +629,8 @@ static int calendarspec_from_time_t(CalendarSpec *c, time_t time) { static int prepend_component(const char **p, bool usec, unsigned nesting, CalendarComponent **c) { int r, start, stop = -1, repeat = 0; CalendarComponent *cc; - const char *e = *p; + const char *e = *ASSERT_PTR(p); - assert(p); assert(c); if (nesting > CALENDARSPEC_COMPONENTS_MAX) @@ -1149,9 +1161,8 @@ static int find_matching_component( } else if (c->repeat > 0) { int k; - k = start + ROUND_UP(*val - start, c->repeat); - - if ((!d_set || k < d) && (stop < 0 || k <= stop)) { + if (ADD_SAFE(&k, start, ROUND_UP(*val - start, c->repeat)) && + (!d_set || k < d) && (stop < 0 || k <= stop)) { d = k; d_set = true; } @@ -1219,6 +1230,8 @@ static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) { struct tm t; int k; + assert(tm); + if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS) return true; @@ -1272,6 +1285,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { assert(spec); assert(tm); + assert(usec); c = *tm; tm_usec = *usec; diff --git a/src/shared/cgroup-show.c b/src/shared/cgroup-show.c index cfc80d666f389..896dd58219178 100644 --- a/src/shared/cgroup-show.c +++ b/src/shared/cgroup-show.c @@ -413,6 +413,8 @@ int show_cgroup_get_path_and_warn( _cleanup_free_ char *root = NULL; int r; + assert(ret); + if (machine) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_free_ char *unit = NULL; diff --git a/src/shared/chown-recursive.c b/src/shared/chown-recursive.c index 850e495836b53..a0e885ece9547 100644 --- a/src/shared/chown-recursive.c +++ b/src/shared/chown-recursive.c @@ -9,6 +9,7 @@ #include "fd-util.h" #include "fs-util.h" #include "path-util.h" +#include "stat-util.h" #include "strv.h" #include "user-util.h" #include "xattr-util.h" @@ -144,6 +145,7 @@ int fd_chown_recursive( int duplicated_fd = -EBADF; struct stat st; + int r; /* Note that the slightly different order of fstat() and the checks here and in * path_chown_recursive(). That's because when we open the directory ourselves we can specify @@ -153,8 +155,9 @@ int fd_chown_recursive( if (fstat(fd, &st) < 0) return -errno; - if (!S_ISDIR(st.st_mode)) - return -ENOTDIR; + r = stat_verify_directory(&st); + if (r < 0) + return r; if (!uid_is_valid(uid) && !gid_is_valid(gid) && FLAGS_SET(mask, 07777)) return 0; /* nothing to do */ diff --git a/src/shared/condition.c b/src/shared/condition.c index 903662edf1a8f..af5093a6a3132 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -82,11 +82,9 @@ Condition* condition_new(ConditionType type, const char *parameter, bool trigger .negate = negate, }; - if (parameter) { - c->parameter = strdup(parameter); - if (!c->parameter) - return mfree(c); - } + c->parameter = strdup(parameter); + if (!c->parameter) + return mfree(c); return c; } @@ -741,6 +739,8 @@ static int condition_test_security(Condition *c, char **env) { return detect_confidential_virtualization() > 0; if (streq(c->parameter, "measured-uki")) return efi_measured_uki(LOG_DEBUG); + if (streq(c->parameter, "measured-os")) + return efi_measured_os(LOG_DEBUG); return false; } diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index 1d1d8a10ccd0b..bfba85564f834 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -189,6 +189,9 @@ static int parse_line( assert(line > 0); assert(lookup); assert(l); + assert(section); + assert(section_line); + assert(section_ignored); l = strstrip(l); if (isempty(l)) @@ -214,8 +217,8 @@ static int parse_line( if (!n) return log_oom(); - if (!string_is_safe(n)) - return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EBADMSG), "Bad characters in section header '%s'", l); + if (!string_is_safe(n, /* flags= */ 0)) + return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EBADMSG), "Section header invalid '%s'", l); if (sections && !nulstr_contains(sections, n)) { bool ignore; @@ -506,7 +509,7 @@ static int config_parse_many_files( /* Pin and stat() all dropins */ STRV_FOREACH(fn, files) { _cleanup_fclose_ FILE *f = NULL; - r = chase_and_fopenat_unlocked(root_fd, *fn, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); + r = chase_and_fopenat_unlocked(root_fd, root_fd, *fn, CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); if (r == -ENOENT) continue; if (r < 0) @@ -541,7 +544,7 @@ static int config_parse_many_files( /* First process the first found main config file. */ STRV_FOREACH(fn, conf_files) { _cleanup_fclose_ FILE *f = NULL; - r = chase_and_fopenat_unlocked(root_fd, *fn, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); + r = chase_and_fopenat_unlocked(root_fd, root_fd, *fn, CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); if (r == -ENOENT) continue; if (r < 0) @@ -918,6 +921,8 @@ static int _hashmap_by_section_find_unused_line( unsigned n = 0; void *entry; + assert(ret); + HASHMAP_BASE_FOREACH_KEY(entry, cs, entries_by_section) { if (filename && !streq(cs->filename, filename)) continue; @@ -1239,7 +1244,7 @@ int config_parse_string( return 1; } - if (FLAGS_SET(ltype, CONFIG_PARSE_STRING_SAFE) && !string_is_safe(rvalue)) { + if (FLAGS_SET(ltype, CONFIG_PARSE_STRING_SAFE) && !string_is_safe(rvalue, STRING_ALLOW_GLOBS)) { _cleanup_free_ char *escaped = NULL; escaped = cescape(rvalue); diff --git a/src/shared/copy.c b/src/shared/copy.c index 445c246359a7a..3ac05c15b7b2a 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -193,16 +193,26 @@ int copy_bytes_full( if (fdt < 0) return fdt; + if (FLAGS_SET(copy_flags, COPY_SEEK0_SOURCE) && + lseek(fdf, 0, SEEK_SET) < 0) + return -errno; + + if (FLAGS_SET(copy_flags, COPY_SEEK0_TARGET) && + lseek(fdt, 0, SEEK_SET) < 0) + return -errno; + /* Try btrfs reflinks first. This only works on regular, seekable files, hence let's check the file offsets of * source and destination first. */ if ((copy_flags & COPY_REFLINK)) { off_t foffset; - foffset = lseek(fdf, 0, SEEK_CUR); + /* In reflink mode we need to know where the current file offset is, but if we just seeked to + * 0 anyway, we can suppress that. */ + foffset = FLAGS_SET(copy_flags, COPY_SEEK0_SOURCE) ? 0 : lseek(fdf, 0, SEEK_CUR); if (foffset >= 0) { off_t toffset; - toffset = lseek(fdt, 0, SEEK_CUR); + toffset = FLAGS_SET(copy_flags, COPY_SEEK0_TARGET) ? 0 : lseek(fdt, 0, SEEK_CUR); if (toffset >= 0) { if (foffset == 0 && toffset == 0 && max_bytes == UINT64_MAX) diff --git a/src/shared/copy.h b/src/shared/copy.h index 6e4a3b177b337..929973532678e 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -34,6 +34,8 @@ typedef enum CopyFlags { COPY_NOCOW_AFTER = 1 << 20, COPY_PRESERVE_FS_VERITY = 1 << 21, /* Preserve fs-verity when copying. */ COPY_MERGE_APPLY_STAT = 1 << 22, /* When we reuse an existing directory inode, apply source ownership/mode/xattrs/timestamps */ + COPY_SEEK0_SOURCE = 1 << 23, /* Seek back to start of source file before copying */ + COPY_SEEK0_TARGET = 1 << 24, /* Seek back to start of target file before copying */ } CopyFlags; typedef enum DenyType { diff --git a/src/shared/cpu-set-util.c b/src/shared/cpu-set-util.c index e4ef36da9aaba..9211dbe47e54a 100644 --- a/src/shared/cpu-set-util.c +++ b/src/shared/cpu-set-util.c @@ -159,6 +159,8 @@ int cpu_set_add(CPUSet *c, size_t i) { if (r < 0) return r; + /* Silence static analyzers */ + assert(i / CHAR_BIT < c->allocated); CPU_SET_S(i, c->allocated, c->set); return 0; } @@ -194,6 +196,8 @@ int cpu_set_add_range(CPUSet *c, size_t start, size_t end) { if (r < 0) return r; + /* Silence static analyzers */ + assert(end / CHAR_BIT < c->allocated); for (size_t i = start; i <= end; i++) CPU_SET_S(i, c->allocated, c->set); diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 2aac4d253bb76..dd2a93844dbdd 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -6,10 +6,6 @@ #include "efivars.h" #include "time-util.h" -#if HAVE_OPENSSL -#include -#endif - #include "sd-id128.h" #include "sd-json.h" #include "sd-varlink.h" @@ -19,6 +15,7 @@ #include "chattr-util.h" #include "copy.h" #include "creds-util.h" +#include "crypto-util.h" #include "efi-api.h" #include "env-util.h" #include "errno-util.h" @@ -32,7 +29,6 @@ #include "log.h" #include "memory-util.h" #include "mkdir-label.h" -#include "openssl-util.h" #include "parse-util.h" #include "path-util.h" #include "random-util.h" @@ -129,6 +125,8 @@ int open_credentials_dir(void) { int get_system_credentials_dir(const char **ret) { int r; + assert(ret); + /* Note that for system credentials the environment variable we honour is just for debugging purpose * (unlike for the per-service credential path env var where it's key part of the protocol). */ r = get_credentials_dir_internal("SYSTEMD_SYSTEM_CREDENTIALS_DIRECTORY", ret); @@ -142,6 +140,8 @@ int get_system_credentials_dir(const char **ret) { int get_encrypted_system_credentials_dir(const char **ret) { int r; + assert(ret); + r = get_credentials_dir_internal("SYSTEMD_ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY", ret); if (r >= 0 || r != -ENXIO) return r; @@ -271,6 +271,8 @@ int read_credential_strings_many_internal( bool all = true; int r, ret = 0; + assert(first_value); + /* Reads a bunch of credentials into the specified buffers. If the specified buffers are already * non-NULL frees them if a credential is found. Only supports string-based credentials * (i.e. refuses embedded NUL bytes). @@ -334,6 +336,9 @@ int get_credential_user_password(const char *username, char **ret_password, bool _cleanup_free_ char *cn = NULL; int r; + assert(ret_password); + assert(ret_is_hashed); + /* Try to pick up the password for this account via the credentials logic */ cn = strjoin("passwd.hashed-password.", username); if (!cn) @@ -725,6 +730,7 @@ static int sha256_hash_host_and_tpm2_key( _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md = NULL; unsigned l; + int r; assert(iovec_is_valid(host_key)); assert(iovec_is_valid(tpm2_key)); @@ -732,22 +738,26 @@ static int sha256_hash_host_and_tpm2_key( /* Combines the host key and the TPM2 HMAC hash into a SHA256 hash value we'll use as symmetric encryption key. */ - md = EVP_MD_CTX_new(); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + md = sym_EVP_MD_CTX_new(); if (!md) return log_oom(); - if (EVP_DigestInit_ex(md, EVP_sha256(), NULL) != 1) + if (sym_EVP_DigestInit_ex(md, sym_EVP_sha256(), NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initial SHA256 context."); - if (iovec_is_set(host_key) && EVP_DigestUpdate(md, host_key->iov_base, host_key->iov_len) != 1) + if (iovec_is_set(host_key) && sym_EVP_DigestUpdate(md, host_key->iov_base, host_key->iov_len) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash host key."); - if (iovec_is_set(tpm2_key) && EVP_DigestUpdate(md, tpm2_key->iov_base, tpm2_key->iov_len) != 1) + if (iovec_is_set(tpm2_key) && sym_EVP_DigestUpdate(md, tpm2_key->iov_base, tpm2_key->iov_len) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash TPM2 key."); - assert(EVP_MD_CTX_size(md) == SHA256_DIGEST_LENGTH); + assert(sym_EVP_MD_CTX_get_size(md) == SHA256_DIGEST_LENGTH); - if (EVP_DigestFinal_ex(md, ret, &l) != 1) + if (sym_EVP_DigestFinal_ex(md, ret, &l) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize SHA256 hash."); assert(l == SHA256_DIGEST_LENGTH); @@ -840,6 +850,8 @@ int encrypt_credential_and_warn( /* Only one of these two flags may be set at the same time */ assert(!FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL) || !FLAGS_SET(flags, CREDENTIAL_REFUSE_NULL)); + CLEANUP_ERASE(md); + if (!CRED_KEY_IS_VALID(with_key) && !CRED_KEY_IS_AUTO(with_key)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid key type: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(with_key)); @@ -892,14 +904,17 @@ int encrypt_credential_and_warn( * container tpm2_support will detect this, and will return a different flag combination of * TPM2_SUPPORT_FULL, effectively skipping the use of TPM2 when inside one. */ - try_tpm2 = tpm2_is_fully_supported(); + try_tpm2 = tpm2_is_mostly_supported(); if (!try_tpm2) log_debug("System lacks TPM2 support or running in a container, not attempting to use TPM2."); } else try_tpm2 = CRED_KEY_REQUIRES_TPM2(with_key); if (try_tpm2) { - if (CRED_KEY_WANTS_TPM2_PK(with_key) || CRED_KEY_REQUIRES_TPM2_PK(with_key)) { + /* If the firmware does not support TPMs, then UKI measurements are not going to work, hence + * PCR 11 public key stuff cannot work. Because of that, if PK is only wanted (but not + * required) we won't try it. */ + if ((CRED_KEY_WANTS_TPM2_PK(with_key) && tpm2_is_fully_supported()) || CRED_KEY_REQUIRES_TPM2_PK(with_key)) { /* Load public key for PCR policies, if one is specified, or explicitly requested */ @@ -924,6 +939,8 @@ int encrypt_credential_and_warn( if (r < 0) return log_error_errno(r, "Could not find best pcr bank: %m"); + log_debug("Selected literal PCR mask: 0x%x, PK PCR mask: 0x%x", tpm2_hash_pcr_mask, tpm2_pubkey_pcr_mask); + TPML_PCR_SELECTION tpm2_hash_pcr_selection; tpm2_tpml_pcr_selection_from_mask(tpm2_hash_pcr_mask, tpm2_pcr_bank, &tpm2_hash_pcr_selection); @@ -1023,16 +1040,20 @@ int encrypt_credential_and_warn( return r; } - assert_se(cc = EVP_aes_256_gcm()); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; - ksz = EVP_CIPHER_key_length(cc); + assert_se(cc = sym_EVP_aes_256_gcm()); + + ksz = sym_EVP_CIPHER_get_key_length(cc); assert(ksz == sizeof(md)); - bsz = EVP_CIPHER_block_size(cc); + bsz = sym_EVP_CIPHER_get_block_size(cc); assert(bsz > 0); assert((size_t) bsz <= CREDENTIAL_FIELD_SIZE_MAX); - ivsz = EVP_CIPHER_iv_length(cc); + ivsz = sym_EVP_CIPHER_get_iv_length(cc); if (ivsz > 0) { assert((size_t) ivsz <= CREDENTIAL_FIELD_SIZE_MAX); @@ -1043,14 +1064,14 @@ int encrypt_credential_and_warn( tsz = 16; /* FIXME: On OpenSSL 3 there is EVP_CIPHER_CTX_get_tag_length(), until then let's hardcode this */ - context = EVP_CIPHER_CTX_new(); + context = sym_EVP_CIPHER_CTX_new(); if (!context) return log_error_errno(SYNTHETIC_ERRNO(ENOMEM), "Failed to allocate encryption object: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (EVP_EncryptInit_ex(context, cc, NULL, md, iv.iov_base) != 1) + if (sym_EVP_EncryptInit_ex(context, cc, NULL, md, iv.iov_base) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); /* Just an upper estimate */ output.iov_len = @@ -1061,6 +1082,8 @@ int encrypt_credential_and_warn( ALIGN8(offsetof(struct metadata_credential_header, name) + strlen_ptr(name)) + input->iov_len + 2U * (size_t) bsz + tsz; + /* Silence static analyzers */ + assert(output.iov_len >= input->iov_len); output.iov_base = malloc0(output.iov_len); if (!output.iov_base) @@ -1113,9 +1136,9 @@ int encrypt_credential_and_warn( } /* Pass the encrypted + TPM2 header + scoped header as AAD */ - if (EVP_EncryptUpdate(context, NULL, &added, output.iov_base, p) != 1) + if (sym_EVP_EncryptUpdate(context, NULL, &added, output.iov_base, p) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); /* Now construct the metadata header */ ml = strlen_ptr(name); @@ -1129,27 +1152,27 @@ int encrypt_credential_and_warn( memcpy_safe(m->name, name, ml); /* And encrypt the metadata header */ - if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1) + if (sym_EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt metadata header: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); assert(added >= 0); assert((size_t) added <= output.iov_len - p); p += added; /* Then encrypt the plaintext */ - if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, input->iov_base, input->iov_len) != 1) + if (sym_EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, input->iov_base, input->iov_len) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt data: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); assert(added >= 0); assert((size_t) added <= output.iov_len - p); p += added; /* Finalize */ - if (EVP_EncryptFinal_ex(context, (uint8_t*) output.iov_base + p, &added) != 1) + if (sym_EVP_EncryptFinal_ex(context, (uint8_t*) output.iov_base + p, &added) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize data encryption: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); assert(added >= 0); assert((size_t) added <= output.iov_len - p); @@ -1158,9 +1181,9 @@ int encrypt_credential_and_warn( assert(p <= output.iov_len - tsz); /* Append tag */ - if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output.iov_base + p) != 1) + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output.iov_base + p) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get tag: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); p += tsz; assert(p <= output.iov_len); @@ -1204,6 +1227,8 @@ int decrypt_credential_and_warn( /* Only one of these two flags may be set at the same time */ assert(!FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL) || !FLAGS_SET(flags, CREDENTIAL_REFUSE_NULL)); + CLEANUP_ERASE(md); + /* Relevant error codes: * * -EBADMSG → Corrupted file @@ -1415,59 +1440,63 @@ int decrypt_credential_and_warn( return r; } - assert_se(cc = EVP_aes_256_gcm()); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + assert_se(cc = sym_EVP_aes_256_gcm()); /* Make sure cipher expectations match the header */ - if (EVP_CIPHER_key_length(cc) != (int) le32toh(h->key_size)) + if (sym_EVP_CIPHER_get_key_length(cc) != (int) le32toh(h->key_size)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected key size in header."); - if (EVP_CIPHER_block_size(cc) != (int) le32toh(h->block_size)) + if (sym_EVP_CIPHER_get_block_size(cc) != (int) le32toh(h->block_size)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected block size in header."); - context = EVP_CIPHER_CTX_new(); + context = sym_EVP_CIPHER_CTX_new(); if (!context) return log_error_errno(SYNTHETIC_ERRNO(ENOMEM), "Failed to allocate decryption object: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (EVP_DecryptInit_ex(context, cc, NULL, NULL, NULL) != 1) + if (sym_EVP_DecryptInit_ex(context, cc, NULL, NULL, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, le32toh(h->iv_size), NULL) != 1) + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, le32toh(h->iv_size), NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set IV size on decryption context: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (EVP_DecryptInit_ex(context, NULL, NULL, md, h->iv) != 1) + if (sym_EVP_DecryptInit_ex(context, NULL, NULL, md, h->iv) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set IV and key: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (EVP_DecryptUpdate(context, NULL, &added, input->iov_base, p) != 1) + if (sym_EVP_DecryptUpdate(context, NULL, &added, input->iov_base, p) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); plaintext.iov_base = malloc(input->iov_len - p - le32toh(h->tag_size)); if (!plaintext.iov_base) return -ENOMEM; - if (EVP_DecryptUpdate( + if (sym_EVP_DecryptUpdate( context, plaintext.iov_base, &added, (uint8_t*) input->iov_base + p, input->iov_len - p - le32toh(h->tag_size)) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt data: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); assert(added >= 0); assert((size_t) added <= input->iov_len - p - le32toh(h->tag_size)); plaintext.iov_len = added; - if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input->iov_base + input->iov_len - le32toh(h->tag_size)) != 1) + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input->iov_base + input->iov_len - le32toh(h->tag_size)) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set tag: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (EVP_DecryptFinal_ex(context, (uint8_t*) plaintext.iov_base + plaintext.iov_len, &added) != 1) + if (sym_EVP_DecryptFinal_ex(context, (uint8_t*) plaintext.iov_base + plaintext.iov_len, &added) != 1) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Decryption failed (incorrect key?): %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); plaintext.iov_len += added; @@ -1690,8 +1719,7 @@ int get_global_boot_credentials_path(char **ret) { /* path= */ NULL, /* unprivileged_mode= */ false, &path, - /* ret_uuid= */ NULL, - /* ret_devid= */ NULL); + /* ret_fd= */ NULL); if (r < 0) { if (r != -ENOKEY) return log_error_errno(r, "Failed to find XBOOTLDR partition: %m"); @@ -1701,11 +1729,7 @@ int get_global_boot_credentials_path(char **ret) { /* path= */ NULL, /* unprivileged_mode= */ false, &path, - /* ret_part= */ NULL, - /* ret_pstart= */ NULL, - /* ret_psize= */ NULL, - /* ret_uuid= */ NULL, - /* ret_devid= */ NULL); + /* ret_fd= */ NULL); if (r < 0) { if (r != -ENOKEY) return log_error_errno(r, "Failed to find ESP partition: %m"); diff --git a/src/shared/openssl-util.c b/src/shared/crypto-util.c similarity index 51% rename from src/shared/openssl-util.c rename to src/shared/crypto-util.c index 9e0b9987463d9..bf770f2432175 100644 --- a/src/shared/openssl-util.c +++ b/src/shared/crypto-util.c @@ -1,33 +1,634 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dlopen.h" + #include "alloc-util.h" #include "ask-password-api.h" +#include "crypto-util.h" +#include "dlfcn-util.h" #include "fd-util.h" #include "fileio.h" #include "hexdecoct.h" #include "log.h" #include "memory-util.h" #include "memstream-util.h" -#include "openssl-util.h" #include "random-util.h" #include "string-util.h" #include "strv.h" #if HAVE_OPENSSL -# include -# include +# include +# include +# include +# include # if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) # include +# endif + +# ifndef OPENSSL_NO_UI_CONSOLE +# include +# endif + +struct OpenSSLAskPasswordUI { + AskPasswordRequest request; +#ifndef OPENSSL_NO_UI_CONSOLE + UI_METHOD *method; +#endif +}; + +static void *libcrypto_dl = NULL; + +static DLSYM_PROTOTYPE(ASN1_INTEGER_dup) = NULL; +static DLSYM_PROTOTYPE(ASN1_INTEGER_free) = NULL; +static DLSYM_PROTOTYPE(ASN1_INTEGER_set) = NULL; +DLSYM_PROTOTYPE(ASN1_ANY_it) = NULL; +DLSYM_PROTOTYPE(ASN1_BIT_STRING_it) = NULL; +DLSYM_PROTOTYPE(ASN1_BMPSTRING_it) = NULL; +DLSYM_PROTOTYPE(ASN1_BMPSTRING_new) = NULL; +DLSYM_PROTOTYPE(ASN1_IA5STRING_it) = NULL; +DLSYM_PROTOTYPE(ASN1_OBJECT_it) = NULL; +DLSYM_PROTOTYPE(ASN1_OCTET_STRING_free) = NULL; +DLSYM_PROTOTYPE(ASN1_OCTET_STRING_it) = NULL; +DLSYM_PROTOTYPE(ASN1_OCTET_STRING_set) = NULL; +DLSYM_PROTOTYPE(ASN1_STRING_get0_data) = NULL; +DLSYM_PROTOTYPE(ASN1_STRING_length) = NULL; +DLSYM_PROTOTYPE(ASN1_STRING_new) = NULL; +DLSYM_PROTOTYPE(ASN1_STRING_set) = NULL; +DLSYM_PROTOTYPE(ASN1_STRING_set0) = NULL; +DLSYM_PROTOTYPE(ASN1_TIME_free) = NULL; +DLSYM_PROTOTYPE(ASN1_TIME_set) = NULL; +DLSYM_PROTOTYPE(ASN1_TYPE_new) = NULL; +DLSYM_PROTOTYPE(ASN1_get_object) = NULL; +DLSYM_PROTOTYPE(ASN1_item_d2i) = NULL; +DLSYM_PROTOTYPE(ASN1_item_free) = NULL; +DLSYM_PROTOTYPE(ASN1_item_i2d) = NULL; +DLSYM_PROTOTYPE(ASN1_item_new) = NULL; +DLSYM_PROTOTYPE(BIO_ctrl) = NULL; +DLSYM_PROTOTYPE(BIO_find_type) = NULL; +DLSYM_PROTOTYPE(BIO_free) = NULL; +DLSYM_PROTOTYPE(BIO_free_all) = NULL; +DLSYM_PROTOTYPE(BIO_new) = NULL; +DLSYM_PROTOTYPE(BIO_new_mem_buf) = NULL; +DLSYM_PROTOTYPE(BIO_new_socket) = NULL; +DLSYM_PROTOTYPE(BIO_s_mem) = NULL; +DLSYM_PROTOTYPE(BIO_write) = NULL; +DLSYM_PROTOTYPE(BN_CTX_free) = NULL; +DLSYM_PROTOTYPE(BN_CTX_new) = NULL; +DLSYM_PROTOTYPE(BN_bin2bn) = NULL; +static DLSYM_PROTOTYPE(BN_bn2bin) = NULL; +DLSYM_PROTOTYPE(BN_bn2nativepad) = NULL; +DLSYM_PROTOTYPE(BN_free) = NULL; +DLSYM_PROTOTYPE(BN_new) = NULL; +DLSYM_PROTOTYPE(BN_num_bits) = NULL; +DLSYM_PROTOTYPE(CRYPTO_free) = NULL; +DLSYM_PROTOTYPE(ECDSA_SIG_free) = NULL; +DLSYM_PROTOTYPE(EC_GROUP_free) = NULL; +DLSYM_PROTOTYPE(EC_GROUP_get0_generator) = NULL; +DLSYM_PROTOTYPE(EC_GROUP_get0_order) = NULL; +DLSYM_PROTOTYPE(EC_GROUP_get_curve) = NULL; +DLSYM_PROTOTYPE(EC_GROUP_get_curve_name) = NULL; +DLSYM_PROTOTYPE(EC_GROUP_get_field_type) = NULL; +DLSYM_PROTOTYPE(EC_GROUP_new_by_curve_name) = NULL; +DLSYM_PROTOTYPE(EC_POINT_free) = NULL; +DLSYM_PROTOTYPE(EC_POINT_new) = NULL; +DLSYM_PROTOTYPE(EC_POINT_oct2point) = NULL; +static DLSYM_PROTOTYPE(EC_POINT_point2buf) = NULL; +DLSYM_PROTOTYPE(EC_POINT_point2oct) = NULL; +static DLSYM_PROTOTYPE(EC_POINT_set_affine_coordinates) = NULL; +DLSYM_PROTOTYPE(ERR_clear_error) = NULL; +DLSYM_PROTOTYPE(ERR_error_string) = NULL; +DLSYM_PROTOTYPE(ERR_error_string_n) = NULL; +DLSYM_PROTOTYPE(ERR_get_error) = NULL; +static DLSYM_PROTOTYPE(ERR_peek_last_error) = NULL; +DLSYM_PROTOTYPE(EVP_CIPHER_CTX_ctrl) = NULL; +DLSYM_PROTOTYPE(EVP_CIPHER_CTX_free) = NULL; +static DLSYM_PROTOTYPE(EVP_CIPHER_CTX_get_block_size) = NULL; +DLSYM_PROTOTYPE(EVP_CIPHER_CTX_new) = NULL; +static DLSYM_PROTOTYPE(EVP_CIPHER_fetch) = NULL; +DLSYM_PROTOTYPE(EVP_CIPHER_free) = NULL; +DLSYM_PROTOTYPE(EVP_CIPHER_get_block_size) = NULL; +DLSYM_PROTOTYPE(EVP_CIPHER_get_iv_length) = NULL; +DLSYM_PROTOTYPE(EVP_CIPHER_get_key_length) = NULL; +DLSYM_PROTOTYPE(EVP_DecryptFinal_ex) = NULL; +DLSYM_PROTOTYPE(EVP_DecryptInit_ex) = NULL; +DLSYM_PROTOTYPE(EVP_DecryptUpdate) = NULL; +DLSYM_PROTOTYPE(EVP_Digest) = NULL; +DLSYM_PROTOTYPE(EVP_DigestFinal_ex) = NULL; +DLSYM_PROTOTYPE(EVP_DigestInit_ex) = NULL; +static DLSYM_PROTOTYPE(EVP_DigestSign) = NULL; +static DLSYM_PROTOTYPE(EVP_DigestSignInit) = NULL; +DLSYM_PROTOTYPE(EVP_DigestUpdate) = NULL; +DLSYM_PROTOTYPE(EVP_DigestVerify) = NULL; +DLSYM_PROTOTYPE(EVP_DigestVerifyInit) = NULL; +DLSYM_PROTOTYPE(EVP_EncryptFinal_ex) = NULL; +static DLSYM_PROTOTYPE(EVP_EncryptInit) = NULL; +DLSYM_PROTOTYPE(EVP_EncryptInit_ex) = NULL; +DLSYM_PROTOTYPE(EVP_EncryptUpdate) = NULL; +static DLSYM_PROTOTYPE(EVP_KDF_CTX_free) = NULL; +static DLSYM_PROTOTYPE(EVP_KDF_CTX_new) = NULL; +static DLSYM_PROTOTYPE(EVP_KDF_derive) = NULL; +static DLSYM_PROTOTYPE(EVP_KDF_fetch) = NULL; +static DLSYM_PROTOTYPE(EVP_KDF_free) = NULL; +DLSYM_PROTOTYPE(EVP_MAC_CTX_free) = NULL; +static DLSYM_PROTOTYPE(EVP_MAC_CTX_get_mac_size) = NULL; +static DLSYM_PROTOTYPE(EVP_MAC_CTX_new) = NULL; +static DLSYM_PROTOTYPE(EVP_MAC_fetch) = NULL; +static DLSYM_PROTOTYPE(EVP_MAC_final) = NULL; +DLSYM_PROTOTYPE(EVP_MAC_free) = NULL; +static DLSYM_PROTOTYPE(EVP_MAC_init) = NULL; +static DLSYM_PROTOTYPE(EVP_MAC_update) = NULL; +DLSYM_PROTOTYPE(EVP_MD_CTX_free) = NULL; +DLSYM_PROTOTYPE(EVP_MD_CTX_get0_md) = NULL; +DLSYM_PROTOTYPE(EVP_MD_CTX_new) = NULL; +DLSYM_PROTOTYPE(EVP_MD_CTX_set_pkey_ctx) = NULL; +static DLSYM_PROTOTYPE(EVP_MD_fetch) = NULL; +DLSYM_PROTOTYPE(EVP_MD_free) = NULL; +DLSYM_PROTOTYPE(EVP_MD_get0_name) = NULL; +DLSYM_PROTOTYPE(EVP_MD_get_size) = NULL; +static DLSYM_PROTOTYPE(EVP_MD_get_type) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_CTX_free) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_CTX_new) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_CTX_new_from_name) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_CTX_new_id) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_CTX_set0_rsa_oaep_label) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_ec_paramgen_curve_nid) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_rsa_oaep_md) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_rsa_padding) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_signature_md) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_derive) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_derive_init) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_derive_set_peer) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_encrypt) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_encrypt_init) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_eq) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_free) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_fromdata) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_fromdata_init) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_get1_encoded_public_key) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_get_base_id) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_get_bits) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_get_bn_param) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_get_group_name) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_get_id) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_get_utf8_string_param) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_keygen) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_keygen_init) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_new) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_new_raw_public_key) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_verify) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_verify_init) = NULL; +DLSYM_PROTOTYPE(EVP_aes_256_ctr) = NULL; +DLSYM_PROTOTYPE(EVP_aes_256_gcm) = NULL; +DLSYM_PROTOTYPE(EVP_get_cipherbyname) = NULL; +DLSYM_PROTOTYPE(EVP_get_digestbyname) = NULL; +DLSYM_PROTOTYPE(EVP_sha1) = NULL; +DLSYM_PROTOTYPE(EVP_sha256) = NULL; +DLSYM_PROTOTYPE(EVP_sha384) = NULL; +DLSYM_PROTOTYPE(EVP_sha512) = NULL; +DLSYM_PROTOTYPE(HMAC) = NULL; +DLSYM_PROTOTYPE(SHA1) = NULL; +DLSYM_PROTOTYPE(SHA512) = NULL; +DLSYM_PROTOTYPE(OBJ_nid2obj) = NULL; +DLSYM_PROTOTYPE(OBJ_nid2sn) = NULL; +DLSYM_PROTOTYPE(OBJ_sn2nid) = NULL; +DLSYM_PROTOTYPE(OBJ_txt2obj) = NULL; +DLSYM_PROTOTYPE(OPENSSL_sk_new_null) = NULL; +DLSYM_PROTOTYPE(OPENSSL_sk_num) = NULL; +DLSYM_PROTOTYPE(OPENSSL_sk_pop_free) = NULL; +DLSYM_PROTOTYPE(OPENSSL_sk_push) = NULL; +DLSYM_PROTOTYPE(OPENSSL_sk_value) = NULL; +DLSYM_PROTOTYPE(OSSL_EC_curve_nid2name) = NULL; +static DLSYM_PROTOTYPE(OSSL_PARAM_BLD_new) = NULL; +static DLSYM_PROTOTYPE(OSSL_PARAM_BLD_free) = NULL; +static DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_octet_string) = NULL; +static DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_utf8_string) = NULL; +static DLSYM_PROTOTYPE(OSSL_PARAM_BLD_to_param) = NULL; +DLSYM_PROTOTYPE(OSSL_PARAM_construct_BN) = NULL; +DLSYM_PROTOTYPE(OSSL_PARAM_construct_end) = NULL; +DLSYM_PROTOTYPE(OSSL_PARAM_construct_octet_string) = NULL; +DLSYM_PROTOTYPE(OSSL_PARAM_construct_utf8_string) = NULL; +DLSYM_PROTOTYPE(OSSL_PARAM_free) = NULL; +static DLSYM_PROTOTYPE(OSSL_PROVIDER_try_load) = NULL; +static DLSYM_PROTOTYPE(OSSL_STORE_INFO_free) = NULL; +static DLSYM_PROTOTYPE(OSSL_STORE_INFO_get1_CERT) = NULL; +static DLSYM_PROTOTYPE(OSSL_STORE_INFO_get1_PKEY) = NULL; +static DLSYM_PROTOTYPE(OSSL_STORE_close) = NULL; +static DLSYM_PROTOTYPE(OSSL_STORE_expect) = NULL; +static DLSYM_PROTOTYPE(OSSL_STORE_load) = NULL; +static DLSYM_PROTOTYPE(OSSL_STORE_open) = NULL; +DLSYM_PROTOTYPE(PEM_read_PUBKEY) = NULL; +DLSYM_PROTOTYPE(PEM_read_PrivateKey) = NULL; +DLSYM_PROTOTYPE(PEM_read_X509) = NULL; +static DLSYM_PROTOTYPE(PEM_read_bio_PrivateKey) = NULL; +static DLSYM_PROTOTYPE(PEM_read_bio_X509) = NULL; +DLSYM_PROTOTYPE(PKCS5_PBKDF2_HMAC) = NULL; +DLSYM_PROTOTYPE(PEM_write_PUBKEY) = NULL; +DLSYM_PROTOTYPE(PEM_write_PrivateKey) = NULL; +DLSYM_PROTOTYPE(PEM_write_X509) = NULL; +DLSYM_PROTOTYPE(PKCS7_SIGNER_INFO_free) = NULL; +DLSYM_PROTOTYPE(PKCS7_ATTR_SIGN_it) = NULL; +static DLSYM_PROTOTYPE(PKCS7_SIGNER_INFO_new) = NULL; +static DLSYM_PROTOTYPE(PKCS7_SIGNER_INFO_set) = NULL; +DLSYM_PROTOTYPE(PKCS7_add0_attrib_signing_time) = NULL; +DLSYM_PROTOTYPE(PKCS7_add1_attrib_digest) = NULL; +DLSYM_PROTOTYPE(PKCS7_add_attrib_content_type) = NULL; +DLSYM_PROTOTYPE(PKCS7_add_attrib_smimecap) = NULL; +static DLSYM_PROTOTYPE(PKCS7_add_certificate) = NULL; +DLSYM_PROTOTYPE(PKCS7_add_signed_attribute) = NULL; +static DLSYM_PROTOTYPE(PKCS7_add_signer) = NULL; +DLSYM_PROTOTYPE(PKCS7_content_new) = NULL; +DLSYM_PROTOTYPE(PKCS7_ctrl) = NULL; +DLSYM_PROTOTYPE(PKCS7_dataFinal) = NULL; +DLSYM_PROTOTYPE(PKCS7_dataInit) = NULL; +DLSYM_PROTOTYPE(PKCS7_free) = NULL; +DLSYM_PROTOTYPE(PKCS7_get_signer_info) = NULL; +DLSYM_PROTOTYPE(PKCS7_new) = NULL; +DLSYM_PROTOTYPE(PKCS7_set_content) = NULL; +static DLSYM_PROTOTYPE(PKCS7_set_type) = NULL; +DLSYM_PROTOTYPE(PKCS7_sign) = NULL; +DLSYM_PROTOTYPE(PKCS7_verify) = NULL; +static DLSYM_PROTOTYPE(X509_ALGOR_set0) = NULL; +DLSYM_PROTOTYPE(X509_NAME_free) = NULL; +DLSYM_PROTOTYPE(X509_ALGOR_free) = NULL; +DLSYM_PROTOTYPE(X509_ATTRIBUTE_free) = NULL; +DLSYM_PROTOTYPE(X509_NAME_oneline) = NULL; +static DLSYM_PROTOTYPE(X509_NAME_set) = NULL; +DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set1_host) = NULL; +DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set1_ip) = NULL; +DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set_hostflags) = NULL; +DLSYM_PROTOTYPE(X509_free) = NULL; +DLSYM_PROTOTYPE(X509_gmtime_adj) = NULL; +static DLSYM_PROTOTYPE(X509_get0_serialNumber) = NULL; +static DLSYM_PROTOTYPE(X509_get_issuer_name) = NULL; +DLSYM_PROTOTYPE(X509_get_pubkey) = NULL; +static DLSYM_PROTOTYPE(X509_get_signature_info) = NULL; +DLSYM_PROTOTYPE(X509_get_subject_name) = NULL; +DLSYM_PROTOTYPE(d2i_ASN1_OCTET_STRING) = NULL; +DLSYM_PROTOTYPE(d2i_ECPKParameters) = NULL; +DLSYM_PROTOTYPE(d2i_PKCS7) = NULL; +DLSYM_PROTOTYPE(d2i_PUBKEY) = NULL; +DLSYM_PROTOTYPE(d2i_X509) = NULL; +DLSYM_PROTOTYPE(i2d_ASN1_INTEGER) = NULL; +DLSYM_PROTOTYPE(i2d_PKCS7) = NULL; +DLSYM_PROTOTYPE(i2d_PKCS7_fp) = NULL; +DLSYM_PROTOTYPE(i2d_PUBKEY) = NULL; +static DLSYM_PROTOTYPE(i2d_PUBKEY_fp) = NULL; +static DLSYM_PROTOTYPE(i2d_PublicKey) = NULL; +DLSYM_PROTOTYPE(i2d_X509) = NULL; +DLSYM_PROTOTYPE(i2d_X509_NAME) = NULL; + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(OSSL_PARAM_BLD*, sym_OSSL_PARAM_BLD_free, OSSL_PARAM_BLD_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(OSSL_STORE_CTX*, sym_OSSL_STORE_close, OSSL_STORE_closep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(OSSL_STORE_INFO*, sym_OSSL_STORE_INFO_free, OSSL_STORE_INFO_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_KDF*, sym_EVP_KDF_free, EVP_KDF_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_KDF_CTX*, sym_EVP_KDF_CTX_free, EVP_KDF_CTX_freep, NULL); + +#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) DISABLE_WARNING_DEPRECATED_DECLARATIONS; -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ENGINE*, ENGINE_free, NULL); +static DLSYM_PROTOTYPE(ENGINE_by_id) = NULL; +static DLSYM_PROTOTYPE(ENGINE_free) = NULL; +static DLSYM_PROTOTYPE(ENGINE_init) = NULL; +static DLSYM_PROTOTYPE(ENGINE_load_private_key) = NULL; REENABLE_WARNING; -# endif + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ENGINE*, sym_ENGINE_free, ENGINE_freep, NULL); +#endif + +#if !defined(OPENSSL_NO_DEPRECATED_3_0) +DISABLE_WARNING_DEPRECATED_DECLARATIONS; +DLSYM_PROTOTYPE(ECDSA_SIG_new) = NULL; +DLSYM_PROTOTYPE(ECDSA_SIG_set0) = NULL; +DLSYM_PROTOTYPE(ECDSA_do_verify) = NULL; +DLSYM_PROTOTYPE(EC_KEY_check_key) = NULL; +DLSYM_PROTOTYPE(EC_KEY_free) = NULL; +DLSYM_PROTOTYPE(EC_KEY_new) = NULL; +DLSYM_PROTOTYPE(EC_KEY_set_group) = NULL; +DLSYM_PROTOTYPE(EC_KEY_set_public_key) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_assign) = NULL; +DLSYM_PROTOTYPE(RSA_free) = NULL; +DLSYM_PROTOTYPE(RSA_new) = NULL; +DLSYM_PROTOTYPE(RSA_set0_key) = NULL; +DLSYM_PROTOTYPE(RSA_size) = NULL; +DLSYM_PROTOTYPE(RSAPublicKey_dup) = NULL; +REENABLE_WARNING; +#endif #ifndef OPENSSL_NO_UI_CONSOLE -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UI_METHOD*, UI_destroy_method, NULL); +static DLSYM_PROTOTYPE(UI_OpenSSL) = NULL; +static DLSYM_PROTOTYPE(UI_create_method) = NULL; +static DLSYM_PROTOTYPE(UI_destroy_method) = NULL; +static DLSYM_PROTOTYPE(UI_get0_output_string) = NULL; +static DLSYM_PROTOTYPE(UI_get_default_method) = NULL; +static DLSYM_PROTOTYPE(UI_get_method) = NULL; +static DLSYM_PROTOTYPE(UI_get_string_type) = NULL; +static DLSYM_PROTOTYPE(UI_method_get_ex_data) = NULL; +static DLSYM_PROTOTYPE(UI_method_get_reader) = NULL; +static DLSYM_PROTOTYPE(UI_method_set_ex_data) = NULL; +static DLSYM_PROTOTYPE(UI_method_set_reader) = NULL; +static DLSYM_PROTOTYPE(UI_set_default_method) = NULL; +static DLSYM_PROTOTYPE(UI_set_result) = NULL; + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(UI_METHOD*, sym_UI_destroy_method, UI_destroy_methodp, NULL); +#endif + #endif +int dlopen_libcrypto(int log_level) { +#if HAVE_OPENSSL + SD_ELF_NOTE_DLOPEN( + "libcrypto", + "Support for cryptographic operations", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libcrypto.so.3"); + + return dlopen_many_sym_or_warn( + &libcrypto_dl, + "libcrypto.so.3", + log_level, + DLSYM_ARG(ASN1_ANY_it), + DLSYM_ARG(ASN1_BIT_STRING_it), + DLSYM_ARG(ASN1_BMPSTRING_it), + DLSYM_ARG(ASN1_BMPSTRING_new), + DLSYM_ARG(ASN1_get_object), + DLSYM_ARG(ASN1_IA5STRING_it), + DLSYM_ARG(ASN1_INTEGER_dup), + DLSYM_ARG(ASN1_INTEGER_free), + DLSYM_ARG(ASN1_INTEGER_set), + DLSYM_ARG(ASN1_item_d2i), + DLSYM_ARG(ASN1_item_free), + DLSYM_ARG(ASN1_item_i2d), + DLSYM_ARG(ASN1_item_new), + DLSYM_ARG(ASN1_OBJECT_it), + DLSYM_ARG(ASN1_OCTET_STRING_free), + DLSYM_ARG(ASN1_OCTET_STRING_it), + DLSYM_ARG(ASN1_OCTET_STRING_set), + DLSYM_ARG(ASN1_STRING_get0_data), + DLSYM_ARG(ASN1_STRING_length), + DLSYM_ARG(ASN1_STRING_new), + DLSYM_ARG(ASN1_STRING_set), + DLSYM_ARG(ASN1_STRING_set0), + DLSYM_ARG(ASN1_TIME_free), + DLSYM_ARG(ASN1_TIME_set), + DLSYM_ARG(ASN1_TYPE_new), + DLSYM_ARG(BIO_ctrl), + DLSYM_ARG(BIO_find_type), + DLSYM_ARG(BIO_free_all), + DLSYM_ARG(BIO_free), + DLSYM_ARG(BIO_new_mem_buf), + DLSYM_ARG(BIO_new_socket), + DLSYM_ARG(BIO_new), + DLSYM_ARG(BIO_s_mem), + DLSYM_ARG(BIO_write), + DLSYM_ARG(BN_bin2bn), + DLSYM_ARG(BN_bn2bin), + DLSYM_ARG(BN_bn2nativepad), + DLSYM_ARG(BN_CTX_free), + DLSYM_ARG(BN_CTX_new), + DLSYM_ARG(BN_free), + DLSYM_ARG(BN_new), + DLSYM_ARG(BN_num_bits), + DLSYM_ARG(CRYPTO_free), + DLSYM_ARG(d2i_ASN1_OCTET_STRING), + DLSYM_ARG(d2i_ECPKParameters), + DLSYM_ARG(d2i_PKCS7), + DLSYM_ARG(d2i_PUBKEY), + DLSYM_ARG(d2i_X509), + DLSYM_ARG(EC_GROUP_free), + DLSYM_ARG(EC_GROUP_get_curve_name), + DLSYM_ARG(EC_GROUP_get_curve), + DLSYM_ARG(EC_GROUP_get_field_type), + DLSYM_ARG(EC_GROUP_get0_generator), + DLSYM_ARG(EC_GROUP_get0_order), + DLSYM_ARG(EC_GROUP_new_by_curve_name), + DLSYM_ARG(EC_POINT_free), + DLSYM_ARG(EC_POINT_new), + DLSYM_ARG(EC_POINT_oct2point), + DLSYM_ARG(EC_POINT_point2buf), + DLSYM_ARG(EC_POINT_point2oct), + DLSYM_ARG(EC_POINT_set_affine_coordinates), + DLSYM_ARG(ECDSA_SIG_free), +#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) + DLSYM_ARG_FORCE(ENGINE_by_id), + DLSYM_ARG_FORCE(ENGINE_free), + DLSYM_ARG_FORCE(ENGINE_init), + DLSYM_ARG_FORCE(ENGINE_load_private_key), +#endif +#if !defined(OPENSSL_NO_DEPRECATED_3_0) + DLSYM_ARG_FORCE(EC_KEY_check_key), + DLSYM_ARG_FORCE(EC_KEY_free), + DLSYM_ARG_FORCE(EC_KEY_new), + DLSYM_ARG_FORCE(EC_KEY_set_group), + DLSYM_ARG_FORCE(EC_KEY_set_public_key), + DLSYM_ARG_FORCE(ECDSA_do_verify), + DLSYM_ARG_FORCE(ECDSA_SIG_new), + DLSYM_ARG_FORCE(ECDSA_SIG_set0), + DLSYM_ARG_FORCE(EVP_PKEY_assign), + DLSYM_ARG_FORCE(RSA_free), + DLSYM_ARG_FORCE(RSA_new), + DLSYM_ARG_FORCE(RSA_set0_key), + DLSYM_ARG_FORCE(RSA_size), + DLSYM_ARG_FORCE(RSAPublicKey_dup), +#endif + DLSYM_ARG(ERR_clear_error), + DLSYM_ARG(ERR_error_string_n), + DLSYM_ARG(ERR_error_string), + DLSYM_ARG(ERR_get_error), + DLSYM_ARG(ERR_peek_last_error), + DLSYM_ARG(EVP_aes_256_ctr), + DLSYM_ARG(EVP_aes_256_gcm), + DLSYM_ARG(EVP_CIPHER_CTX_ctrl), + DLSYM_ARG(EVP_CIPHER_CTX_free), + DLSYM_ARG(EVP_CIPHER_CTX_get_block_size), + DLSYM_ARG(EVP_CIPHER_CTX_new), + DLSYM_ARG(EVP_CIPHER_fetch), + DLSYM_ARG(EVP_CIPHER_free), + DLSYM_ARG(EVP_CIPHER_get_block_size), + DLSYM_ARG(EVP_CIPHER_get_iv_length), + DLSYM_ARG(EVP_CIPHER_get_key_length), + DLSYM_ARG(EVP_DecryptFinal_ex), + DLSYM_ARG(EVP_DecryptInit_ex), + DLSYM_ARG(EVP_DecryptUpdate), + DLSYM_ARG(EVP_Digest), + DLSYM_ARG(EVP_DigestFinal_ex), + DLSYM_ARG(EVP_DigestInit_ex), + DLSYM_ARG(EVP_DigestSign), + DLSYM_ARG(EVP_DigestSignInit), + DLSYM_ARG(EVP_DigestUpdate), + DLSYM_ARG(EVP_DigestVerify), + DLSYM_ARG(EVP_DigestVerifyInit), + DLSYM_ARG(EVP_EncryptFinal_ex), + DLSYM_ARG(EVP_EncryptInit_ex), + DLSYM_ARG(EVP_EncryptInit), + DLSYM_ARG(EVP_EncryptUpdate), + DLSYM_ARG(EVP_get_cipherbyname), + DLSYM_ARG(EVP_get_digestbyname), + DLSYM_ARG(EVP_KDF_CTX_free), + DLSYM_ARG(EVP_KDF_CTX_new), + DLSYM_ARG(EVP_KDF_derive), + DLSYM_ARG(EVP_KDF_fetch), + DLSYM_ARG(EVP_KDF_free), + DLSYM_ARG(EVP_MAC_CTX_free), + DLSYM_ARG(EVP_MAC_CTX_get_mac_size), + DLSYM_ARG(EVP_MAC_CTX_new), + DLSYM_ARG(EVP_MAC_fetch), + DLSYM_ARG(EVP_MAC_final), + DLSYM_ARG(EVP_MAC_free), + DLSYM_ARG(EVP_MAC_init), + DLSYM_ARG(EVP_MAC_update), + DLSYM_ARG(EVP_MD_CTX_free), + DLSYM_ARG(EVP_MD_CTX_get0_md), + DLSYM_ARG(EVP_MD_CTX_new), + DLSYM_ARG(EVP_MD_CTX_set_pkey_ctx), + DLSYM_ARG(EVP_MD_fetch), + DLSYM_ARG(EVP_MD_free), + DLSYM_ARG(EVP_MD_get_size), + DLSYM_ARG(EVP_MD_get_type), + DLSYM_ARG(EVP_MD_get0_name), + DLSYM_ARG(EVP_PKEY_CTX_free), + DLSYM_ARG(EVP_PKEY_CTX_new_from_name), + DLSYM_ARG(EVP_PKEY_CTX_new_id), + DLSYM_ARG(EVP_PKEY_CTX_new), + DLSYM_ARG(EVP_PKEY_CTX_set_ec_paramgen_curve_nid), + DLSYM_ARG(EVP_PKEY_CTX_set_rsa_oaep_md), + DLSYM_ARG(EVP_PKEY_CTX_set_rsa_padding), + DLSYM_ARG(EVP_PKEY_CTX_set_signature_md), + DLSYM_ARG(EVP_PKEY_CTX_set0_rsa_oaep_label), + DLSYM_ARG(EVP_PKEY_derive_init), + DLSYM_ARG(EVP_PKEY_derive_set_peer), + DLSYM_ARG(EVP_PKEY_derive), + DLSYM_ARG(EVP_PKEY_encrypt_init), + DLSYM_ARG(EVP_PKEY_encrypt), + DLSYM_ARG(EVP_PKEY_eq), + DLSYM_ARG(EVP_PKEY_free), + DLSYM_ARG(EVP_PKEY_fromdata_init), + DLSYM_ARG(EVP_PKEY_fromdata), + DLSYM_ARG(EVP_PKEY_get_base_id), + DLSYM_ARG(EVP_PKEY_get_bits), + DLSYM_ARG(EVP_PKEY_get_bn_param), + DLSYM_ARG(EVP_PKEY_get_group_name), + DLSYM_ARG(EVP_PKEY_get_id), + DLSYM_ARG(EVP_PKEY_get_utf8_string_param), + DLSYM_ARG(EVP_PKEY_get1_encoded_public_key), + DLSYM_ARG(EVP_PKEY_keygen_init), + DLSYM_ARG(EVP_PKEY_keygen), + DLSYM_ARG(EVP_PKEY_new_raw_public_key), + DLSYM_ARG(EVP_PKEY_new), + DLSYM_ARG(EVP_PKEY_verify_init), + DLSYM_ARG(EVP_PKEY_verify), + DLSYM_ARG(EVP_sha1), + DLSYM_ARG(EVP_sha256), + DLSYM_ARG(EVP_sha384), + DLSYM_ARG(EVP_sha512), + DLSYM_ARG(HMAC), + DLSYM_ARG(i2d_ASN1_INTEGER), + DLSYM_ARG(i2d_PKCS7_fp), + DLSYM_ARG(i2d_PKCS7), + DLSYM_ARG(i2d_PUBKEY_fp), + DLSYM_ARG(i2d_PUBKEY), + DLSYM_ARG(i2d_PublicKey), + DLSYM_ARG(i2d_X509_NAME), + DLSYM_ARG(i2d_X509), + DLSYM_ARG(OBJ_nid2obj), + DLSYM_ARG(OBJ_nid2sn), + DLSYM_ARG(OBJ_sn2nid), + DLSYM_ARG(OBJ_txt2obj), + DLSYM_ARG(OPENSSL_sk_new_null), + DLSYM_ARG(OPENSSL_sk_num), + DLSYM_ARG(OPENSSL_sk_pop_free), + DLSYM_ARG(OPENSSL_sk_push), + DLSYM_ARG(OPENSSL_sk_value), + DLSYM_ARG(OSSL_EC_curve_nid2name), + DLSYM_ARG(OSSL_PARAM_BLD_free), + DLSYM_ARG(OSSL_PARAM_BLD_new), + DLSYM_ARG(OSSL_PARAM_BLD_push_octet_string), + DLSYM_ARG(OSSL_PARAM_BLD_push_utf8_string), + DLSYM_ARG(OSSL_PARAM_BLD_to_param), + DLSYM_ARG(OSSL_PARAM_construct_BN), + DLSYM_ARG(OSSL_PARAM_construct_end), + DLSYM_ARG(OSSL_PARAM_construct_octet_string), + DLSYM_ARG(OSSL_PARAM_construct_utf8_string), + DLSYM_ARG(OSSL_PARAM_free), + DLSYM_ARG(OSSL_PROVIDER_try_load), + DLSYM_ARG(OSSL_STORE_close), + DLSYM_ARG(OSSL_STORE_expect), + DLSYM_ARG(OSSL_STORE_INFO_free), + DLSYM_ARG(OSSL_STORE_INFO_get1_CERT), + DLSYM_ARG(OSSL_STORE_INFO_get1_PKEY), + DLSYM_ARG(OSSL_STORE_load), + DLSYM_ARG(OSSL_STORE_open), + DLSYM_ARG(PEM_read_bio_PrivateKey), + DLSYM_ARG(PEM_read_bio_X509), + DLSYM_ARG(PEM_read_PrivateKey), + DLSYM_ARG(PEM_read_PUBKEY), + DLSYM_ARG(PEM_read_X509), + DLSYM_ARG(PEM_write_PrivateKey), + DLSYM_ARG(PEM_write_PUBKEY), + DLSYM_ARG(PEM_write_X509), + DLSYM_ARG(PKCS5_PBKDF2_HMAC), + DLSYM_ARG(PKCS7_add_attrib_content_type), + DLSYM_ARG(PKCS7_add_attrib_smimecap), + DLSYM_ARG(PKCS7_add_certificate), + DLSYM_ARG(PKCS7_add_signed_attribute), + DLSYM_ARG(PKCS7_add_signer), + DLSYM_ARG(PKCS7_add0_attrib_signing_time), + DLSYM_ARG(PKCS7_add1_attrib_digest), + DLSYM_ARG(PKCS7_ATTR_SIGN_it), + DLSYM_ARG(PKCS7_content_new), + DLSYM_ARG(PKCS7_ctrl), + DLSYM_ARG(PKCS7_dataFinal), + DLSYM_ARG(PKCS7_dataInit), + DLSYM_ARG(PKCS7_free), + DLSYM_ARG(PKCS7_get_signer_info), + DLSYM_ARG(PKCS7_new), + DLSYM_ARG(PKCS7_set_content), + DLSYM_ARG(PKCS7_set_type), + DLSYM_ARG(PKCS7_sign), + DLSYM_ARG(PKCS7_SIGNER_INFO_free), + DLSYM_ARG(PKCS7_SIGNER_INFO_new), + DLSYM_ARG(PKCS7_SIGNER_INFO_set), + DLSYM_ARG(PKCS7_verify), + DLSYM_ARG(SHA1), + DLSYM_ARG(SHA512), +#ifndef OPENSSL_NO_UI_CONSOLE + DLSYM_ARG(UI_create_method), + DLSYM_ARG(UI_destroy_method), + DLSYM_ARG(UI_get_default_method), + DLSYM_ARG(UI_get_method), + DLSYM_ARG(UI_get_string_type), + DLSYM_ARG(UI_get0_output_string), + DLSYM_ARG(UI_method_get_ex_data), + DLSYM_ARG(UI_method_get_reader), + DLSYM_ARG(UI_method_set_ex_data), + DLSYM_ARG(UI_method_set_reader), + DLSYM_ARG(UI_OpenSSL), + DLSYM_ARG(UI_set_default_method), + DLSYM_ARG(UI_set_result), +#endif + DLSYM_ARG(X509_ALGOR_free), + DLSYM_ARG(X509_ALGOR_set0), + DLSYM_ARG(X509_ATTRIBUTE_free), + DLSYM_ARG(X509_free), + DLSYM_ARG(X509_get_issuer_name), + DLSYM_ARG(X509_get_pubkey), + DLSYM_ARG(X509_get_signature_info), + DLSYM_ARG(X509_get_subject_name), + DLSYM_ARG(X509_get0_serialNumber), + DLSYM_ARG(X509_gmtime_adj), + DLSYM_ARG(X509_NAME_free), + DLSYM_ARG(X509_NAME_oneline), + DLSYM_ARG(X509_NAME_set), + DLSYM_ARG(X509_VERIFY_PARAM_set_hostflags), + DLSYM_ARG(X509_VERIFY_PARAM_set1_host), + DLSYM_ARG(X509_VERIFY_PARAM_set1_ip)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libcrypto support is not compiled in."); +#endif +} + +#if HAVE_OPENSSL + /* For each error in the OpenSSL thread error queue, log the provided message and the OpenSSL error * string. If there are no errors in the OpenSSL thread queue, this logs the message with "No OpenSSL * errors." This logs at level debug. Returns -EIO (or -ENOMEM). */ @@ -45,19 +646,25 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UI_METHOD*, UI_destroy_method, NULL); ({ \ int UNIQ_T(R, u) = 0; \ for (;;) { \ - unsigned long UNIQ_T(E, u) = ERR_get_error(); \ + unsigned long UNIQ_T(E, u) = sym_ERR_get_error(); \ if (UNIQ_T(E, u) == 0) \ break; \ - ERR_error_string_n(UNIQ_T(E, u), buf, max); \ + sym_ERR_error_string_n(UNIQ_T(E, u), buf, max); \ UNIQ_T(R, u) = log_debug_errno(SYNTHETIC_ERRNO(EIO), fmt ": %s", ##__VA_ARGS__, buf); \ } \ UNIQ_T(R, u); \ }) int openssl_pubkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret) { + int r; + assert(pem); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + if (pem_size == SIZE_MAX) pem_size = strlen(pem); @@ -66,7 +673,7 @@ int openssl_pubkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret) { if (!f) return log_oom_debug(); - _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = PEM_read_PUBKEY(f, /* x= */ NULL, /* pam_password_cb= */ NULL, /* userdata= */ NULL); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = sym_PEM_read_PUBKEY(f, /* x= */ NULL, /* pam_password_cb= */ NULL, /* userdata= */ NULL); if (!pkey) return log_openssl_errors("Failed to parse PEM"); @@ -75,15 +682,21 @@ int openssl_pubkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret) { } int openssl_pubkey_to_pem(EVP_PKEY *pkey, char **ret) { + int r; + assert(pkey); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + _cleanup_(memstream_done) MemStream m = {}; FILE *f = memstream_init(&m); if (!f) return -ENOMEM; - if (PEM_write_PUBKEY(f, pkey) <= 0) + if (sym_PEM_write_PUBKEY(f, pkey) <= 0) return -EIO; return memstream_finalize(&m, ret, /* ret_size= */ NULL); @@ -94,15 +707,21 @@ int openssl_pubkey_to_pem(EVP_PKEY *pkey, char **ret) { * e.g. shake128. Returns 0 on success, -EOPNOTSUPP if the algorithm is not supported, or < 0 for any other * error. */ int openssl_digest_size(const char *digest_alg, size_t *ret_digest_size) { + int r; + assert(digest_alg); assert(ret_digest_size); - _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_MD_freep) EVP_MD *md = sym_EVP_MD_fetch(NULL, digest_alg, NULL); if (!md) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Digest algorithm '%s' not supported.", digest_alg); - size_t digest_size = EVP_MD_get_size(md); + size_t digest_size = sym_EVP_MD_get_size(md); if (digest_size == 0) return log_openssl_errors("Failed to get Digest size"); @@ -127,20 +746,24 @@ int openssl_digest_many( assert(ret_digest); /* ret_digest_size is optional, as caller may already know the digest size */ - _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_MD_freep) EVP_MD *md = sym_EVP_MD_fetch(NULL, digest_alg, NULL); if (!md) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Digest algorithm '%s' not supported.", digest_alg); - _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = sym_EVP_MD_CTX_new(); if (!ctx) return log_openssl_errors("Failed to create new EVP_MD_CTX"); - if (!EVP_DigestInit_ex(ctx, md, NULL)) + if (!sym_EVP_DigestInit_ex(ctx, md, NULL)) return log_openssl_errors("Failed to initialize EVP_MD_CTX"); for (size_t i = 0; i < n_data; i++) - if (!EVP_DigestUpdate(ctx, data[i].iov_base, data[i].iov_len)) + if (!sym_EVP_DigestUpdate(ctx, data[i].iov_base, data[i].iov_len)) return log_openssl_errors("Failed to update Digest"); size_t digest_size; @@ -153,7 +776,7 @@ int openssl_digest_many( return log_oom_debug(); unsigned size; - if (!EVP_DigestFinal_ex(ctx, buf, &size)) + if (!sym_EVP_DigestFinal_ex(ctx, buf, &size)) return log_openssl_errors("Failed to finalize Digest"); assert(size == digest_size); @@ -177,44 +800,50 @@ int openssl_hmac_many( void **ret_digest, size_t *ret_digest_size) { + int r; + assert(digest_alg); assert(key); assert(data || n_data == 0); assert(ret_digest); /* ret_digest_size is optional, as caller may already know the digest size */ - _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_MD_freep) EVP_MD *md = sym_EVP_MD_fetch(NULL, digest_alg, NULL); if (!md) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Digest algorithm '%s' not supported.", digest_alg); - _cleanup_(EVP_MAC_freep) EVP_MAC *mac = EVP_MAC_fetch(NULL, "HMAC", NULL); + _cleanup_(EVP_MAC_freep) EVP_MAC *mac = sym_EVP_MAC_fetch(NULL, "HMAC", NULL); if (!mac) return log_openssl_errors("Failed to create new EVP_MAC"); - _cleanup_(EVP_MAC_CTX_freep) EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(mac); + _cleanup_(EVP_MAC_CTX_freep) EVP_MAC_CTX *ctx = sym_EVP_MAC_CTX_new(mac); if (!ctx) return log_openssl_errors("Failed to create new EVP_MAC_CTX"); - _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); if (!bld) return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); - if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_MAC_PARAM_DIGEST, (char*) digest_alg, 0)) + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_MAC_PARAM_DIGEST, (char*) digest_alg, 0)) return log_openssl_errors("Failed to set HMAC OSSL_MAC_PARAM_DIGEST"); - _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld); if (!params) return log_openssl_errors("Failed to build HMAC OSSL_PARAM"); - if (!EVP_MAC_init(ctx, key, key_size, params)) + if (!sym_EVP_MAC_init(ctx, key, key_size, params)) return log_openssl_errors("Failed to initialize EVP_MAC_CTX"); for (size_t i = 0; i < n_data; i++) - if (!EVP_MAC_update(ctx, data[i].iov_base, data[i].iov_len)) + if (!sym_EVP_MAC_update(ctx, data[i].iov_base, data[i].iov_len)) return log_openssl_errors("Failed to update HMAC"); - size_t digest_size = EVP_MAC_CTX_get_mac_size(ctx); + size_t digest_size = sym_EVP_MAC_CTX_get_mac_size(ctx); if (digest_size == 0) return log_openssl_errors("Failed to get HMAC digest size"); @@ -223,7 +852,7 @@ int openssl_hmac_many( return log_oom_debug(); size_t size; - if (!EVP_MAC_final(ctx, buf, &size, digest_size)) + if (!sym_EVP_MAC_final(ctx, buf, &size, digest_size)) return log_openssl_errors("Failed to finalize HMAC"); assert(size == digest_size); @@ -253,6 +882,8 @@ int openssl_cipher_many( void **ret, size_t *ret_size) { + int r; + assert(alg); assert(bits > 0); assert(mode); @@ -262,28 +893,32 @@ int openssl_cipher_many( assert(ret); assert(ret_size); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + _cleanup_free_ char *cipher_alg = NULL; if (asprintf(&cipher_alg, "%s-%zu-%s", alg, bits, mode) < 0) return log_oom_debug(); - _cleanup_(EVP_CIPHER_freep) EVP_CIPHER *cipher = EVP_CIPHER_fetch(NULL, cipher_alg, NULL); + _cleanup_(EVP_CIPHER_freep) EVP_CIPHER *cipher = sym_EVP_CIPHER_fetch(NULL, cipher_alg, NULL); if (!cipher) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Cipher algorithm '%s' not supported.", cipher_alg); - _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *ctx = sym_EVP_CIPHER_CTX_new(); if (!ctx) return log_openssl_errors("Failed to create new EVP_CIPHER_CTX"); /* Verify enough key data was provided. */ - int cipher_key_length = EVP_CIPHER_key_length(cipher); + int cipher_key_length = sym_EVP_CIPHER_get_key_length(cipher); assert(cipher_key_length >= 0); if ((size_t) cipher_key_length > key_size) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Not enough key bytes provided, require %d", cipher_key_length); /* Verify enough IV data was provided or, if no IV was provided, use a zeroed buffer for IV data. */ - int cipher_iv_length = EVP_CIPHER_iv_length(cipher); + int cipher_iv_length = sym_EVP_CIPHER_get_iv_length(cipher); assert(cipher_iv_length >= 0); _cleanup_free_ void *zero_iv = NULL; if (iv_size == 0) { @@ -298,10 +933,10 @@ int openssl_cipher_many( return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Not enough IV bytes provided, require %d", cipher_iv_length); - if (!EVP_EncryptInit(ctx, cipher, key, iv)) + if (!sym_EVP_EncryptInit(ctx, cipher, key, iv)) return log_openssl_errors("Failed to initialize EVP_CIPHER_CTX."); - int cipher_block_size = EVP_CIPHER_CTX_block_size(ctx); + int cipher_block_size = sym_EVP_CIPHER_CTX_get_block_size(ctx); assert(cipher_block_size > 0); _cleanup_free_ uint8_t *buf = NULL; @@ -313,7 +948,7 @@ int openssl_cipher_many( return log_oom_debug(); int update_size; - if (!EVP_EncryptUpdate(ctx, &buf[size], &update_size, data[i].iov_base, data[i].iov_len)) + if (!sym_EVP_EncryptUpdate(ctx, &buf[size], &update_size, data[i].iov_base, data[i].iov_len)) return log_openssl_errors("Failed to update Cipher."); size += update_size; @@ -323,7 +958,7 @@ int openssl_cipher_many( return log_oom_debug(); int final_size; - if (!EVP_EncryptFinal_ex(ctx, &buf[size], &final_size)) + if (!sym_EVP_EncryptFinal_ex(ctx, &buf[size], &final_size)) return log_openssl_errors("Failed to finalize Cipher."); *ret = TAKE_PTR(buf); @@ -347,20 +982,26 @@ int kdf_ss_derive( size_t derive_size, void **ret) { + int r; + assert(digest); assert(key); assert(derive_size > 0); assert(ret); - _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = EVP_KDF_fetch(NULL, "SSKDF", NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = sym_EVP_KDF_fetch(NULL, "SSKDF", NULL); if (!kdf) return log_openssl_errors("Failed to create new EVP_KDF"); - _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf); + _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = sym_EVP_KDF_CTX_new(kdf); if (!ctx) return log_openssl_errors("Failed to create new EVP_KDF_CTX"); - _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); if (!bld) return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); @@ -368,25 +1009,25 @@ int kdf_ss_derive( if (!buf) return log_oom_debug(); - if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, 0)) + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, 0)) return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_DIGEST"); - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, (char*) key, key_size)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, (char*) key, key_size)) return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_KEY"); if (salt) - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, (char*) salt, salt_size)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, (char*) salt, salt_size)) return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_SALT"); if (info) - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, (char*) info, info_size)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, (char*) info, info_size)) return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_INFO"); - _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld); if (!params) return log_openssl_errors("Failed to build KDF-SS OSSL_PARAM"); - if (EVP_KDF_derive(ctx, buf, derive_size, params) <= 0) + if (sym_EVP_KDF_derive(ctx, buf, derive_size, params) <= 0) return log_openssl_errors("OpenSSL KDF-SS derive failed"); *ret = TAKE_PTR(buf); @@ -413,6 +1054,8 @@ int kdf_kb_hmac_derive( size_t derive_size, void **ret) { + int r; + assert(mode); assert(strcaseeq(mode, "COUNTER") || strcaseeq(mode, "FEEDBACK")); assert(digest); @@ -423,44 +1066,48 @@ int kdf_kb_hmac_derive( assert(derive_size > 0); assert(ret); - _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = EVP_KDF_fetch(NULL, "KBKDF", NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = sym_EVP_KDF_fetch(NULL, "KBKDF", NULL); if (!kdf) return log_openssl_errors("Failed to create new EVP_KDF"); - _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf); + _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = sym_EVP_KDF_CTX_new(kdf); if (!ctx) return log_openssl_errors("Failed to create new EVP_KDF_CTX"); - _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); if (!bld) return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); - if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_MAC, (char*) "HMAC", 0)) + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_MAC, (char*) "HMAC", 0)) return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_MAC"); - if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_MODE, (char*) mode, 0)) + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_MODE, (char*) mode, 0)) return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_MODE"); - if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, 0)) + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, 0)) return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_DIGEST"); if (key) - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, (char*) key, key_size)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, (char*) key, key_size)) return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_KEY"); if (salt) - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, (char*) salt, salt_size)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, (char*) salt, salt_size)) return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_SALT"); if (info) - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, (char*) info, info_size)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, (char*) info, info_size)) return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_INFO"); if (seed) - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SEED, (char*) seed, seed_size)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SEED, (char*) seed, seed_size)) return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_SEED"); - _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld); if (!params) return log_openssl_errors("Failed to build KDF-KB OSSL_PARAM"); @@ -468,7 +1115,7 @@ int kdf_kb_hmac_derive( if (!buf) return log_oom_debug(); - if (EVP_KDF_derive(ctx, buf, derive_size, params) <= 0) + if (sym_EVP_KDF_derive(ctx, buf, derive_size, params) <= 0) return log_openssl_errors("OpenSSL KDF-KB derive failed"); *ret = TAKE_PTR(buf); @@ -486,25 +1133,33 @@ int rsa_encrypt_bytes( _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = NULL; _cleanup_free_ void *b = NULL; size_t l; + int r; + + assert(ret_encrypt_key); + assert(ret_encrypt_key_size); - ctx = EVP_PKEY_CTX_new(pkey, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + ctx = sym_EVP_PKEY_CTX_new(pkey, NULL); if (!ctx) return log_openssl_errors("Failed to allocate public key context"); - if (EVP_PKEY_encrypt_init(ctx) <= 0) + if (sym_EVP_PKEY_encrypt_init(ctx) <= 0) return log_openssl_errors("Failed to initialize public key context"); - if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) + if (sym_EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) return log_openssl_errors("Failed to configure PKCS#1 padding"); - if (EVP_PKEY_encrypt(ctx, NULL, &l, decrypted_key, decrypted_key_size) <= 0) + if (sym_EVP_PKEY_encrypt(ctx, NULL, &l, decrypted_key, decrypted_key_size) <= 0) return log_openssl_errors("Failed to determine encrypted key size"); b = malloc(l); if (!b) return -ENOMEM; - if (EVP_PKEY_encrypt(ctx, b, &l, decrypted_key, decrypted_key_size) <= 0) + if (sym_EVP_PKEY_encrypt(ctx, b, &l, decrypted_key, decrypted_key_size) <= 0) return log_openssl_errors("Failed to determine encrypted key size"); *ret_encrypt_key = TAKE_PTR(b); @@ -523,6 +1178,8 @@ int rsa_oaep_encrypt_bytes( void **ret_encrypt_key, size_t *ret_encrypt_key_size) { + int r; + assert(pkey); assert(digest_alg); assert(label); @@ -531,42 +1188,46 @@ int rsa_oaep_encrypt_bytes( assert(ret_encrypt_key); assert(ret_encrypt_key_size); - _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_MD_freep) EVP_MD *md = sym_EVP_MD_fetch(NULL, digest_alg, NULL); if (!md) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Digest algorithm '%s' not supported.", digest_alg); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*) pkey, NULL); + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new((EVP_PKEY*) pkey, NULL); if (!ctx) return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); - if (EVP_PKEY_encrypt_init(ctx) <= 0) + if (sym_EVP_PKEY_encrypt_init(ctx) <= 0) return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); - if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) + if (sym_EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) return log_openssl_errors("Failed to configure RSA-OAEP padding"); - if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) <= 0) + if (sym_EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) <= 0) return log_openssl_errors("Failed to configure RSA-OAEP MD"); _cleanup_free_ char *duplabel = strdup(label); if (!duplabel) return log_oom_debug(); - if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, duplabel, strlen(duplabel) + 1) <= 0) + if (sym_EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, duplabel, strlen(duplabel) + 1) <= 0) return log_openssl_errors("Failed to configure RSA-OAEP label"); /* ctx owns this now, don't free */ TAKE_PTR(duplabel); size_t size = 0; - if (EVP_PKEY_encrypt(ctx, NULL, &size, decrypted_key, decrypted_key_size) <= 0) + if (sym_EVP_PKEY_encrypt(ctx, NULL, &size, decrypted_key, decrypted_key_size) <= 0) return log_openssl_errors("Failed to determine RSA-OAEP encrypted key size"); _cleanup_free_ void *buf = malloc(size); if (!buf) return log_oom_debug(); - if (EVP_PKEY_encrypt(ctx, buf, &size, decrypted_key, decrypted_key_size) <= 0) + if (sym_EVP_PKEY_encrypt(ctx, buf, &size, decrypted_key, decrypted_key_size) <= 0) return log_openssl_errors("Failed to RSA-OAEP encrypt"); *ret_encrypt_key = TAKE_PTR(buf); @@ -580,7 +1241,7 @@ int rsa_pkey_to_suitable_key_size( size_t *ret_suitable_key_size) { size_t suitable_key_size; - int bits; + int bits, r; assert(pkey); assert(ret_suitable_key_size); @@ -588,10 +1249,14 @@ int rsa_pkey_to_suitable_key_size( /* Analyzes the specified public key and that it is RSA. If so, will return a suitable size for a * disk encryption key to encrypt with RSA for use in PKCS#11 security token schemes. */ - if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + if (sym_EVP_PKEY_get_base_id(pkey) != EVP_PKEY_RSA) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "X.509 certificate does not refer to RSA key."); - bits = EVP_PKEY_bits(pkey); + bits = sym_EVP_PKEY_get_bits(pkey); log_debug("Bits in RSA key: %i", bits); /* We use PKCS#1 padding for the RSA cleartext, hence let's leave some extra space for it, hence only @@ -609,6 +1274,7 @@ int rsa_pkey_to_suitable_key_size( * in big-endian format, e.g. wrap it with htobe32() for uint32_t. */ int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size, EVP_PKEY **ret) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + int r; assert(n); assert(n_size != 0); @@ -616,18 +1282,22 @@ int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size assert(e_size != 0); assert(ret); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); if (!ctx) return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); - if (EVP_PKEY_fromdata_init(ctx) <= 0) + if (sym_EVP_PKEY_fromdata_init(ctx) <= 0) return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); OSSL_PARAM params[3]; #if __BYTE_ORDER == __BIG_ENDIAN - params[0] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, (void*)n, n_size); - params[1] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, (void*)e, e_size); + params[0] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, (void*)n, n_size); + params[1] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, (void*)e, e_size); #else _cleanup_free_ void *native_n = memdup_reverse(n, n_size); if (!native_n) @@ -637,12 +1307,12 @@ int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size if (!native_e) return log_oom_debug(); - params[0] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, native_n, n_size); - params[1] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, native_e, e_size); + params[0] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, native_n, n_size); + params[1] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, native_e, e_size); #endif - params[2] = OSSL_PARAM_construct_end(); + params[2] = sym_OSSL_PARAM_construct_end(); - if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) + if (sym_EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) return log_openssl_errors("Failed to create RSA EVP_PKEY"); *ret = TAKE_PTR(pkey); @@ -658,27 +1328,33 @@ int rsa_pkey_to_n_e( void **ret_e, size_t *ret_e_size) { + int r; + assert(pkey); assert(ret_n); assert(ret_n_size); assert(ret_e); assert(ret_e_size); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + _cleanup_(BN_freep) BIGNUM *bn_n = NULL; - if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &bn_n)) + if (!sym_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &bn_n)) return log_openssl_errors("Failed to get RSA n"); _cleanup_(BN_freep) BIGNUM *bn_e = NULL; - if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &bn_e)) + if (!sym_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &bn_e)) return log_openssl_errors("Failed to get RSA e"); - size_t n_size = BN_num_bytes(bn_n), e_size = BN_num_bytes(bn_e); + size_t n_size = sym_BN_num_bytes(bn_n), e_size = sym_BN_num_bytes(bn_e); _cleanup_free_ void *n = malloc(n_size), *e = malloc(e_size); if (!n || !e) return log_oom_debug(); - assert(BN_bn2bin(bn_n, n) == (int) n_size); - assert(BN_bn2bin(bn_e, e) == (int) e_size); + assert(sym_BN_bn2bin(bn_n, n) == (int) n_size); + assert(sym_BN_bn2bin(bn_e, e) == (int) e_size); *ret_n = TAKE_PTR(n); *ret_n_size = n_size; @@ -697,58 +1373,64 @@ int ecc_pkey_from_curve_x_y( size_t y_size, EVP_PKEY **ret) { + int r; + assert(x); assert(y); assert(ret); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); if (!ctx) return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); - _cleanup_(BN_freep) BIGNUM *bn_x = BN_bin2bn(x, x_size, NULL); + _cleanup_(BN_freep) BIGNUM *bn_x = sym_BN_bin2bn(x, x_size, NULL); if (!bn_x) return log_openssl_errors("Failed to create BIGNUM x"); - _cleanup_(BN_freep) BIGNUM *bn_y = BN_bin2bn(y, y_size, NULL); + _cleanup_(BN_freep) BIGNUM *bn_y = sym_BN_bin2bn(y, y_size, NULL); if (!bn_y) return log_openssl_errors("Failed to create BIGNUM y"); - _cleanup_(EC_GROUP_freep) EC_GROUP *group = EC_GROUP_new_by_curve_name(curve_id); + _cleanup_(EC_GROUP_freep) EC_GROUP *group = sym_EC_GROUP_new_by_curve_name(curve_id); if (!group) return log_openssl_errors("ECC curve id %d not supported", curve_id); - _cleanup_(EC_POINT_freep) EC_POINT *point = EC_POINT_new(group); + _cleanup_(EC_POINT_freep) EC_POINT *point = sym_EC_POINT_new(group); if (!point) return log_openssl_errors("Failed to create new EC_POINT"); - if (!EC_POINT_set_affine_coordinates(group, point, bn_x, bn_y, NULL)) + if (!sym_EC_POINT_set_affine_coordinates(group, point, bn_x, bn_y, NULL)) return log_openssl_errors("Failed to set ECC coordinates"); - if (EVP_PKEY_fromdata_init(ctx) <= 0) + if (sym_EVP_PKEY_fromdata_init(ctx) <= 0) return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); - _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); if (!bld) return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); - if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, (char*) OSSL_EC_curve_nid2name(curve_id), 0)) + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, (char*) sym_OSSL_EC_curve_nid2name(curve_id), 0)) return log_openssl_errors("Failed to add ECC OSSL_PKEY_PARAM_GROUP_NAME"); _cleanup_(OPENSSL_freep) void *pbuf = NULL; size_t pbuf_len = 0; - pbuf_len = EC_POINT_point2buf(group, point, POINT_CONVERSION_UNCOMPRESSED, (unsigned char**) &pbuf, NULL); + pbuf_len = sym_EC_POINT_point2buf(group, point, POINT_CONVERSION_UNCOMPRESSED, (unsigned char**) &pbuf, NULL); if (pbuf_len == 0) return log_openssl_errors("Failed to convert ECC point to buffer"); - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pbuf, pbuf_len)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pbuf, pbuf_len)) return log_openssl_errors("Failed to add ECC OSSL_PKEY_PARAM_PUB_KEY"); - _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld); if (!params) return log_openssl_errors("Failed to build ECC OSSL_PARAM"); _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; - if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) + if (sym_EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) return log_openssl_errors("Failed to create ECC EVP_PKEY"); *ret = TAKE_PTR(pkey); @@ -764,38 +1446,42 @@ int ecc_pkey_to_curve_x_y( size_t *ret_y_size) { _cleanup_(BN_freep) BIGNUM *bn_x = NULL, *bn_y = NULL; - int curve_id; + int curve_id, r; assert(pkey); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + size_t name_size; - if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, NULL, 0, &name_size)) + if (!sym_EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, NULL, 0, &name_size)) return log_openssl_errors("Failed to get ECC group name size"); _cleanup_free_ char *name = new(char, name_size + 1); if (!name) return log_oom_debug(); - if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, name, name_size + 1, NULL)) + if (!sym_EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, name, name_size + 1, NULL)) return log_openssl_errors("Failed to get ECC group name"); - curve_id = OBJ_sn2nid(name); + curve_id = sym_OBJ_sn2nid(name); if (curve_id == NID_undef) return log_openssl_errors("Failed to get ECC curve id"); - if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &bn_x)) + if (!sym_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &bn_x)) return log_openssl_errors("Failed to get ECC point x"); - if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &bn_y)) + if (!sym_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &bn_y)) return log_openssl_errors("Failed to get ECC point y"); - size_t x_size = BN_num_bytes(bn_x), y_size = BN_num_bytes(bn_y); + size_t x_size = sym_BN_num_bytes(bn_x), y_size = sym_BN_num_bytes(bn_y); _cleanup_free_ void *x = malloc(x_size), *y = malloc(y_size); if (!x || !y) return log_oom_debug(); - assert(BN_bn2bin(bn_x, x) == (int) x_size); - assert(BN_bn2bin(bn_y, y) == (int) y_size); + assert(sym_BN_bn2bin(bn_x, x) == (int) x_size); + assert(sym_BN_bn2bin(bn_y, y) == (int) y_size); if (ret_curve_id) *ret_curve_id = curve_id; @@ -813,20 +1499,26 @@ int ecc_pkey_to_curve_x_y( /* Generate a new ECC key for the specified ECC curve id. */ int ecc_pkey_new(int curve_id, EVP_PKEY **ret) { + int r; + assert(ret); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); if (!ctx) return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); - if (EVP_PKEY_keygen_init(ctx) <= 0) + if (sym_EVP_PKEY_keygen_init(ctx) <= 0) return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); - if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, curve_id) <= 0) + if (sym_EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, curve_id) <= 0) return log_openssl_errors("Failed to set ECC curve %d", curve_id); _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; - if (EVP_PKEY_keygen(ctx, &pkey) <= 0) + if (sym_EVP_PKEY_keygen(ctx, &pkey) <= 0) return log_openssl_errors("Failed to generate ECC key"); *ret = TAKE_PTR(pkey); @@ -844,30 +1536,36 @@ int ecc_ecdh(const EVP_PKEY *private_pkey, void **ret_shared_secret, size_t *ret_shared_secret_size) { + int r; + assert(private_pkey); assert(peer_pkey); assert(ret_shared_secret); assert(ret_shared_secret_size); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*) private_pkey, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new((EVP_PKEY*) private_pkey, NULL); if (!ctx) return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); - if (EVP_PKEY_derive_init(ctx) <= 0) + if (sym_EVP_PKEY_derive_init(ctx) <= 0) return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); - if (EVP_PKEY_derive_set_peer(ctx, (EVP_PKEY*) peer_pkey) <= 0) + if (sym_EVP_PKEY_derive_set_peer(ctx, (EVP_PKEY*) peer_pkey) <= 0) return log_openssl_errors("Failed to set ECC derive peer"); size_t shared_secret_size; - if (EVP_PKEY_derive(ctx, NULL, &shared_secret_size) <= 0) + if (sym_EVP_PKEY_derive(ctx, NULL, &shared_secret_size) <= 0) return log_openssl_errors("Failed to get ECC shared secret size"); _cleanup_(erase_and_freep) void *shared_secret = malloc(shared_secret_size); if (!shared_secret) return log_oom_debug(); - if (EVP_PKEY_derive(ctx, (unsigned char*) shared_secret, &shared_secret_size) <= 0) + if (sym_EVP_PKEY_derive(ctx, (unsigned char*) shared_secret, &shared_secret_size) <= 0) return log_openssl_errors("Failed to derive ECC shared secret"); *ret_shared_secret = TAKE_PTR(shared_secret); @@ -882,6 +1580,7 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s int sz, lsz, msz; unsigned umsz; unsigned char *dd; + int r; /* Calculates a message digest of the DER encoded public key */ @@ -890,7 +1589,11 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s assert(ret); assert(ret_size); - sz = i2d_PublicKey(pk, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + sz = sym_i2d_PublicKey(pk, NULL); if (sz < 0) return log_openssl_errors("Unable to convert public key to DER format"); @@ -898,21 +1601,21 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s if (!d) return log_oom_debug(); - lsz = i2d_PublicKey(pk, &dd); + lsz = sym_i2d_PublicKey(pk, &dd); if (lsz < 0) return log_openssl_errors("Unable to convert public key to DER format"); - m = EVP_MD_CTX_new(); + m = sym_EVP_MD_CTX_new(); if (!m) return log_openssl_errors("Failed to create new EVP_MD_CTX"); - if (EVP_DigestInit_ex(m, md, NULL) != 1) - return log_openssl_errors("Failed to initialize %s context", EVP_MD_name(md)); + if (sym_EVP_DigestInit_ex(m, md, NULL) != 1) + return log_openssl_errors("Failed to initialize %s context", sym_EVP_MD_get0_name(md)); - if (EVP_DigestUpdate(m, d, lsz) != 1) - return log_openssl_errors("Failed to run %s context", EVP_MD_name(md)); + if (sym_EVP_DigestUpdate(m, d, lsz) != 1) + return log_openssl_errors("Failed to run %s context", sym_EVP_MD_get0_name(md)); - msz = EVP_MD_size(md); + msz = sym_EVP_MD_get_size(md); assert(msz > 0); h = malloc(msz); @@ -920,7 +1623,7 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s return log_oom_debug(); umsz = msz; - if (EVP_DigestFinal_ex(m, h, &umsz) != 1) + if (sym_EVP_DigestFinal_ex(m, h, &umsz) != 1) return log_openssl_errors("Failed to finalize hash context"); assert(umsz == (unsigned) msz); @@ -943,6 +1646,10 @@ int digest_and_sign( assert(ret); assert(ret_size); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + if (size == 0) data = ""; /* make sure to pass a valid pointer to OpenSSL */ else { @@ -952,28 +1659,28 @@ int digest_and_sign( size = strlen(data); } - _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = sym_EVP_MD_CTX_new(); if (!mdctx) return log_openssl_errors("Failed to create new EVP_MD_CTX"); - if (EVP_DigestSignInit(mdctx, NULL, md, NULL, privkey) != 1) { + if (sym_EVP_DigestSignInit(mdctx, NULL, md, NULL, privkey) != 1) { /* Distro security policies often disable support for SHA-1. Let's return a recognizable * error for that case. */ - bool invalid_digest = ERR_GET_REASON(ERR_peek_last_error()) == EVP_R_INVALID_DIGEST; + bool invalid_digest = ERR_GET_REASON(sym_ERR_peek_last_error()) == EVP_R_INVALID_DIGEST; r = log_openssl_errors("Failed to initialize signature context"); return invalid_digest ? -EADDRNOTAVAIL : r; } /* Determine signature size */ size_t ss; - if (EVP_DigestSign(mdctx, NULL, &ss, data, size) != 1) + if (sym_EVP_DigestSign(mdctx, NULL, &ss, data, size) != 1) return log_openssl_errors("Failed to determine size of signature"); _cleanup_free_ void *sig = malloc(ss); if (!sig) return log_oom_debug(); - if (EVP_DigestSign(mdctx, sig, &ss, data, size) != 1) + if (sym_EVP_DigestSign(mdctx, sig, &ss, data, size) != 1) return log_openssl_errors("Failed to sign data"); *ret = TAKE_PTR(sig); @@ -982,6 +1689,8 @@ int digest_and_sign( } int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, const char *hash_algorithm, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si) { + int r; + assert(certificate); assert(ret_p7); @@ -991,67 +1700,71 @@ int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, const char *hash_algorit * copied into the signer info's "enc_digest" field. If the signing hash algorithm is not provided, * SHA-256 is used. */ - _cleanup_(PKCS7_freep) PKCS7 *p7 = PKCS7_new(); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(PKCS7_freep) PKCS7 *p7 = sym_PKCS7_new(); if (!p7) return log_oom(); - if (PKCS7_set_type(p7, NID_pkcs7_signed) == 0) + if (sym_PKCS7_set_type(p7, NID_pkcs7_signed) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 type: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (PKCS7_content_new(p7, NID_pkcs7_data) == 0) + if (sym_PKCS7_content_new(p7, NID_pkcs7_data) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 content: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (PKCS7_add_certificate(p7, certificate) == 0) + if (sym_PKCS7_add_certificate(p7, certificate) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 certificate: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); int x509_pknid = 0; - if (X509_get_signature_info(certificate, NULL, &x509_pknid, NULL, NULL) == 0) + if (sym_X509_get_signature_info(certificate, NULL, &x509_pknid, NULL, NULL) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get X509 digest NID: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - const EVP_MD *md = EVP_get_digestbyname(hash_algorithm ?: "SHA256"); + const EVP_MD *md = sym_EVP_get_digestbyname(hash_algorithm ?: "SHA256"); if (!md) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get digest algorithm '%s'", hash_algorithm ?: "SHA256"); - _cleanup_(PKCS7_SIGNER_INFO_freep) PKCS7_SIGNER_INFO *si = PKCS7_SIGNER_INFO_new(); + _cleanup_(PKCS7_SIGNER_INFO_freep) PKCS7_SIGNER_INFO *si = sym_PKCS7_SIGNER_INFO_new(); if (!si) return log_oom(); if (private_key) { - if (PKCS7_SIGNER_INFO_set(si, certificate, private_key, md) <= 0) + if (sym_PKCS7_SIGNER_INFO_set(si, certificate, private_key, md) <= 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to configure signer info: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); } else { - if (ASN1_INTEGER_set(si->version, 1) == 0) + if (sym_ASN1_INTEGER_set(si->version, 1) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info version: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (X509_NAME_set(&si->issuer_and_serial->issuer, X509_get_issuer_name(certificate)) == 0) + if (sym_X509_NAME_set(&si->issuer_and_serial->issuer, sym_X509_get_issuer_name(certificate)) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info issuer: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - ASN1_INTEGER_free(si->issuer_and_serial->serial); - si->issuer_and_serial->serial = ASN1_INTEGER_dup(X509_get0_serialNumber(certificate)); + sym_ASN1_INTEGER_free(si->issuer_and_serial->serial); + si->issuer_and_serial->serial = sym_ASN1_INTEGER_dup(sym_X509_get0_serialNumber(certificate)); if (!si->issuer_and_serial->serial) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info serial: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (X509_ALGOR_set0(si->digest_alg, OBJ_nid2obj(EVP_MD_type(md)), V_ASN1_NULL, NULL) == 0) + if (sym_X509_ALGOR_set0(si->digest_alg, sym_OBJ_nid2obj(sym_EVP_MD_get_type(md)), V_ASN1_NULL, NULL) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info digest algorithm: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (X509_ALGOR_set0(si->digest_enc_alg, OBJ_nid2obj(x509_pknid), V_ASN1_NULL, NULL) == 0) + if (sym_X509_ALGOR_set0(si->digest_enc_alg, sym_OBJ_nid2obj(x509_pknid), V_ASN1_NULL, NULL) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info signing algorithm: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); } - if (PKCS7_add_signer(p7, si) == 0) + if (sym_PKCS7_add_signer(p7, si) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 signer info: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); *ret_p7 = TAKE_PTR(p7); if (ret_si) @@ -1097,6 +1810,11 @@ static int ecc_pkey_generate_volume_keys( void **ret_saved_key, size_t *ret_saved_key_size) { + assert(ret_decrypted_key); + assert(ret_decrypted_key_size); + assert(ret_saved_key); + assert(ret_saved_key_size); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_new = NULL; _cleanup_(erase_and_freep) void *decrypted_key = NULL; _cleanup_free_ unsigned char *saved_key = NULL; @@ -1106,7 +1824,11 @@ static int ecc_pkey_generate_volume_keys( _cleanup_free_ char *curve_name = NULL; size_t len = 0; - if (EVP_PKEY_get_group_name(pkey, NULL, 0, &len) != 1 || len == 0) + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + if (sym_EVP_PKEY_get_group_name(pkey, NULL, 0, &len) != 1 || len == 0) return log_openssl_errors("Failed to determine PKEY group name length"); len++; @@ -1114,10 +1836,10 @@ static int ecc_pkey_generate_volume_keys( if (!curve_name) return log_oom_debug(); - if (EVP_PKEY_get_group_name(pkey, curve_name, len, &len) != 1) + if (sym_EVP_PKEY_get_group_name(pkey, curve_name, len, &len) != 1) return log_openssl_errors("Failed to get PKEY group name"); - r = ecc_pkey_new(OBJ_sn2nid(curve_name), &pkey_new); + r = ecc_pkey_new(sym_OBJ_sn2nid(curve_name), &pkey_new); if (r < 0) return log_debug_errno(r, "Failed to generate a new EC keypair: %m"); @@ -1127,7 +1849,7 @@ static int ecc_pkey_generate_volume_keys( /* EVP_PKEY_get1_encoded_public_key() always returns uncompressed format of EC points. See https://github.com/openssl/openssl/discussions/22835 */ - saved_key_size = EVP_PKEY_get1_encoded_public_key(pkey_new, &saved_key); + saved_key_size = sym_EVP_PKEY_get1_encoded_public_key(pkey_new, &saved_key); if (saved_key_size == 0) return log_openssl_errors("Failed to convert the generated public key to SEC1 format"); @@ -1150,6 +1872,11 @@ static int rsa_pkey_generate_volume_keys( size_t decrypted_key_size, saved_key_size; int r; + assert(ret_decrypted_key); + assert(ret_decrypted_key_size); + assert(ret_saved_key); + assert(ret_saved_key_size); + r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size); if (r < 0) return log_debug_errno(r, "Failed to determine RSA public key size."); @@ -1182,13 +1909,19 @@ int pkey_generate_volume_keys( void **ret_saved_key, size_t *ret_saved_key_size) { + int r; + assert(pkey); assert(ret_decrypted_key); assert(ret_decrypted_key_size); assert(ret_saved_key); assert(ret_saved_key_size); - int type = EVP_PKEY_get_base_id(pkey); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + int type = sym_EVP_PKEY_get_base_id(pkey); switch (type) { case EVP_PKEY_RSA: @@ -1201,7 +1934,7 @@ int pkey_generate_volume_keys( return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine a type of public key."); default: - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported public key type: %s", OBJ_nid2sn(type)); + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported public key type: %s", sym_OBJ_nid2sn(type)); } } @@ -1211,18 +1944,24 @@ static int load_key_from_provider( UI_METHOD *ui_method, EVP_PKEY **ret) { + int r; + assert(provider); assert(private_key_uri); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + /* Load the provider so that this can work without any custom written configuration in /etc/. * Also load the 'default' as that seems to be the recommendation. */ - if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true)) + if (!sym_OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true)) return log_openssl_errors("Failed to load OpenSSL provider '%s'", provider); - if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, "default", /* retain_fallbacks= */ true)) + if (!sym_OSSL_PROVIDER_try_load(/* ctx= */ NULL, "default", /* retain_fallbacks= */ true)) return log_openssl_errors("Failed to load OpenSSL provider 'default'"); - _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = OSSL_STORE_open( + _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = sym_OSSL_STORE_open( private_key_uri, ui_method, /* ui_data= */ NULL, @@ -1231,14 +1970,14 @@ static int load_key_from_provider( if (!store) return log_openssl_errors("Failed to open OpenSSL store via '%s'", private_key_uri); - if (OSSL_STORE_expect(store, OSSL_STORE_INFO_PKEY) == 0) + if (sym_OSSL_STORE_expect(store, OSSL_STORE_INFO_PKEY) == 0) return log_openssl_errors("Failed to filter store by private keys"); - _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = OSSL_STORE_load(store); + _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = sym_OSSL_STORE_load(store); if (!info) return log_openssl_errors("Failed to load OpenSSL store via '%s'", private_key_uri); - _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = OSSL_STORE_INFO_get1_PKEY(info); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = sym_OSSL_STORE_INFO_get1_PKEY(info); if (!private_key) return log_openssl_errors("Failed to load private key via '%s'", private_key_uri); @@ -1248,20 +1987,28 @@ static int load_key_from_provider( } static int load_key_from_engine(const char *engine, const char *private_key_uri, UI_METHOD *ui_method, EVP_PKEY **ret) { +#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) + int r; +#endif + assert(engine); assert(private_key_uri); assert(ret); #if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + DISABLE_WARNING_DEPRECATED_DECLARATIONS; - _cleanup_(ENGINE_freep) ENGINE *e = ENGINE_by_id(engine); + _cleanup_(ENGINE_freep) ENGINE *e = sym_ENGINE_by_id(engine); if (!e) return log_openssl_errors("Failed to load signing engine '%s'", engine); - if (ENGINE_init(e) == 0) + if (sym_ENGINE_init(e) == 0) return log_openssl_errors("Failed to initialize signing engine '%s'", engine); - _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key(e, private_key_uri, ui_method, /* callback_data= */ NULL); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = sym_ENGINE_load_private_key(e, private_key_uri, ui_method, /* callback_data= */ NULL); if (!private_key) return log_openssl_errors("Failed to load private key from '%s'", private_key_uri); REENABLE_WARNING; @@ -1278,14 +2025,14 @@ static int load_key_from_engine(const char *engine, const char *private_key_uri, static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) { int r; - switch(UI_get_string_type(uis)) { + switch(sym_UI_get_string_type(uis)) { case UIT_PROMPT: { /* If no ask password request was configured use the default openssl UI. */ - AskPasswordRequest *req = (AskPasswordRequest*) UI_method_get_ex_data(UI_get_method(ui), 0); + AskPasswordRequest *req = (AskPasswordRequest*) sym_UI_method_get_ex_data(sym_UI_get_method(ui), 0); if (!req) - return (UI_method_get_reader(UI_OpenSSL()))(ui, uis); + return (sym_UI_method_get_reader(sym_UI_OpenSSL()))(ui, uis); - req->message = UI_get0_output_string(uis); + req->message = sym_UI_get0_output_string(uis); _cleanup_strv_free_ char **l = NULL; r = ask_password_auto(req, ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE, &l); @@ -1299,7 +2046,7 @@ static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) { return 0; } - if (UI_set_result(ui, uis, *l) != 0) { + if (sym_UI_set_result(ui, uis, *l) != 0) { log_openssl_errors("Failed to set user interface result"); return 0; } @@ -1307,7 +2054,7 @@ static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) { return 1; } default: - return (UI_method_get_reader(UI_OpenSSL()))(ui, uis); + return (sym_UI_method_get_reader(sym_UI_OpenSSL()))(ui, uis); } } #endif @@ -1322,6 +2069,10 @@ static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) assert(path); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + r = read_full_file_full( AT_FDCWD, path, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, @@ -1330,30 +2081,38 @@ static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) if (r < 0) return log_debug_errno(r, "Failed to read key file '%s': %m", path); - kb = BIO_new_mem_buf(rawkey, rawkeysz); + kb = sym_BIO_new_mem_buf(rawkey, rawkeysz); if (!kb) return log_oom_debug(); - pk = PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL); + pk = sym_PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL); if (!pk) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM private key: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (ret) - *ret = TAKE_PTR(pk); + *ret = TAKE_PTR(pk); return 0; } static int openssl_ask_password_ui_new(const AskPasswordRequest *request, OpenSSLAskPasswordUI **ret) { +#ifndef OPENSSL_NO_UI_CONSOLE + int r; +#endif + + assert(request); assert(ret); #ifndef OPENSSL_NO_UI_CONSOLE - _cleanup_(UI_destroy_methodp) UI_METHOD *method = UI_create_method("systemd-ask-password"); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(UI_destroy_methodp) UI_METHOD *method = sym_UI_create_method("systemd-ask-password"); if (!method) return log_openssl_errors("Failed to initialize openssl user interface"); - if (UI_method_set_reader(method, openssl_ask_password_ui_read) != 0) + if (sym_UI_method_set_reader(method, openssl_ask_password_ui_read) != 0) return log_openssl_errors("Failed to set openssl user interface reader"); OpenSSLAskPasswordUI *ui = new(OpenSSLAskPasswordUI, 1); @@ -1365,9 +2124,9 @@ static int openssl_ask_password_ui_new(const AskPasswordRequest *request, OpenSS .request = *request, }; - UI_set_default_method(ui->method); + sym_UI_set_default_method(ui->method); - if (UI_method_set_ex_data(ui->method, 0, &ui->request) == 0) + if (sym_UI_method_set_ex_data(ui->method, 0, &ui->request) == 0) return log_openssl_errors("Failed to set extra data for UI method"); *ret = TAKE_PTR(ui); @@ -1387,6 +2146,10 @@ static int load_x509_certificate_from_file(const char *path, X509 **ret) { assert(path); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + r = read_full_file_full( AT_FDCWD, path, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_CONNECT_SOCKET, @@ -1395,34 +2158,39 @@ static int load_x509_certificate_from_file(const char *path, X509 **ret) { if (r < 0) return log_debug_errno(r, "Failed to read certificate file '%s': %m", path); - cb = BIO_new_mem_buf(rawcert, rawcertsz); + cb = sym_BIO_new_mem_buf(rawcert, rawcertsz); if (!cb) return log_oom_debug(); - cert = PEM_read_bio_X509(cb, NULL, NULL, NULL); + cert = sym_PEM_read_bio_X509(cb, NULL, NULL, NULL); if (!cert) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse X.509 certificate: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (ret) - *ret = TAKE_PTR(cert); + *ret = TAKE_PTR(cert); return 0; } static int load_x509_certificate_from_provider(const char *provider, const char *certificate_uri, X509 **ret) { + int r; + assert(provider); assert(certificate_uri); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + /* Load the provider so that this can work without any custom written configuration in /etc/. * Also load the 'default' as that seems to be the recommendation. */ - if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true)) + if (!sym_OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true)) return log_openssl_errors("Failed to load OpenSSL provider '%s'", provider); - if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, "default", /* retain_fallbacks= */ true)) + if (!sym_OSSL_PROVIDER_try_load(/* ctx= */ NULL, "default", /* retain_fallbacks= */ true)) return log_openssl_errors("Failed to load OpenSSL provider 'default'"); - _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = OSSL_STORE_open( + _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = sym_OSSL_STORE_open( certificate_uri, /* ui_method= */ NULL, /* ui_method= */ NULL, @@ -1431,14 +2199,14 @@ static int load_x509_certificate_from_provider(const char *provider, const char if (!store) return log_openssl_errors("Failed to open OpenSSL store via '%s'", certificate_uri); - if (OSSL_STORE_expect(store, OSSL_STORE_INFO_CERT) == 0) + if (sym_OSSL_STORE_expect(store, OSSL_STORE_INFO_CERT) == 0) return log_openssl_errors("Failed to filter store by X.509 certificates"); - _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = OSSL_STORE_load(store); + _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = sym_OSSL_STORE_load(store); if (!info) return log_openssl_errors("Failed to load OpenSSL store via '%s'", certificate_uri); - _cleanup_(X509_freep) X509 *cert = OSSL_STORE_INFO_get1_CERT(info); + _cleanup_(X509_freep) X509 *cert = sym_OSSL_STORE_INFO_get1_CERT(info); if (!cert) return log_openssl_errors("Failed to load certificate via '%s'", certificate_uri); @@ -1452,20 +2220,24 @@ OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) { return NULL; #ifndef OPENSSL_NO_UI_CONSOLE - assert(UI_get_default_method() == ui->method); - UI_set_default_method(UI_OpenSSL()); - UI_destroy_method(ui->method); + assert(sym_UI_get_default_method() == ui->method); + sym_UI_set_default_method(sym_UI_OpenSSL()); + sym_UI_destroy_method(ui->method); #endif return mfree(ui); } int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) { _cleanup_free_ uint8_t *der = NULL; - int dersz; + int dersz, r; assert(cert); - dersz = i2d_X509(cert, &der); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + dersz = sym_i2d_X509(cert, &der); if (dersz < 0) return log_openssl_errors("Unable to convert PEM certificate to DER format"); @@ -1569,12 +2341,16 @@ int openssl_extract_public_key(EVP_PKEY *private_key, EVP_PKEY **ret) { assert(private_key); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + _cleanup_(memstream_done) MemStream m = {}; FILE *tf = memstream_init(&m); if (!tf) return -ENOMEM; - if (i2d_PUBKEY_fp(tf, private_key) != 1) + if (sym_i2d_PUBKEY_fp(tf, private_key) != 1) return -EIO; _cleanup_(erase_and_freep) char *buf = NULL; @@ -1584,7 +2360,7 @@ int openssl_extract_public_key(EVP_PKEY *private_key, EVP_PKEY **ret) { return r; const unsigned char *t = (const unsigned char*) buf; - if (!d2i_PUBKEY(ret, &t, len)) + if (!sym_d2i_PUBKEY(ret, &t, len)) return -EIO; return 0; diff --git a/src/shared/crypto-util.h b/src/shared/crypto-util.h new file mode 100644 index 0000000000000..08c5e1ac8511d --- /dev/null +++ b/src/shared/crypto-util.h @@ -0,0 +1,406 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" +#include "iovec-util.h" +#include "sha256.h" + +typedef enum CertificateSourceType { + OPENSSL_CERTIFICATE_SOURCE_FILE, + OPENSSL_CERTIFICATE_SOURCE_PROVIDER, + _OPENSSL_CERTIFICATE_SOURCE_MAX, + _OPENSSL_CERTIFICATE_SOURCE_INVALID = -EINVAL, +} CertificateSourceType; + +typedef enum KeySourceType { + OPENSSL_KEY_SOURCE_FILE, + OPENSSL_KEY_SOURCE_ENGINE, + OPENSSL_KEY_SOURCE_PROVIDER, + _OPENSSL_KEY_SOURCE_MAX, + _OPENSSL_KEY_SOURCE_INVALID = -EINVAL, +} KeySourceType; + +typedef struct OpenSSLAskPasswordUI OpenSSLAskPasswordUI; + +int parse_openssl_certificate_source_argument(const char *argument, char **certificate_source, CertificateSourceType *certificate_source_type); + +int parse_openssl_key_source_argument(const char *argument, char **private_key_source, KeySourceType *private_key_source_type); + +int dlopen_libcrypto(int log_level); + +#define X509_FINGERPRINT_SIZE SHA256_DIGEST_SIZE + +#if HAVE_OPENSSL +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ + +# include "dlfcn-util.h" + +extern DLSYM_PROTOTYPE(ASN1_ANY_it); +extern DLSYM_PROTOTYPE(ASN1_BIT_STRING_it); +extern DLSYM_PROTOTYPE(ASN1_BMPSTRING_it); +extern DLSYM_PROTOTYPE(ASN1_BMPSTRING_new); +extern DLSYM_PROTOTYPE(ASN1_get_object); +extern DLSYM_PROTOTYPE(ASN1_IA5STRING_it); +extern DLSYM_PROTOTYPE(ASN1_item_d2i); +extern DLSYM_PROTOTYPE(ASN1_item_free); +extern DLSYM_PROTOTYPE(ASN1_item_i2d); +extern DLSYM_PROTOTYPE(ASN1_item_new); +extern DLSYM_PROTOTYPE(ASN1_OBJECT_it); +extern DLSYM_PROTOTYPE(ASN1_OCTET_STRING_free); +extern DLSYM_PROTOTYPE(ASN1_OCTET_STRING_it); +extern DLSYM_PROTOTYPE(ASN1_OCTET_STRING_set); +extern DLSYM_PROTOTYPE(ASN1_STRING_get0_data); +extern DLSYM_PROTOTYPE(ASN1_STRING_length); +extern DLSYM_PROTOTYPE(ASN1_STRING_new); +extern DLSYM_PROTOTYPE(ASN1_STRING_set); +extern DLSYM_PROTOTYPE(ASN1_STRING_set0); +extern DLSYM_PROTOTYPE(ASN1_TIME_free); +extern DLSYM_PROTOTYPE(ASN1_TIME_set); +extern DLSYM_PROTOTYPE(ASN1_TYPE_new); +extern DLSYM_PROTOTYPE(BIO_ctrl); +extern DLSYM_PROTOTYPE(BIO_find_type); +extern DLSYM_PROTOTYPE(BIO_free_all); +extern DLSYM_PROTOTYPE(BIO_free); +extern DLSYM_PROTOTYPE(BIO_new_mem_buf); +extern DLSYM_PROTOTYPE(BIO_new_socket); +extern DLSYM_PROTOTYPE(BIO_new); +extern DLSYM_PROTOTYPE(BIO_s_mem); +extern DLSYM_PROTOTYPE(BIO_write); +extern DLSYM_PROTOTYPE(BN_bin2bn); +extern DLSYM_PROTOTYPE(BN_bn2nativepad); +extern DLSYM_PROTOTYPE(BN_CTX_free); +extern DLSYM_PROTOTYPE(BN_CTX_new); +extern DLSYM_PROTOTYPE(BN_free); +extern DLSYM_PROTOTYPE(BN_new); +extern DLSYM_PROTOTYPE(BN_num_bits); +extern DLSYM_PROTOTYPE(CRYPTO_free); +extern DLSYM_PROTOTYPE(d2i_ASN1_OCTET_STRING); +extern DLSYM_PROTOTYPE(d2i_ECPKParameters); +extern DLSYM_PROTOTYPE(d2i_PKCS7); +extern DLSYM_PROTOTYPE(d2i_PUBKEY); +extern DLSYM_PROTOTYPE(d2i_X509); +extern DLSYM_PROTOTYPE(EC_GROUP_free); +extern DLSYM_PROTOTYPE(EC_GROUP_get_curve_name); +extern DLSYM_PROTOTYPE(EC_GROUP_get_curve); +extern DLSYM_PROTOTYPE(EC_GROUP_get_field_type); +extern DLSYM_PROTOTYPE(EC_GROUP_get0_generator); +extern DLSYM_PROTOTYPE(EC_GROUP_get0_order); +extern DLSYM_PROTOTYPE(EC_GROUP_new_by_curve_name); +extern DLSYM_PROTOTYPE(EC_POINT_free); +extern DLSYM_PROTOTYPE(EC_POINT_new); +extern DLSYM_PROTOTYPE(EC_POINT_oct2point); +extern DLSYM_PROTOTYPE(EC_POINT_point2oct); +extern DLSYM_PROTOTYPE(ECDSA_SIG_free); +extern DLSYM_PROTOTYPE(ERR_clear_error); +extern DLSYM_PROTOTYPE(ERR_error_string_n); +extern DLSYM_PROTOTYPE(ERR_error_string); +extern DLSYM_PROTOTYPE(ERR_get_error); +extern DLSYM_PROTOTYPE(EVP_aes_256_ctr); +extern DLSYM_PROTOTYPE(EVP_aes_256_gcm); +extern DLSYM_PROTOTYPE(EVP_CIPHER_CTX_ctrl); +extern DLSYM_PROTOTYPE(EVP_CIPHER_CTX_free); +extern DLSYM_PROTOTYPE(EVP_CIPHER_CTX_new); +extern DLSYM_PROTOTYPE(EVP_CIPHER_free); +extern DLSYM_PROTOTYPE(EVP_CIPHER_get_block_size); +extern DLSYM_PROTOTYPE(EVP_CIPHER_get_iv_length); +extern DLSYM_PROTOTYPE(EVP_CIPHER_get_key_length); +extern DLSYM_PROTOTYPE(EVP_DecryptFinal_ex); +extern DLSYM_PROTOTYPE(EVP_DecryptInit_ex); +extern DLSYM_PROTOTYPE(EVP_DecryptUpdate); +extern DLSYM_PROTOTYPE(EVP_Digest); +extern DLSYM_PROTOTYPE(EVP_DigestFinal_ex); +extern DLSYM_PROTOTYPE(EVP_DigestInit_ex); +extern DLSYM_PROTOTYPE(EVP_DigestUpdate); +extern DLSYM_PROTOTYPE(EVP_DigestVerify); +extern DLSYM_PROTOTYPE(EVP_DigestVerifyInit); +extern DLSYM_PROTOTYPE(EVP_EncryptFinal_ex); +extern DLSYM_PROTOTYPE(EVP_EncryptInit_ex); +extern DLSYM_PROTOTYPE(EVP_EncryptUpdate); +extern DLSYM_PROTOTYPE(EVP_get_cipherbyname); +extern DLSYM_PROTOTYPE(EVP_get_digestbyname); +extern DLSYM_PROTOTYPE(EVP_MAC_CTX_free); +extern DLSYM_PROTOTYPE(EVP_MAC_free); +extern DLSYM_PROTOTYPE(EVP_MD_CTX_free); +extern DLSYM_PROTOTYPE(EVP_MD_CTX_get0_md); +extern DLSYM_PROTOTYPE(EVP_MD_CTX_new); +extern DLSYM_PROTOTYPE(EVP_MD_CTX_set_pkey_ctx); +extern DLSYM_PROTOTYPE(EVP_MD_free); +extern DLSYM_PROTOTYPE(EVP_MD_get_size); +extern DLSYM_PROTOTYPE(EVP_MD_get0_name); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_free); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_new_from_name); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_new_id); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_new); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_rsa_padding); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_signature_md); +extern DLSYM_PROTOTYPE(EVP_PKEY_eq); +extern DLSYM_PROTOTYPE(EVP_PKEY_free); +extern DLSYM_PROTOTYPE(EVP_PKEY_fromdata_init); +extern DLSYM_PROTOTYPE(EVP_PKEY_fromdata); +extern DLSYM_PROTOTYPE(EVP_PKEY_get_id); +extern DLSYM_PROTOTYPE(EVP_PKEY_keygen_init); +extern DLSYM_PROTOTYPE(EVP_PKEY_keygen); +extern DLSYM_PROTOTYPE(EVP_PKEY_new_raw_public_key); +extern DLSYM_PROTOTYPE(EVP_PKEY_new); +extern DLSYM_PROTOTYPE(EVP_PKEY_verify_init); +extern DLSYM_PROTOTYPE(EVP_PKEY_verify); +extern DLSYM_PROTOTYPE(EVP_sha1); +extern DLSYM_PROTOTYPE(EVP_sha256); +extern DLSYM_PROTOTYPE(EVP_sha384); +extern DLSYM_PROTOTYPE(EVP_sha512); +extern DLSYM_PROTOTYPE(HMAC); +extern DLSYM_PROTOTYPE(i2d_ASN1_INTEGER); +extern DLSYM_PROTOTYPE(i2d_PKCS7_fp); +extern DLSYM_PROTOTYPE(i2d_PKCS7); +extern DLSYM_PROTOTYPE(i2d_PUBKEY); +extern DLSYM_PROTOTYPE(i2d_X509_NAME); +extern DLSYM_PROTOTYPE(i2d_X509); +extern DLSYM_PROTOTYPE(OBJ_nid2obj); +extern DLSYM_PROTOTYPE(OBJ_nid2sn); +extern DLSYM_PROTOTYPE(OBJ_sn2nid); +extern DLSYM_PROTOTYPE(OBJ_txt2obj); +extern DLSYM_PROTOTYPE(OPENSSL_sk_new_null); +extern DLSYM_PROTOTYPE(OPENSSL_sk_num); +extern DLSYM_PROTOTYPE(OPENSSL_sk_pop_free); +extern DLSYM_PROTOTYPE(OPENSSL_sk_push); +extern DLSYM_PROTOTYPE(OPENSSL_sk_value); +extern DLSYM_PROTOTYPE(OSSL_EC_curve_nid2name); +extern DLSYM_PROTOTYPE(OSSL_PARAM_construct_BN); +extern DLSYM_PROTOTYPE(OSSL_PARAM_construct_end); +extern DLSYM_PROTOTYPE(OSSL_PARAM_construct_octet_string); +extern DLSYM_PROTOTYPE(OSSL_PARAM_construct_utf8_string); +extern DLSYM_PROTOTYPE(OSSL_PARAM_free); +extern DLSYM_PROTOTYPE(PEM_read_PrivateKey); +extern DLSYM_PROTOTYPE(PEM_read_PUBKEY); +extern DLSYM_PROTOTYPE(PEM_read_X509); +extern DLSYM_PROTOTYPE(PEM_write_PrivateKey); +extern DLSYM_PROTOTYPE(PEM_write_PUBKEY); +extern DLSYM_PROTOTYPE(PEM_write_X509); +extern DLSYM_PROTOTYPE(PKCS5_PBKDF2_HMAC); +extern DLSYM_PROTOTYPE(PKCS7_add_attrib_content_type); +extern DLSYM_PROTOTYPE(PKCS7_add_attrib_smimecap); +extern DLSYM_PROTOTYPE(PKCS7_add_signed_attribute); +extern DLSYM_PROTOTYPE(PKCS7_add0_attrib_signing_time); +extern DLSYM_PROTOTYPE(PKCS7_add1_attrib_digest); +extern DLSYM_PROTOTYPE(PKCS7_ATTR_SIGN_it); +extern DLSYM_PROTOTYPE(PKCS7_content_new); +extern DLSYM_PROTOTYPE(PKCS7_ctrl); +extern DLSYM_PROTOTYPE(PKCS7_dataFinal); +extern DLSYM_PROTOTYPE(PKCS7_dataInit); +extern DLSYM_PROTOTYPE(PKCS7_free); +extern DLSYM_PROTOTYPE(PKCS7_get_signer_info); +extern DLSYM_PROTOTYPE(PKCS7_new); +extern DLSYM_PROTOTYPE(PKCS7_set_content); +extern DLSYM_PROTOTYPE(PKCS7_sign); +extern DLSYM_PROTOTYPE(PKCS7_SIGNER_INFO_free); +extern DLSYM_PROTOTYPE(PKCS7_verify); +extern DLSYM_PROTOTYPE(SHA1); +extern DLSYM_PROTOTYPE(SHA512); +extern DLSYM_PROTOTYPE(X509_ALGOR_free); +extern DLSYM_PROTOTYPE(X509_ATTRIBUTE_free); +extern DLSYM_PROTOTYPE(X509_free); +extern DLSYM_PROTOTYPE(X509_get_pubkey); +extern DLSYM_PROTOTYPE(X509_get_subject_name); +extern DLSYM_PROTOTYPE(X509_gmtime_adj); +extern DLSYM_PROTOTYPE(X509_NAME_free); +extern DLSYM_PROTOTYPE(X509_NAME_oneline); +extern DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set_hostflags); +extern DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set1_host); +extern DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set1_ip); + +#if !defined(OPENSSL_NO_DEPRECATED_3_0) +DISABLE_WARNING_DEPRECATED_DECLARATIONS; +extern DLSYM_PROTOTYPE(ECDSA_SIG_new); +extern DLSYM_PROTOTYPE(ECDSA_SIG_set0); +extern DLSYM_PROTOTYPE(ECDSA_do_verify); +extern DLSYM_PROTOTYPE(EC_KEY_check_key); +extern DLSYM_PROTOTYPE(EC_KEY_free); +extern DLSYM_PROTOTYPE(EC_KEY_new); +extern DLSYM_PROTOTYPE(EC_KEY_set_group); +extern DLSYM_PROTOTYPE(EC_KEY_set_public_key); +extern DLSYM_PROTOTYPE(EVP_PKEY_assign); +extern DLSYM_PROTOTYPE(RSA_free); +extern DLSYM_PROTOTYPE(RSA_new); +extern DLSYM_PROTOTYPE(RSA_set0_key); +extern DLSYM_PROTOTYPE(RSA_size); +extern DLSYM_PROTOTYPE(RSAPublicKey_dup); +REENABLE_WARNING; + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EC_KEY*, sym_EC_KEY_free, EC_KEY_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(RSA*, sym_RSA_free, RSA_freep, NULL); +#endif + +/* Mirrors of OpenSSL macros that go through our dlopen'd sym_* variants, so we don't end up linking against + * libcrypto just for these. */ +#define sym_BIO_get_md_ctx(b, mdcp) sym_BIO_ctrl((b), BIO_C_GET_MD_CTX, 0, (char*) (mdcp)) +#define sym_BIO_get_mem_ptr(b, pp) sym_BIO_ctrl((b), BIO_C_GET_BUF_MEM_PTR, 0, (char *) (pp)) +#define sym_BIO_reset(b) sym_BIO_ctrl((b), BIO_CTRL_RESET, 0, NULL) +#define sym_BN_num_bytes(a) ((sym_BN_num_bits(a) + 7) / 8) +#define sym_EVP_MD_CTX_get_size(ctx) sym_EVP_MD_get_size(sym_EVP_MD_CTX_get0_md(ctx)) +#define sym_EVP_MD_CTX_get0_name(ctx) sym_EVP_MD_get0_name(sym_EVP_MD_CTX_get0_md(ctx)) +#define sym_EVP_PKEY_assign_RSA(pkey, rsa) sym_EVP_PKEY_assign((pkey), EVP_PKEY_RSA, (rsa)) +#define sym_OPENSSL_free(addr) sym_CRYPTO_free((addr), OPENSSL_FILE, OPENSSL_LINE) +#define sym_PKCS7_set_detached(p, v) sym_PKCS7_ctrl((p), PKCS7_OP_SET_DETACHED_SIGNATURE, (v), NULL) + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO_RENAME(void*, sym_OPENSSL_free, OPENSSL_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ASN1_OCTET_STRING*, sym_ASN1_OCTET_STRING_free, ASN1_OCTET_STRING_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ASN1_TIME*, sym_ASN1_TIME_free, ASN1_TIME_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(BIGNUM*, sym_BN_free, BN_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(BIO*, sym_BIO_free_all, BIO_free_allp, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(BIO*, sym_BIO_free, BIO_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(BN_CTX*, sym_BN_CTX_free, BN_CTX_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EC_GROUP*, sym_EC_GROUP_free, EC_GROUP_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EC_POINT*, sym_EC_POINT_free, EC_POINT_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ECDSA_SIG*, sym_ECDSA_SIG_free, ECDSA_SIG_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_CIPHER_CTX*, sym_EVP_CIPHER_CTX_free, EVP_CIPHER_CTX_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_CIPHER*, sym_EVP_CIPHER_free, EVP_CIPHER_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_MAC_CTX*, sym_EVP_MAC_CTX_free, EVP_MAC_CTX_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_MAC*, sym_EVP_MAC_free, EVP_MAC_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_MD_CTX*, sym_EVP_MD_CTX_free, EVP_MD_CTX_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_MD*, sym_EVP_MD_free, EVP_MD_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_PKEY_CTX*, sym_EVP_PKEY_CTX_free, EVP_PKEY_CTX_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_PKEY*, sym_EVP_PKEY_free, EVP_PKEY_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(OSSL_PARAM*, sym_OSSL_PARAM_free, OSSL_PARAM_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(PKCS7_SIGNER_INFO*, sym_PKCS7_SIGNER_INFO_free, PKCS7_SIGNER_INFO_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(PKCS7*, sym_PKCS7_free, PKCS7_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(X509_NAME*, sym_X509_NAME_free, X509_NAME_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(X509*, sym_X509_free, X509_freep, NULL); + +/* Stack-of macros that go through the dlopen'd sym_OPENSSL_sk_* variants, mirroring the sk_TYPE_OP() helpers + * from and friends. */ +#define sym_sk_X509_new_null() \ + ((STACK_OF(X509)*) sym_OPENSSL_sk_new_null()) +#define sym_sk_X509_push(sk, ptr) \ + sym_OPENSSL_sk_push(ossl_check_X509_sk_type(sk), ossl_check_X509_type(ptr)) +#define sym_sk_X509_pop_free(sk, freefunc) \ + sym_OPENSSL_sk_pop_free(ossl_check_X509_sk_type(sk), ossl_check_X509_freefunc_type(freefunc)) +#define sym_sk_X509_ALGOR_pop_free(sk, freefunc) \ + sym_OPENSSL_sk_pop_free(ossl_check_X509_ALGOR_sk_type(sk), ossl_check_X509_ALGOR_freefunc_type(freefunc)) +#define sym_sk_X509_ATTRIBUTE_pop_free(sk, freefunc) \ + sym_OPENSSL_sk_pop_free(ossl_check_X509_ATTRIBUTE_sk_type(sk), ossl_check_X509_ATTRIBUTE_freefunc_type(freefunc)) +#define sym_sk_PKCS7_SIGNER_INFO_num(sk) \ + sym_OPENSSL_sk_num(ossl_check_const_PKCS7_SIGNER_INFO_sk_type(sk)) +#define sym_sk_PKCS7_SIGNER_INFO_value(sk, idx) \ + ((PKCS7_SIGNER_INFO*) sym_OPENSSL_sk_value(ossl_check_const_PKCS7_SIGNER_INFO_sk_type(sk), (idx))) + +static inline STACK_OF(X509_ALGOR) *x509_algor_free_many(STACK_OF(X509_ALGOR) *attrs) { + if (!attrs) + return NULL; + + sym_sk_X509_ALGOR_pop_free(attrs, sym_X509_ALGOR_free); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(STACK_OF(X509_ALGOR)*, x509_algor_free_many, NULL); + +static inline STACK_OF(X509_ATTRIBUTE) *x509_attribute_free_many(STACK_OF(X509_ATTRIBUTE) *attrs) { + if (!attrs) + return NULL; + + sym_sk_X509_ATTRIBUTE_pop_free(attrs, sym_X509_ATTRIBUTE_free); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(STACK_OF(X509_ATTRIBUTE)*, x509_attribute_free_many, NULL); + +static inline void sk_X509_free_allp(STACK_OF(X509) **sk) { + if (!sk || !*sk) + return; + + sym_sk_X509_pop_free(*sk, sym_X509_free); +} + +int openssl_pubkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret); +int openssl_pubkey_to_pem(EVP_PKEY *pkey, char **ret); + +int openssl_digest_size(const char *digest_alg, size_t *ret_digest_size); + +int openssl_digest_many(const char *digest_alg, const struct iovec data[], size_t n_data, void **ret_digest, size_t *ret_digest_size); + +static inline int openssl_digest(const char *digest_alg, const void *buf, size_t len, void **ret_digest, size_t *ret_digest_size) { + return openssl_digest_many(digest_alg, &IOVEC_MAKE((void*) buf, len), 1, ret_digest, ret_digest_size); +} + +int openssl_hmac_many(const char *digest_alg, const void *key, size_t key_size, const struct iovec data[], size_t n_data, void **ret_digest, size_t *ret_digest_size); + +static inline int openssl_hmac(const char *digest_alg, const void *key, size_t key_size, const void *buf, size_t len, void **ret_digest, size_t *ret_digest_size) { + return openssl_hmac_many(digest_alg, key, key_size, &IOVEC_MAKE((void*) buf, len), 1, ret_digest, ret_digest_size); +} + +int openssl_cipher_many(const char *alg, size_t bits, const char *mode, const void *key, size_t key_size, const void *iv, size_t iv_size, const struct iovec data[], size_t n_data, void **ret, size_t *ret_size); + +static inline int openssl_cipher(const char *alg, size_t bits, const char *mode, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *buf, size_t len, void **ret, size_t *ret_size) { + return openssl_cipher_many(alg, bits, mode, key, key_size, iv, iv_size, &IOVEC_MAKE((void*) buf, len), 1, ret, ret_size); +} + +int kdf_ss_derive(const char *digest, const void *key, size_t key_size, const void *salt, size_t salt_size, const void *info, size_t info_size, size_t derive_size, void **ret); + +int kdf_kb_hmac_derive(const char *mode, const char *digest, const void *key, size_t key_size, const void *salt, size_t salt_size, const void *info, size_t info_size, const void *seed, size_t seed_size, size_t derive_size, void **ret); + +int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); + +int rsa_oaep_encrypt_bytes(const EVP_PKEY *pkey, const char *digest_alg, const char *label, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); + +int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size); + +int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size, EVP_PKEY **ret); + +int rsa_pkey_to_n_e(const EVP_PKEY *pkey, void **ret_n, size_t *ret_n_size, void **ret_e, size_t *ret_e_size); + +int ecc_pkey_from_curve_x_y(int curve_id, const void *x, size_t x_size, const void *y, size_t y_size, EVP_PKEY **ret); + +int ecc_pkey_to_curve_x_y(const EVP_PKEY *pkey, int *ret_curve_id, void **ret_x, size_t *ret_x_size, void **ret_y, size_t *ret_y_size); + +int ecc_pkey_new(int curve_id, EVP_PKEY **ret); + +int ecc_ecdh(const EVP_PKEY *private_pkey, const EVP_PKEY *peer_pkey, void **ret_shared_secret, size_t *ret_shared_secret_size); + +int pkey_generate_volume_keys(EVP_PKEY *pkey, void **ret_decrypted_key, size_t *ret_decrypted_key_size, void **ret_saved_key, size_t *ret_saved_key_size); + +int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size); + +int digest_and_sign(const EVP_MD *md, EVP_PKEY *privkey, const void *data, size_t size, void **ret, size_t *ret_size); + +int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, const char *hash_algorithm, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si); + +int string_hashsum(const char *s, size_t len, const char *md_algorithm, char **ret); +static inline int string_hashsum_sha224(const char *s, size_t len, char **ret) { + return string_hashsum(s, len, "SHA224", ret); +} +static inline int string_hashsum_sha256(const char *s, size_t len, char **ret) { + return string_hashsum(s, len, "SHA256", ret); +} + +int x509_fingerprint(X509 *cert, uint8_t buffer[static X509_FINGERPRINT_SIZE]); + +int openssl_load_x509_certificate( + CertificateSourceType certificate_source_type, + const char *certificate_source, + const char *certificate, + X509 **ret); + +int openssl_load_private_key( + KeySourceType private_key_source_type, + const char *private_key_source, + const char *private_key, + const AskPasswordRequest *request, + EVP_PKEY **ret_private_key, + OpenSSLAskPasswordUI **ret_user_interface); + +int openssl_extract_public_key(EVP_PKEY *private_key, EVP_PKEY **ret); + +OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OpenSSLAskPasswordUI*, openssl_ask_password_ui_free, NULL); + +#endif diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index a766a92b2037f..47a148459d95f 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -2,6 +2,7 @@ #include +#include "sd-dlopen.h" #include "sd-json.h" #include "alloc-util.h" @@ -20,7 +21,9 @@ static void *cryptsetup_dl = NULL; DLSYM_PROTOTYPE(crypt_activate_by_passphrase) = NULL; DLSYM_PROTOTYPE(crypt_activate_by_signed_key) = NULL; +DLSYM_PROTOTYPE(crypt_activate_by_token_pin) = NULL; DLSYM_PROTOTYPE(crypt_activate_by_volume_key) = NULL; +DLSYM_PROTOTYPE(crypt_deactivate) = NULL; DLSYM_PROTOTYPE(crypt_deactivate_by_name) = NULL; DLSYM_PROTOTYPE(crypt_format) = NULL; DLSYM_PROTOTYPE(crypt_free) = NULL; @@ -36,11 +39,15 @@ DLSYM_PROTOTYPE(crypt_get_volume_key_size) = NULL; DLSYM_PROTOTYPE(crypt_header_restore) = NULL; DLSYM_PROTOTYPE(crypt_init) = NULL; DLSYM_PROTOTYPE(crypt_init_by_name) = NULL; +DLSYM_PROTOTYPE(crypt_init_data_device) = NULL; DLSYM_PROTOTYPE(crypt_keyslot_add_by_volume_key) = NULL; DLSYM_PROTOTYPE(crypt_keyslot_destroy) = NULL; DLSYM_PROTOTYPE(crypt_keyslot_max) = NULL; +DLSYM_PROTOTYPE(crypt_keyslot_status) = NULL; DLSYM_PROTOTYPE(crypt_load) = NULL; DLSYM_PROTOTYPE(crypt_metadata_locking) = NULL; +DLSYM_PROTOTYPE(crypt_persistent_flags_get) = NULL; +DLSYM_PROTOTYPE(crypt_persistent_flags_set) = NULL; DLSYM_PROTOTYPE(crypt_reencrypt_init_by_passphrase) = NULL; DLSYM_PROTOTYPE(crypt_reencrypt_run); DLSYM_PROTOTYPE(crypt_resize) = NULL; @@ -48,10 +55,15 @@ DLSYM_PROTOTYPE(crypt_resume_by_volume_key) = NULL; DLSYM_PROTOTYPE(crypt_set_data_device) = NULL; DLSYM_PROTOTYPE(crypt_set_data_offset) = NULL; DLSYM_PROTOTYPE(crypt_set_debug_level) = NULL; +#if HAVE_CRYPT_SET_KEYRING_TO_LINK +DLSYM_PROTOTYPE(crypt_set_keyring_to_link) = NULL; +#endif DLSYM_PROTOTYPE(crypt_set_log_callback) = NULL; DLSYM_PROTOTYPE(crypt_set_metadata_size) = NULL; DLSYM_PROTOTYPE(crypt_set_pbkdf_type) = NULL; +DLSYM_PROTOTYPE(crypt_status) = NULL; DLSYM_PROTOTYPE(crypt_suspend) = NULL; +DLSYM_PROTOTYPE(crypt_token_external_path) = NULL; DLSYM_PROTOTYPE(crypt_token_json_get) = NULL; DLSYM_PROTOTYPE(crypt_token_json_set) = NULL; DLSYM_PROTOTYPE(crypt_token_max) = NULL; @@ -95,7 +107,7 @@ void cryptsetup_enable_logging(struct crypt_device *cd) { * endless loop, but isn't because we break it via the check for 'cryptsetup_dl' early in * dlopen_cryptsetup(). */ - if (dlopen_cryptsetup() < 0) + if (dlopen_cryptsetup(LOG_DEBUG) < 0) return; /* If this fails, let's gracefully ignore the issue, this is just debug logging after * all, and if this failed we already generated a debug log message that should help * to track things down. */ @@ -121,10 +133,6 @@ int cryptsetup_set_minimal_pbkdf(struct crypt_device *cd) { /* Sets a minimal PKBDF in case we already have a high entropy key. */ - r = dlopen_cryptsetup(); - if (r < 0) - return r; - r = sym_crypt_set_pbkdf_type(cd, &minimal_pbkdf); if (r < 0) return r; @@ -152,10 +160,6 @@ int cryptsetup_get_token_as_json( * -EMEDIUMTYPE → "verify_type" specified and doesn't match token's type */ - r = dlopen_cryptsetup(); - if (r < 0) - return r; - r = sym_crypt_token_json_get(cd, idx, &text); if (r < 0) return r; @@ -185,10 +189,6 @@ int cryptsetup_add_token_json(struct crypt_device *cd, sd_json_variant *v) { _cleanup_free_ char *text = NULL; int r; - r = dlopen_cryptsetup(); - if (r < 0) - return r; - r = sd_json_variant_format(v, 0, &text); if (r < 0) return log_debug_errno(r, "Failed to format token data for LUKS: %m"); @@ -211,6 +211,8 @@ int cryptsetup_get_volume_key_prefix( const char *uuid; char *s; + assert(ret); + uuid = sym_crypt_get_uuid(cd); if (!uuid) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get LUKS UUID."); @@ -245,6 +247,8 @@ int cryptsetup_get_volume_key_id( char *hex; int r; + assert(ret); + r = cryptsetup_get_volume_key_prefix(cd, volume_name, &prefix); if (r < 0) return log_debug_errno(r, "Failed to get LUKS volume key prefix."); @@ -261,7 +265,7 @@ int cryptsetup_get_volume_key_id( } #endif -int dlopen_cryptsetup(void) { +int dlopen_cryptsetup(int log_level) { #if HAVE_LIBCRYPTSETUP int r; @@ -270,16 +274,19 @@ int dlopen_cryptsetup(void) { * still available though, and given we want to support 2.2.0 for a while longer, we'll use the old * symbol if the new one is not available. */ - ELF_NOTE_DLOPEN("cryptsetup", + SD_ELF_NOTE_DLOPEN( + "cryptsetup", "Support for disk encryption, integrity, and authentication", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libcryptsetup.so.12"); r = dlopen_many_sym_or_warn( - &cryptsetup_dl, "libcryptsetup.so.12", LOG_DEBUG, + &cryptsetup_dl, "libcryptsetup.so.12", log_level, DLSYM_ARG(crypt_activate_by_passphrase), DLSYM_ARG(crypt_activate_by_signed_key), + DLSYM_ARG(crypt_activate_by_token_pin), DLSYM_ARG(crypt_activate_by_volume_key), + DLSYM_ARG(crypt_deactivate), DLSYM_ARG(crypt_deactivate_by_name), DLSYM_ARG(crypt_format), DLSYM_ARG(crypt_free), @@ -295,11 +302,15 @@ int dlopen_cryptsetup(void) { DLSYM_ARG(crypt_header_restore), DLSYM_ARG(crypt_init), DLSYM_ARG(crypt_init_by_name), + DLSYM_ARG(crypt_init_data_device), DLSYM_ARG(crypt_keyslot_add_by_volume_key), DLSYM_ARG(crypt_keyslot_destroy), DLSYM_ARG(crypt_keyslot_max), + DLSYM_ARG(crypt_keyslot_status), DLSYM_ARG(crypt_load), DLSYM_ARG(crypt_metadata_locking), + DLSYM_ARG(crypt_persistent_flags_get), + DLSYM_ARG(crypt_persistent_flags_set), DLSYM_ARG(crypt_reencrypt_init_by_passphrase), DLSYM_ARG(crypt_reencrypt_run), DLSYM_ARG(crypt_resize), @@ -307,10 +318,15 @@ int dlopen_cryptsetup(void) { DLSYM_ARG(crypt_set_data_device), DLSYM_ARG(crypt_set_data_offset), DLSYM_ARG(crypt_set_debug_level), +#if HAVE_CRYPT_SET_KEYRING_TO_LINK + DLSYM_ARG(crypt_set_keyring_to_link), +#endif DLSYM_ARG(crypt_set_log_callback), DLSYM_ARG(crypt_set_metadata_size), DLSYM_ARG(crypt_set_pbkdf_type), + DLSYM_ARG(crypt_status), DLSYM_ARG(crypt_suspend), + DLSYM_ARG(crypt_token_external_path), DLSYM_ARG(crypt_token_json_get), DLSYM_ARG(crypt_token_json_set), DLSYM_ARG(crypt_token_max), @@ -345,7 +361,8 @@ int dlopen_cryptsetup(void) { return 1; #else - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "cryptsetup support is not compiled in."); + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libcryptsetup support is not compiled in."); #endif } diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index e9be8249fa1a0..4e994ce9f9951 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -9,7 +9,9 @@ extern DLSYM_PROTOTYPE(crypt_activate_by_passphrase); extern DLSYM_PROTOTYPE(crypt_activate_by_signed_key); +extern DLSYM_PROTOTYPE(crypt_activate_by_token_pin); extern DLSYM_PROTOTYPE(crypt_activate_by_volume_key); +extern DLSYM_PROTOTYPE(crypt_deactivate); extern DLSYM_PROTOTYPE(crypt_deactivate_by_name); extern DLSYM_PROTOTYPE(crypt_format); extern DLSYM_PROTOTYPE(crypt_free); @@ -25,11 +27,15 @@ extern DLSYM_PROTOTYPE(crypt_get_volume_key_size); extern DLSYM_PROTOTYPE(crypt_header_restore); extern DLSYM_PROTOTYPE(crypt_init); extern DLSYM_PROTOTYPE(crypt_init_by_name); +extern DLSYM_PROTOTYPE(crypt_init_data_device); extern DLSYM_PROTOTYPE(crypt_keyslot_add_by_volume_key); extern DLSYM_PROTOTYPE(crypt_keyslot_destroy); extern DLSYM_PROTOTYPE(crypt_keyslot_max); +extern DLSYM_PROTOTYPE(crypt_keyslot_status); extern DLSYM_PROTOTYPE(crypt_load); extern DLSYM_PROTOTYPE(crypt_metadata_locking); +extern DLSYM_PROTOTYPE(crypt_persistent_flags_get); +extern DLSYM_PROTOTYPE(crypt_persistent_flags_set); extern DLSYM_PROTOTYPE(crypt_reencrypt_init_by_passphrase); extern DLSYM_PROTOTYPE(crypt_reencrypt_run); extern DLSYM_PROTOTYPE(crypt_resize); @@ -37,10 +43,15 @@ extern DLSYM_PROTOTYPE(crypt_resume_by_volume_key); extern DLSYM_PROTOTYPE(crypt_set_data_device); extern DLSYM_PROTOTYPE(crypt_set_data_offset); extern DLSYM_PROTOTYPE(crypt_set_debug_level); +#if HAVE_CRYPT_SET_KEYRING_TO_LINK +extern DLSYM_PROTOTYPE(crypt_set_keyring_to_link); +#endif extern DLSYM_PROTOTYPE(crypt_set_log_callback); extern DLSYM_PROTOTYPE(crypt_set_metadata_size); extern DLSYM_PROTOTYPE(crypt_set_pbkdf_type); +extern DLSYM_PROTOTYPE(crypt_status); extern DLSYM_PROTOTYPE(crypt_suspend); +extern DLSYM_PROTOTYPE(crypt_token_external_path); extern DLSYM_PROTOTYPE(crypt_token_json_get); extern DLSYM_PROTOTYPE(crypt_token_json_set); extern DLSYM_PROTOTYPE(crypt_token_max); @@ -53,10 +64,9 @@ extern DLSYM_PROTOTYPE(crypt_volume_key_keyring); extern DLSYM_PROTOTYPE(crypt_wipe); extern DLSYM_PROTOTYPE(crypt_get_integrity_info); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, crypt_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, sym_crypt_free, NULL); - -/* Be careful, this works with dlopen_cryptsetup(), that is, it calls sym_crypt_free() instead of crypt_free(). */ +/* Be careful, these work with dlopen_cryptsetup(), that is, they call sym_crypt_free() instead of + * crypt_free() and hence depend on dlopen_cryptsetup() having been called. */ +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct crypt_device *, sym_crypt_free, crypt_freep, NULL); #define crypt_free_and_replace(a, b) \ free_and_replace_full(a, b, sym_crypt_free) @@ -71,7 +81,7 @@ int cryptsetup_get_volume_key_id(struct crypt_device *cd, const char *volume_nam size_t volume_key_size, char **ret); #endif -int dlopen_cryptsetup(void); +int dlopen_cryptsetup(int log_level); int cryptsetup_get_keyslot_from_token(sd_json_variant *v); diff --git a/src/import/curl-util.c b/src/shared/curl-util.c similarity index 64% rename from src/import/curl-util.c rename to src/shared/curl-util.c index 4747d0993a8c3..9254b83dd74fb 100644 --- a/src/import/curl-util.c +++ b/src/shared/curl-util.c @@ -1,16 +1,45 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "curl-util.h" +#include "log.h" + +#if HAVE_LIBCURL + +#include "sd-dlopen.h" #include "sd-event.h" #include "alloc-util.h" -#include "curl-util.h" +#include "dlfcn-util.h" #include "fd-util.h" #include "hashmap.h" -#include "log.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" #include "version.h" +static void *curl_dl = NULL; + +DLSYM_PROTOTYPE(curl_easy_cleanup) = NULL; +DLSYM_PROTOTYPE(curl_easy_getinfo) = NULL; +DLSYM_PROTOTYPE(curl_easy_init) = NULL; +DLSYM_PROTOTYPE(curl_easy_perform) = NULL; +DLSYM_PROTOTYPE(curl_easy_setopt) = NULL; +DLSYM_PROTOTYPE(curl_easy_strerror) = NULL; +#if LIBCURL_VERSION_NUM >= 0x075300 +DLSYM_PROTOTYPE(curl_easy_header) = NULL; +#endif +DLSYM_PROTOTYPE(curl_getdate) = NULL; +DLSYM_PROTOTYPE(curl_multi_add_handle) = NULL; +DLSYM_PROTOTYPE(curl_multi_assign) = NULL; +DLSYM_PROTOTYPE(curl_multi_cleanup) = NULL; +DLSYM_PROTOTYPE(curl_multi_info_read) = NULL; +DLSYM_PROTOTYPE(curl_multi_init) = NULL; +DLSYM_PROTOTYPE(curl_multi_remove_handle) = NULL; +DLSYM_PROTOTYPE(curl_multi_setopt) = NULL; +DLSYM_PROTOTYPE(curl_multi_socket_action) = NULL; +DLSYM_PROTOTYPE(curl_slist_append) = NULL; +DLSYM_PROTOTYPE(curl_slist_free_all) = NULL; + static void curl_glue_check_finished(CurlGlue *g) { int r; @@ -25,7 +54,7 @@ static void curl_glue_check_finished(CurlGlue *g) { CURLMsg *msg; int k = 0; - msg = curl_multi_info_read(g->curl, &k); + msg = sym_curl_multi_info_read(g->curl, &k); if (!msg) return; @@ -51,7 +80,7 @@ static int curl_glue_on_io(sd_event_source *s, int fd, uint32_t revents, void *u else action = 0; - if (curl_multi_socket_action(g->curl, fd, action, &k) != CURLM_OK) + if (sym_curl_multi_socket_action(g->curl, fd, action, &k) != CURLM_OK) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to propagate IO event."); @@ -104,7 +133,7 @@ static int curl_glue_socket_callback(CURL *curl, curl_socket_t s, int action, vo if (sd_event_add_io(g->event, &io, s, events, curl_glue_on_io, g) < 0) return -1; - if (curl_multi_assign(g->curl, s, io) != CURLM_OK) + if (sym_curl_multi_assign(g->curl, s, io) != CURLM_OK) return -1; (void) sd_event_source_set_description(io, "curl-io"); @@ -126,7 +155,7 @@ static int curl_glue_on_timer(sd_event_source *s, uint64_t usec, void *userdata) assert(s); - if (curl_multi_socket_action(g->curl, CURL_SOCKET_TIMEOUT, 0, &k) != CURLM_OK) + if (sym_curl_multi_socket_action(g->curl, CURL_SOCKET_TIMEOUT, 0, &k) != CURLM_OK) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to propagate timeout."); @@ -188,7 +217,7 @@ CurlGlue *curl_glue_unref(CurlGlue *g) { return NULL; if (g->curl) - curl_multi_cleanup(g->curl); + sym_curl_multi_cleanup(g->curl); while ((io = hashmap_steal_first(g->ios))) sd_event_source_unref(io); @@ -207,6 +236,12 @@ int curl_glue_new(CurlGlue **glue, sd_event *event) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; int r; + assert(glue); + + r = dlopen_curl(LOG_DEBUG); + if (r < 0) + return r; + if (event) e = sd_event_ref(event); else { @@ -215,7 +250,7 @@ int curl_glue_new(CurlGlue **glue, sd_event *event) { return r; } - c = curl_multi_init(); + c = sym_curl_multi_init(); if (!c) return -ENOMEM; @@ -228,16 +263,16 @@ int curl_glue_new(CurlGlue **glue, sd_event *event) { .curl = TAKE_PTR(c), }; - if (curl_multi_setopt(g->curl, CURLMOPT_SOCKETDATA, g) != CURLM_OK) + if (sym_curl_multi_setopt(g->curl, CURLMOPT_SOCKETDATA, g) != CURLM_OK) return -EINVAL; - if (curl_multi_setopt(g->curl, CURLMOPT_SOCKETFUNCTION, curl_glue_socket_callback) != CURLM_OK) + if (sym_curl_multi_setopt(g->curl, CURLMOPT_SOCKETFUNCTION, curl_glue_socket_callback) != CURLM_OK) return -EINVAL; - if (curl_multi_setopt(g->curl, CURLMOPT_TIMERDATA, g) != CURLM_OK) + if (sym_curl_multi_setopt(g->curl, CURLMOPT_TIMERDATA, g) != CURLM_OK) return -EINVAL; - if (curl_multi_setopt(g->curl, CURLMOPT_TIMERFUNCTION, curl_glue_timer_callback) != CURLM_OK) + if (sym_curl_multi_setopt(g->curl, CURLMOPT_TIMERFUNCTION, curl_glue_timer_callback) != CURLM_OK) return -EINVAL; r = sd_event_add_defer(g->event, &g->defer, curl_glue_on_defer, g); @@ -254,43 +289,50 @@ int curl_glue_new(CurlGlue **glue, sd_event *event) { int curl_glue_make(CURL **ret, const char *url, void *userdata) { _cleanup_(curl_easy_cleanupp) CURL *c = NULL; const char *useragent; + int r; assert(ret); assert(url); - c = curl_easy_init(); + r = dlopen_curl(LOG_DEBUG); + if (r < 0) + return r; + + c = sym_curl_easy_init(); if (!c) return -ENOMEM; if (DEBUG_LOGGING) - (void) curl_easy_setopt(c, CURLOPT_VERBOSE, 1L); + (void) sym_curl_easy_setopt(c, CURLOPT_VERBOSE, 1L); - if (curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK) return -EIO; - if (curl_easy_setopt(c, CURLOPT_PRIVATE, userdata) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_PRIVATE, userdata) != CURLE_OK) return -EIO; useragent = strjoina(program_invocation_short_name, "/" GIT_VERSION); - if (curl_easy_setopt(c, CURLOPT_USERAGENT, useragent) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_USERAGENT, useragent) != CURLE_OK) return -EIO; - if (curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) return -EIO; - if (curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L) != CURLE_OK) return -EIO; - if (curl_easy_setopt(c, CURLOPT_LOW_SPEED_TIME, 60L) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_LOW_SPEED_TIME, 60L) != CURLE_OK) return -EIO; - if (curl_easy_setopt(c, CURLOPT_LOW_SPEED_LIMIT, 30L) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_LOW_SPEED_LIMIT, 30L) != CURLE_OK) return -EIO; #if LIBCURL_VERSION_NUM >= 0x075500 /* libcurl 7.85.0 */ - if (curl_easy_setopt(c, CURLOPT_PROTOCOLS_STR, "HTTP,HTTPS,FILE") != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_PROTOCOLS_STR, "HTTP,HTTPS,FILE") != CURLE_OK) #else - if (curl_easy_setopt(c, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS|CURLPROTO_FILE) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS|CURLPROTO_FILE) != CURLE_OK) + return -EIO; + if (sym_curl_easy_setopt(c, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS) != CURLE_OK) #endif return -EIO; @@ -302,7 +344,7 @@ int curl_glue_add(CurlGlue *g, CURL *c) { assert(g); assert(c); - if (curl_multi_add_handle(g->curl, c) != CURLM_OK) + if (sym_curl_multi_add_handle(g->curl, c) != CURLM_OK) return -EIO; return 0; @@ -315,9 +357,9 @@ void curl_glue_remove_and_free(CurlGlue *g, CURL *c) { return; if (g->curl) - curl_multi_remove_handle(g->curl, c); + sym_curl_multi_remove_handle(g->curl, c); - curl_easy_cleanup(c); + sym_curl_easy_cleanup(c); } struct curl_slist *curl_slist_new(const char *first, ...) { @@ -327,7 +369,7 @@ struct curl_slist *curl_slist_new(const char *first, ...) { if (!first) return NULL; - l = curl_slist_append(NULL, first); + l = sym_curl_slist_append(NULL, first); if (!l) return NULL; @@ -341,10 +383,10 @@ struct curl_slist *curl_slist_new(const char *first, ...) { if (!i) break; - n = curl_slist_append(l, i); + n = sym_curl_slist_append(l, i); if (!n) { va_end(ap); - curl_slist_free_all(l); + sym_curl_slist_free_all(l); return NULL; } @@ -359,6 +401,8 @@ int curl_header_strdup(const void *contents, size_t sz, const char *field, char const char *p; char *s; + assert(value); + p = memory_startswith_no_case(contents, sz, field); if (!p) return 0; @@ -390,7 +434,7 @@ int curl_parse_http_time(const char *t, usec_t *ret) { assert(t); assert(ret); - time_t v = curl_getdate(t, NULL); + time_t v = sym_curl_getdate(t, NULL); if (v == (time_t) -1) return -EINVAL; @@ -401,3 +445,59 @@ int curl_parse_http_time(const char *t, usec_t *ret) { return 0; } + +int curl_append_to_header(struct curl_slist **list, char **headers) { + /* This function leaves 'list' modified on partial failure. + * Input/output param list may point to NULL. */ + + assert(list); + + STRV_FOREACH(h, headers) { + struct curl_slist *l = sym_curl_slist_append(*list, *h); + if (!l) + return -ENOMEM; + *list = l; + } + + return 0; +} + +#endif + +int dlopen_curl(int log_level) { +#if HAVE_LIBCURL + SD_ELF_NOTE_DLOPEN( + "curl", + "Support for downloading and uploading files over HTTP", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libcurl.so.4"); + + return dlopen_many_sym_or_warn( + &curl_dl, + "libcurl.so.4", + log_level, + DLSYM_ARG(curl_easy_cleanup), + DLSYM_ARG(curl_easy_getinfo), + DLSYM_ARG(curl_easy_init), + DLSYM_ARG(curl_easy_perform), + DLSYM_ARG(curl_easy_setopt), + DLSYM_ARG(curl_easy_strerror), +#if LIBCURL_VERSION_NUM >= 0x075300 + DLSYM_ARG(curl_easy_header), +#endif + DLSYM_ARG(curl_getdate), + DLSYM_ARG(curl_multi_add_handle), + DLSYM_ARG(curl_multi_assign), + DLSYM_ARG(curl_multi_cleanup), + DLSYM_ARG(curl_multi_info_read), + DLSYM_ARG(curl_multi_init), + DLSYM_ARG(curl_multi_remove_handle), + DLSYM_ARG(curl_multi_setopt), + DLSYM_ARG(curl_multi_socket_action), + DLSYM_ARG(curl_slist_append), + DLSYM_ARG(curl_slist_free_all)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libcurl support is not compiled in."); +#endif +} diff --git a/src/shared/curl-util.h b/src/shared/curl-util.h new file mode 100644 index 0000000000000..112649f371ba7 --- /dev/null +++ b/src/shared/curl-util.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +#if HAVE_LIBCURL +#include /* IWYU pragma: export */ + +#include "dlfcn-util.h" + +extern DLSYM_PROTOTYPE(curl_easy_cleanup); +extern DLSYM_PROTOTYPE(curl_easy_getinfo); +extern DLSYM_PROTOTYPE(curl_easy_init); +extern DLSYM_PROTOTYPE(curl_easy_perform); +extern DLSYM_PROTOTYPE(curl_easy_setopt); +extern DLSYM_PROTOTYPE(curl_easy_strerror); +#if LIBCURL_VERSION_NUM >= 0x075300 +extern DLSYM_PROTOTYPE(curl_easy_header); +#endif +extern DLSYM_PROTOTYPE(curl_getdate); +extern DLSYM_PROTOTYPE(curl_multi_add_handle); +extern DLSYM_PROTOTYPE(curl_multi_assign); +extern DLSYM_PROTOTYPE(curl_multi_cleanup); +extern DLSYM_PROTOTYPE(curl_multi_info_read); +extern DLSYM_PROTOTYPE(curl_multi_init); +extern DLSYM_PROTOTYPE(curl_multi_remove_handle); +extern DLSYM_PROTOTYPE(curl_multi_setopt); +extern DLSYM_PROTOTYPE(curl_multi_socket_action); +extern DLSYM_PROTOTYPE(curl_slist_append); +extern DLSYM_PROTOTYPE(curl_slist_free_all); + +#define easy_setopt(curl, log_level, opt, value) ({ \ + CURLcode code = sym_curl_easy_setopt(ASSERT_PTR(curl), opt, value); \ + if (code) \ + log_full(log_level, \ + "curl_easy_setopt %s failed: %s", \ + #opt, sym_curl_easy_strerror(code)); \ + code == CURLE_OK; \ +}) + +typedef struct CurlGlue CurlGlue; + +typedef struct CurlGlue { + sd_event *event; + CURLM *curl; + sd_event_source *timer; + Hashmap *ios; + sd_event_source *defer; + + void (*on_finished)(CurlGlue *g, CURL *curl, CURLcode code); + void *userdata; +} CurlGlue; + +int curl_glue_new(CurlGlue **glue, sd_event *event); +CurlGlue* curl_glue_unref(CurlGlue *glue); + +DEFINE_TRIVIAL_CLEANUP_FUNC(CurlGlue*, curl_glue_unref); + +int curl_glue_make(CURL **ret, const char *url, void *userdata); +int curl_glue_add(CurlGlue *g, CURL *c); +void curl_glue_remove_and_free(CurlGlue *g, CURL *c); + +struct curl_slist *curl_slist_new(const char *first, ...) _sentinel_; +int curl_header_strdup(const void *contents, size_t sz, const char *field, char **value); +int curl_parse_http_time(const char *t, usec_t *ret); +int curl_append_to_header(struct curl_slist **list, char **headers); + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(CURL*, sym_curl_easy_cleanup, curl_easy_cleanupp, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(CURLM*, sym_curl_multi_cleanup, curl_multi_cleanupp, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct curl_slist*, sym_curl_slist_free_all, curl_slist_free_allp, NULL); + +#endif + +int dlopen_curl(int log_level); diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 4c0752c979128..0410b523baafd 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -142,7 +142,9 @@ static const char auxiliary_suffixes_nulstr[] = ".roothash.p7s\0" ".usrhash\0" ".usrhash.p7s\0" - ".verity\0"; + ".verity\0" + ".raw.tpmstate\0" + ".raw.efinvramstate\0"; DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(image_dirname, ImageClass); @@ -889,7 +891,7 @@ int image_find(RuntimeScope scope, _cleanup_closedir_ DIR *d = NULL; _cleanup_free_ char *search_path = NULL; - r = chase_and_opendirat(rfd, *s, CHASE_AT_RESOLVE_IN_ROOT, &search_path, &d); + r = chase_and_opendirat(rfd, rfd, *s, /* chase_flags= */ 0, &search_path, &d); if (r == -ENOENT) continue; if (r < 0) @@ -905,7 +907,7 @@ int image_find(RuntimeScope scope, return -ENOMEM; /* Follow symlinks only inside given root */ - r = chaseat(rfd, fname_path, CHASE_AT_RESOLVE_IN_ROOT, &chased_path, &fd); + r = chaseat(rfd, rfd, fname_path, /* flags= */ 0, &chased_path, &fd); if (r == -ENOENT) continue; if (r < 0) @@ -1095,7 +1097,7 @@ int image_discover( _cleanup_closedir_ DIR *d = NULL; _cleanup_free_ char *search_path = NULL; - r = chase_and_opendirat(rfd, *s, CHASE_AT_RESOLVE_IN_ROOT, &search_path, &d); + r = chase_and_opendirat(rfd, rfd, *s, /* chase_flags= */ 0, &search_path, &d); if (r == -ENOENT) continue; if (r < 0) @@ -1110,12 +1112,16 @@ int image_discover( if (dot_or_dot_dot(fname)) continue; + /* Ignore sysupdate temporary files */ + if (startswith(fname, ".sysupdate.")) + continue; + fname_path = path_join(search_path, fname); if (!fname_path) return -ENOMEM; /* Follow symlinks only inside given root */ - r = chaseat(rfd, fname_path, CHASE_AT_RESOLVE_IN_ROOT, &chased_path, &fd); + r = chaseat(rfd, rfd, fname_path, /* flags= */ 0, &chased_path, &fd); if (r == -ENOENT) continue; if (r < 0) @@ -1889,17 +1895,15 @@ int image_read_only(Image *i, bool b, RuntimeScope scope) { case IMAGE_BLOCK: { _cleanup_close_ int fd = -EBADF; - struct stat st; int state = b; fd = open(i->path, O_CLOEXEC|O_RDONLY|O_NONBLOCK|O_NOCTTY); if (fd < 0) return -errno; - if (fstat(fd, &st) < 0) - return -errno; - if (!S_ISBLK(st.st_mode)) - return -ENOTTY; + r = fd_verify_block(fd); + if (r < 0) + return r; if (ioctl(fd, BLKROSET, &state) < 0) return -errno; @@ -2187,10 +2191,8 @@ int image_setup_pool(RuntimeScope scope, ImageClass class, bool use_btrfs_subvol return r; r = check_btrfs(pool); - if (r < 0) + if (r <= 0) return r; - if (r == 0) - return 0; if (!use_btrfs_subvol) return 0; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 9b9c3af2529f5..662739ea0fe9c 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -6,12 +6,6 @@ #include #include -#if HAVE_OPENSSL -#include -#include -#include -#endif - #include "sd-device.h" #include "sd-id128.h" #include "sd-json.h" @@ -26,6 +20,7 @@ #include "conf-files.h" #include "constants.h" #include "copy.h" +#include "crypto-util.h" #include "cryptsetup-util.h" #include "device-private.h" #include "devnum-util.h" @@ -57,7 +52,6 @@ #include "mountpoint-util.h" #include "namespace-util.h" #include "nulstr-util.h" -#include "openssl-util.h" #include "os-util.h" #include "path-util.h" #include "pcrextend-util.h" @@ -69,6 +63,7 @@ #include "runtime-scope.h" #include "siphash24.h" #include "stat-util.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "time-util.h" @@ -138,54 +133,24 @@ static const char *getenv_fstype(PartitionDesignator d) { int probe_sector_size(int fd, uint32_t *ret) { - /* Disk images might be for 512B or for 4096 sector sizes, let's try to auto-detect that by searching - * for the GPT headers at the relevant byte offsets */ - - assert_cc(sizeof(GptHeader) == 92); - - /* We expect a sector size in the range 512…4096. The GPT header is located in the second - * sector. Hence it could be at byte 512 at the earliest, and at byte 4096 at the latest. And we must - * read with granularity of the largest sector size we care about. Which means 8K. */ - uint8_t sectors[2 * 4096]; - uint32_t found = 0; - ssize_t n; - assert(fd >= 0); assert(ret); - n = pread(fd, sectors, sizeof(sectors), 0); - if (n < 0) - return -errno; - if (n != sizeof(sectors)) /* too short? */ - goto not_found; - - /* Let's see if we find the GPT partition header with various expected sector sizes */ - for (uint32_t sz = 512; sz <= 4096; sz <<= 1) { - const GptHeader *p; - - assert(sizeof(sectors) >= sz * 2); - p = (const GptHeader*) (sectors + sz); - - if (!gpt_header_has_signature(p)) - continue; - - if (found != 0) - return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), - "Detected valid partition table at offsets matching multiple sector sizes, refusing."); - - found = sz; - } - - if (found != 0) { - log_debug("Determined sector size %" PRIu32 " based on discovered partition table.", found); - *ret = found; - return 1; /* indicate we *did* find it */ + ssize_t ssz = gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL); + if (ssz == -ENOTUNIQ) + return log_debug_errno(ssz, + "Detected valid partition table at offsets matching multiple sector sizes, refusing."); + if (ssz < 0) + return ssz; + if (ssz == 0) { + log_debug("Couldn't find any partition table to derive sector size of."); + *ret = 512; /* pick the traditional default */ + return 0; /* indicate we didn't find it */ } -not_found: - log_debug("Couldn't find any partition table to derive sector size of."); - *ret = 512; /* pick the traditional default */ - return 0; /* indicate we didn't find it */ + log_debug("Determined sector size %" PRIu32 " based on discovered partition table.", (uint32_t) ssz); + *ret = ssz; + return 1; /* indicate we *did* find it */ } int probe_sector_size_prefer_ioctl(int fd, uint32_t *ret) { @@ -224,15 +189,23 @@ static int probe_blkid_filter(blkid_probe p) { if (r < 0) return r; + /* allowed_fstypes() returns the list of filesystem types that we are willing to mount. For the + * blkid probe filter we additionally need to be able to detect crypto_LUKS (so that we can set up + * LUKS decryption for encrypted partitions) and swap (so that we can identify swap partitions). */ + r = strv_extend_many(&fstypes, "crypto_LUKS", "swap"); + if (r < 0) + return r; + errno = 0; r = sym_blkid_probe_filter_superblocks_type(p, BLKID_FLTR_ONLYIN, fstypes); if (r != 0) return errno_or_else(EINVAL); - errno = 0; - r = sym_blkid_probe_filter_superblocks_usage(p, BLKID_FLTR_NOTIN, BLKID_USAGE_RAID); - if (r != 0) - return errno_or_else(EINVAL); + /* Note: don't call blkid_probe_filter_superblocks_usage() here. Both filter functions share the + * same bitmap internally, and each call resets it before applying its own filter — so a subsequent + * usage filter would wipe the type filter we just set. The ONLYIN type filter above already + * excludes everything not in the allowed list, including RAID superblocks, so a separate usage + * filter is redundant anyway. */ return 0; } @@ -260,7 +233,7 @@ int probe_filesystem_full( assert(fd >= 0 || path); assert(ret_fstype); - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) return r; @@ -963,6 +936,7 @@ static int dissect_image_from_unpartitioned( assert(devname); assert(m); assert(fstype); + POINTER_MAY_BE_NULL(mount_node_fd); if (!image_filter_test(filter, PARTITION_ROOT, /* label= */ NULL)) /* do a filter check with an empty partition label */ return -ECOMM; @@ -1096,7 +1070,7 @@ static int dissect_image( } } - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) return r; @@ -1149,10 +1123,7 @@ static int dissect_image( /* If flags permit this, also allow using non-partitioned single-filesystem images */ - if (root_fstype_string) - usage = encrypted ? "crypto" : "filesystem"; - else - (void) sym_blkid_probe_lookup_value(b, "USAGE", &usage, NULL); + (void) sym_blkid_probe_lookup_value(b, "USAGE", &usage, NULL); if (STRPTR_IN_SET(usage, "filesystem", "crypto")) { _cleanup_free_ char *t = NULL; const char *fstype = NULL; @@ -1397,41 +1368,43 @@ static int dissect_image( if (!fstype) fstype = "vfat"; - } else if (type.designator == PARTITION_ROOT) { + } else if (IN_SET(type.designator, PARTITION_ROOT, PARTITION_USR)) { + sd_id128_t expected_uuid = type.designator == PARTITION_ROOT ? root_uuid : usr_uuid; check_partition_flags(node, pflags, SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY | SD_GPT_FLAG_GROWFS); - /* If a root ID is specified, ignore everything but the root id */ - if (!sd_id128_is_null(root_uuid) && !sd_id128_equal(root_uuid, id)) { - log_debug("Partition UUID '%s' does not match expected UUID '%s' derived from root verity hash, ignoring.", + if (!sd_id128_is_null(expected_uuid) && !sd_id128_equal(expected_uuid, id)) { + log_debug("Partition UUID '%s' does not match expected UUID '%s' derived from %s verity hash, ignoring.", SD_ID128_TO_UUID_STRING(id), - SD_ID128_TO_UUID_STRING(root_uuid)); + SD_ID128_TO_UUID_STRING(expected_uuid), + partition_designator_to_string(type.designator)); continue; } rw = !(pflags & SD_GPT_FLAG_READ_ONLY); growfs = FLAGS_SET(pflags, SD_GPT_FLAG_GROWFS); - } else if (type.designator == PARTITION_ROOT_VERITY) { + } else if (IN_SET(type.designator, PARTITION_ROOT_VERITY, PARTITION_USR_VERITY)) { + sd_id128_t expected_uuid = type.designator == PARTITION_ROOT_VERITY ? root_verity_uuid : usr_verity_uuid; check_partition_flags(node, pflags, SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY); m->has_verity = true; - /* If root hash is specified, then ignore everything but the root id */ - if (!sd_id128_is_null(root_verity_uuid) && !sd_id128_equal(root_verity_uuid, id)) { - log_debug("Partition UUID '%s' does not match expected UUID '%s' derived from root verity hash, ignoring.", + if (!sd_id128_is_null(expected_uuid) && !sd_id128_equal(expected_uuid, id)) { + log_debug("Partition UUID '%s' does not match expected UUID '%s' derived from %s verity hash, ignoring.", SD_ID128_TO_UUID_STRING(id), - SD_ID128_TO_UUID_STRING(root_verity_uuid)); + SD_ID128_TO_UUID_STRING(expected_uuid), + partition_designator_to_string(partition_verity_to_data(type.designator))); continue; } fstype = "DM_verity_hash"; rw = false; - } else if (type.designator == PARTITION_ROOT_VERITY_SIG) { + } else if (IN_SET(type.designator, PARTITION_ROOT_VERITY_SIG, PARTITION_USR_VERITY_SIG)) { if (verity && iovec_is_set(&verity->root_hash)) { _cleanup_(iovec_done) struct iovec root_hash = {}; @@ -1443,80 +1416,14 @@ static int dissect_image( /* ret_root_hash_sig= */ NULL); if (r < 0) return r; - if (iovec_memcmp(&verity->root_hash, &root_hash) != 0) { + if (!iovec_equal(&verity->root_hash, &root_hash)) { if (DEBUG_LOGGING) { _cleanup_free_ char *found = NULL, *expected = NULL; found = hexmem(root_hash.iov_base, root_hash.iov_len); expected = hexmem(verity->root_hash.iov_base, verity->root_hash.iov_len); - log_debug("Root hash in signature JSON data (%s) doesn't match configured hash (%s).", strna(found), strna(expected)); - } - continue; - } - } - - check_partition_flags(node, pflags, - SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY); - - m->has_verity_sig = true; - fstype = "verity_hash_signature"; - rw = false; - - } else if (type.designator == PARTITION_USR) { - - check_partition_flags(node, pflags, - SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY | SD_GPT_FLAG_GROWFS); - - /* If a usr ID is specified, ignore everything but the usr id */ - if (!sd_id128_is_null(usr_uuid) && !sd_id128_equal(usr_uuid, id)) { - log_debug("Partition UUID '%s' does not match expected UUID '%s' derived from usr verity hash, ignoring.", - SD_ID128_TO_UUID_STRING(id), - SD_ID128_TO_UUID_STRING(usr_uuid)); - continue; - } - - rw = !(pflags & SD_GPT_FLAG_READ_ONLY); - growfs = FLAGS_SET(pflags, SD_GPT_FLAG_GROWFS); - - } else if (type.designator == PARTITION_USR_VERITY) { - - check_partition_flags(node, pflags, - SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY); - - m->has_verity = true; - - /* If usr hash is specified, then ignore everything but the usr id */ - if (!sd_id128_is_null(usr_verity_uuid) && !sd_id128_equal(usr_verity_uuid, id)) { - log_debug("Partition UUID '%s' does not match expected UUID '%s' derived from usr verity hash, ignoring.", - SD_ID128_TO_UUID_STRING(id), - SD_ID128_TO_UUID_STRING(usr_uuid)); - continue; - } - - fstype = "DM_verity_hash"; - rw = false; - - } else if (type.designator == PARTITION_USR_VERITY_SIG) { - if (verity && iovec_is_set(&verity->root_hash)) { - _cleanup_(iovec_done) struct iovec root_hash = {}; - - r = acquire_sig_for_roothash( - fd, - start * 512, - size * 512, - &root_hash, - /* ret_root_hash_sig= */ NULL); - if (r < 0) - return r; - if (iovec_memcmp(&verity->root_hash, &root_hash) != 0) { - if (DEBUG_LOGGING) { - _cleanup_free_ char *found = NULL, *expected = NULL; - - found = hexmem(root_hash.iov_base, root_hash.iov_len); - expected = hexmem(verity->root_hash.iov_base, verity->root_hash.iov_len); - - log_debug("Root hash in signature JSON data (%s) doesn't match configured hash (%s).", strna(found), strna(expected)); + log_debug("Verity root hash in signature JSON data (%s) doesn't match configured hash (%s).", strna(found), strna(expected)); } continue; } @@ -1764,31 +1671,29 @@ static int dissect_image( } } - /* Verity found but no matching rootfs? Something is off, refuse. */ - if (!m->partitions[PARTITION_ROOT].found && - (m->partitions[PARTITION_ROOT_VERITY].found || - m->partitions[PARTITION_ROOT_VERITY_SIG].found)) - return log_debug_errno( - SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Found root verity hash partition without matching root data partition."); + /* Verity found but no matching data partition? Something is off, refuse. */ + FOREACH_ELEMENT(dd, ((const PartitionDesignator[]) { PARTITION_ROOT, PARTITION_USR })) { + PartitionDesignator dv = partition_verity_hash_of(*dd); + PartitionDesignator ds = partition_verity_sig_of(*dd); - /* Hmm, we found a signature partition but no Verity data? Something is off. */ - if (m->partitions[PARTITION_ROOT_VERITY_SIG].found && !m->partitions[PARTITION_ROOT_VERITY].found) - return log_debug_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Found root verity signature partition without matching root verity hash partition."); + /* Hint to help static analyzers */ + assert(dv >= 0); + assert(ds >= 0); - /* as above */ - if (!m->partitions[PARTITION_USR].found && - (m->partitions[PARTITION_USR_VERITY].found || - m->partitions[PARTITION_USR_VERITY_SIG].found)) + if (!m->partitions[*dd].found && (m->partitions[dv].found || m->partitions[ds].found)) return log_debug_errno( SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Found usr verity hash partition without matching usr data partition."); + "Found %s verity hash partition without matching %s data partition.", + partition_designator_to_string(*dd), + partition_designator_to_string(*dd)); - /* as above */ - if (m->partitions[PARTITION_USR_VERITY_SIG].found && !m->partitions[PARTITION_USR_VERITY].found) - return log_debug_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Found usr verity signature partition without matching usr verity hash partition."); + if (m->partitions[ds].found && !m->partitions[dv].found) + return log_debug_errno( + SYNTHETIC_ERRNO(EADDRNOTAVAIL), + "Found %s verity signature partition without matching %s verity hash partition.", + partition_designator_to_string(*dd), + partition_designator_to_string(*dd)); + } /* If root and /usr are combined then insist that the architecture matches */ if (m->partitions[PARTITION_ROOT].found && @@ -1893,35 +1798,27 @@ static int dissect_image( /* If we have an explicit root hash and found the partitions for it, then we are ready to use * Verity, set things up for it */ - if (verity->designator < 0 || verity->designator == PARTITION_ROOT) { - if (!m->partitions[PARTITION_ROOT].found) - return log_debug_errno( - SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Verity enabled root partition was requested but did not find a root data partition."); - - if (!m->partitions[PARTITION_ROOT_VERITY].found) - return log_debug_errno( - SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Verity enabled root partition was requested but did not find a root verity hash partition."); - - /* If we found a verity setup, then the root partition is necessarily read-only. */ - m->partitions[PARTITION_ROOT].rw = false; - } else { - assert(verity->designator == PARTITION_USR); + PartitionDesignator d = verity->designator < 0 || verity->designator == PARTITION_ROOT + ? PARTITION_ROOT : PARTITION_USR; + PartitionDesignator dv = partition_verity_hash_of(d); + assert(dv >= 0); - if (!m->partitions[PARTITION_USR].found) - return log_debug_errno( - SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Verity enabled usr partition was requested but did not find a usr data partition."); + if (!m->partitions[d].found) + return log_debug_errno( + SYNTHETIC_ERRNO(EADDRNOTAVAIL), + "Verity enabled %s partition was requested but did not find a %s data partition.", + partition_designator_to_string(d), + partition_designator_to_string(d)); - if (!m->partitions[PARTITION_USR_VERITY].found) - return log_debug_errno( - SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Verity enabled usr partition was requested but did not find a usr verity hash partition."); + if (!m->partitions[dv].found) + return log_debug_errno( + SYNTHETIC_ERRNO(EADDRNOTAVAIL), + "Verity enabled %s partition was requested but did not find a %s verity hash partition.", + partition_designator_to_string(d), + partition_designator_to_string(d)); - - m->partitions[PARTITION_USR].rw = false; - } + /* If we found a verity setup, then the data partition is necessarily read-only. */ + m->partitions[d].rw = false; m->verity_ready = true; @@ -2239,14 +2136,16 @@ DissectedImage* dissected_image_unref(DissectedImage *m) { static int is_loop_device(const char *path) { char s[SYS_BLOCK_PATH_MAX("/../loop/")]; struct stat st; + int r; assert(path); if (stat(path, &st) < 0) return -errno; - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; + r = stat_verify_block(&st); + if (r < 0) + return r; xsprintf_sys_block_path(s, "/loop/", st.st_dev); if (access(s, F_OK) < 0) { @@ -2954,30 +2853,35 @@ static int decrypted_image_new(DecryptedImage **ret) { return 0; } -static int make_dm_name_and_node(const void *original_node, const char *suffix, char **ret_name, char **ret_node) { - _cleanup_free_ char *name = NULL, *node = NULL; - const char *base; +static uint64_t dissected_image_diskseq(const DissectedImage *di) { + assert(di); - assert(original_node); + return di->loop ? di->loop->diskseq : 0; +} + +static int make_dm_name_and_node( + const char *base, + uint64_t diskseq, + const char *suffix, + char **ret_name, + char **ret_node) { + + assert(base); assert(suffix); assert(ret_name); assert(ret_node); - base = strrchr(original_node, '/'); - if (!base) - base = original_node; + _cleanup_free_ char *name = NULL; + if (diskseq != 0) + name = asprintf_safe("%s-%" PRIu64 "%s", base, diskseq, suffix); else - base++; - if (isempty(base)) - return -EINVAL; - - name = strjoin(base, suffix); + name = strjoin(base, suffix); if (!name) return -ENOMEM; if (!filename_is_valid(name)) return -EINVAL; - node = path_join(sym_crypt_get_dir(), name); + _cleanup_free_ char *node = path_join(sym_crypt_get_dir(), name); if (!node) return -ENOMEM; @@ -2987,7 +2891,27 @@ static int make_dm_name_and_node(const void *original_node, const char *suffix, return 0; } +static int make_dm_name_and_node_from_node( + const char *original_node, + uint64_t diskseq, + const char *suffix, + char **ret_name, + char **ret_node) { + + int r; + + assert(original_node); + + _cleanup_free_ char *base = NULL; + r = path_extract_filename(original_node, &base); + if (r < 0) + return r; + + return make_dm_name_and_node(base, diskseq, suffix, ret_name, ret_node); +} + static int decrypt_partition( + DissectedImage *di, DissectedPartition *m, const char *passphrase, DissectImageFlags flags, @@ -2995,10 +2919,11 @@ static int decrypt_partition( DecryptedImage *d) { _cleanup_free_ char *node = NULL, *name = NULL; - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_close_ int fd = -EBADF; int r; + assert(di); assert(m); assert(d); @@ -3014,11 +2939,11 @@ static int decrypt_partition( if (!FLAGS_SET(policy_flags, PARTITION_POLICY_ENCRYPTED)) return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Attempted to unlock partition via LUKS, but it's prohibited."); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; - r = make_dm_name_and_node(m->node, "-decrypted", &name, &node); + r = make_dm_name_and_node_from_node(m->node, dissected_image_diskseq(di), "-decrypted", &name, &node); if (r < 0) return r; @@ -3064,7 +2989,7 @@ static int verity_can_reuse( struct crypt_device **ret_cd) { /* If the same volume was already open, check that the root hashes match, and reuse it if they do */ - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; struct crypt_params_verity crypt_params = {}; int r; @@ -3092,7 +3017,7 @@ static int verity_can_reuse( r = sym_crypt_volume_key_get(cd, CRYPT_ANY_SLOT, root_hash_existing.iov_base, &root_hash_existing.iov_len, NULL, 0); if (r < 0) return log_debug_errno(r, "Error opening verity device, crypt_volume_key_get failed: %m"); - if (iovec_memcmp(&verity->root_hash, &root_hash_existing) != 0) + if (!iovec_equal(&verity->root_hash, &root_hash_existing)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Error opening verity device, it already exists but root hashes are different."); /* Ensure that, if signatures are supported, we only reuse the device if the previous mount used the @@ -3141,7 +3066,7 @@ static int validate_signature_userspace(const VeritySettings *verity, const char return 0; } if (!b) { - log_debug("Userspace dm-verity signature authentication disabled via systemd.allow_userspace_verity= kernel command line variable."); + log_debug("Userspace dm-verity signature authentication disabled via systemd.allow_userspace_verity= kernel command line option."); return 0; } @@ -3156,6 +3081,10 @@ static int validate_signature_userspace(const VeritySettings *verity, const char assert(iovec_is_set(&verity->root_hash)); assert(iovec_is_set(&verity->root_hash_sig)); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + /* Because installing a signature certificate into the kernel chain is so messy, let's optionally do * userspace validation. */ @@ -3168,7 +3097,7 @@ static int validate_signature_userspace(const VeritySettings *verity, const char } const unsigned char *d = verity->root_hash_sig.iov_base; - p7 = d2i_PKCS7(NULL, &d, (long) verity->root_hash_sig.iov_len); + p7 = sym_d2i_PKCS7(NULL, &d, (long) verity->root_hash_sig.iov_len); if (!p7) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse PKCS7 DER signature data."); @@ -3176,11 +3105,11 @@ static int validate_signature_userspace(const VeritySettings *verity, const char if (!s) return log_oom_debug(); - bio = BIO_new_mem_buf(s, strlen(s)); + bio = sym_BIO_new_mem_buf(s, strlen(s)); if (!bio) return log_oom_debug(); - sk = sk_X509_new_null(); + sk = sym_sk_X509_new_null(); if (!sk) return log_oom_debug(); @@ -3194,23 +3123,23 @@ static int validate_signature_userspace(const VeritySettings *verity, const char continue; } - c = PEM_read_X509(f, NULL, NULL, NULL); + c = sym_PEM_read_X509(f, NULL, NULL, NULL); if (!c) { log_debug("Failed to load X509 certificate '%s', ignoring.", *i); continue; } - if (sk_X509_push(sk, c) == 0) + if (sym_sk_X509_push(sk, c) == 0) return log_oom_debug(); TAKE_PTR(c); } - r = PKCS7_verify(p7, sk, NULL, bio, NULL, PKCS7_NOINTERN|PKCS7_NOVERIFY); + r = sym_PKCS7_verify(p7, sk, NULL, bio, NULL, PKCS7_NOINTERN|PKCS7_NOVERIFY); if (r) log_debug("Userspace PKCS#7 validation succeeded."); else - log_debug("Userspace PKCS#7 validation failed: %s", ERR_error_string(ERR_get_error(), NULL)); + log_debug("Userspace PKCS#7 validation failed: %s", sym_ERR_error_string(sym_ERR_get_error(), NULL)); return r; #else @@ -3342,6 +3271,7 @@ static usec_t verity_timeout(void) { } static int verity_partition( + DissectedImage *di, PartitionDesignator designator, DissectedPartition *m, /* data partition */ DissectedPartition *v, /* verity partition */ @@ -3351,11 +3281,12 @@ static int verity_partition( PartitionPolicyFlags policy_flags, DecryptedImage *d) { - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_free_ char *node = NULL, *name = NULL; _cleanup_close_ int mount_node_fd = -EBADF; int r; + assert(di); assert(m); assert(v || (verity && verity->data_path)); @@ -3380,7 +3311,7 @@ static int verity_partition( return 0; } - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -3392,9 +3323,9 @@ static int verity_partition( if (!root_hash_encoded) return -ENOMEM; - r = make_dm_name_and_node(root_hash_encoded, "-verity", &name, &node); + r = make_dm_name_and_node(root_hash_encoded, /* diskseq= */ 0, "-verity", &name, &node); } else - r = make_dm_name_and_node(m->node, "-verity", &name, &node); + r = make_dm_name_and_node_from_node(m->node, dissected_image_diskseq(di), "-verity", &name, &node); if (r < 0) return r; @@ -3420,7 +3351,7 @@ static int verity_partition( * retry a few times before giving up. */ for (unsigned i = 0; i < N_DEVICE_NODE_LIST_ATTEMPTS; i++) { _cleanup_(dm_deferred_remove_cleanp) char *restore_deferred_remove = NULL; - _cleanup_(sym_crypt_freep) struct crypt_device *existing_cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *existing_cd = NULL; _cleanup_close_ int fd = -EBADF; /* First, check if the device already exists. */ @@ -3525,7 +3456,16 @@ static int verity_partition( */ sym_crypt_free(cd); cd = NULL; - return verity_partition(designator, m, v, root, verity, flags & ~DISSECT_IMAGE_VERITY_SHARE, policy_flags, d); + return verity_partition( + di, + designator, + m, + v, + root, + verity, + flags & ~DISSECT_IMAGE_VERITY_SHARE, + policy_flags, + d); } return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "All attempts to activate verity device %s failed.", name); @@ -3596,13 +3536,13 @@ int dissected_image_decrypt( PartitionPolicyFlags fl = image_policy_get_exhaustively(policy, i); - r = decrypt_partition(p, passphrase, flags, fl, d); + r = decrypt_partition(m, p, passphrase, flags, fl, d); if (r < 0) return r; k = partition_verity_hash_of(i); if (k >= 0) { - r = verity_partition(i, p, m->partitions + k, root, verity, flags, fl, d); + r = verity_partition(m, i, p, m->partitions + k, root, verity, flags, fl, d); if (r < 0) return r; } @@ -3786,7 +3726,7 @@ static char *build_auxiliary_path(const char *image, const char *suffix) { e = endswith(image, ".raw"); if (!e) - return strjoin(e, suffix); + return strjoin(image, suffix); n = new(char, e - image + strlen(suffix) + 1); if (!n) @@ -4106,7 +4046,7 @@ int dissected_image_load_verity_sig_partition( /* Check if specified root hash matches if it is specified */ if (iovec_is_set(&verity->root_hash) && - iovec_memcmp(&verity->root_hash, &root_hash) != 0) { + !iovec_equal(&verity->root_hash, &root_hash)) { _cleanup_free_ char *a = NULL, *b = NULL; a = hexmem(root_hash.iov_base, root_hash.iov_len); @@ -4234,7 +4174,7 @@ int dissected_image_acquire_metadata( assert(m); - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0) return r; @@ -4757,7 +4697,7 @@ int mount_image_privately_interactively( r = loop_device_make_by_path( image, - FLAGS_SET(flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR, + FLAGS_SET(flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : -1, /* sector_size= */ UINT32_MAX, FLAGS_SET(flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN, LOCK_SH, diff --git a/src/shared/dns-answer.c b/src/shared/dns-answer.c index bfc6aef6f55a5..24174d2bd2c07 100644 --- a/src/shared/dns-answer.c +++ b/src/shared/dns-answer.c @@ -574,6 +574,8 @@ int dns_answer_remove_by_answer_keys(DnsAnswer **a, DnsAnswer *b) { DnsAnswerItem *item; int r; + assert(a); + /* Removes all items from '*a' that have a matching key in 'b' */ DNS_ANSWER_FOREACH_ITEM(item, b) { diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 727e1d0625ba3..c90da5fc465f2 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -1267,7 +1267,7 @@ int dns_name_apply_idna(const char *name, char **ret) { #if HAVE_LIBIDN2 int r; - r = dlopen_idn(); + r = dlopen_idn(LOG_DEBUG); if (r == -EOPNOTSUPP) { *ret = NULL; return 0; diff --git a/src/shared/dns-packet.c b/src/shared/dns-packet.c index 04178e5df2b5c..c8c54e7988afc 100644 --- a/src/shared/dns-packet.c +++ b/src/shared/dns-packet.c @@ -120,6 +120,8 @@ int dns_packet_new( a = min_alloc_dsize; /* round up to next page size */ + /* Silence static analyzers */ + assert(a <= SIZE_MAX - ALIGN(sizeof(DnsPacket))); a = PAGE_ALIGN(ALIGN(sizeof(DnsPacket)) + a) - ALIGN(sizeof(DnsPacket)); /* make sure we never allocate more than useful */ @@ -226,6 +228,8 @@ int dns_packet_dup(DnsPacket **ret, DnsPacket *p) { if (r < 0) return r; + /* Silence static analyzers */ + assert(p->size <= SIZE_MAX - ALIGN(sizeof(DnsPacket))); c = malloc(ALIGN(sizeof(DnsPacket)) + p->size); if (!c) return -ENOMEM; @@ -284,11 +288,10 @@ DnsPacket *dns_packet_unref(DnsPacket *p) { assert(p->n_ref > 0); - dns_packet_unref(p->more); - - if (p->n_ref == 1) + if (p->n_ref == 1) { + dns_packet_unref(p->more); dns_packet_free(p); - else + } else p->n_ref--; return NULL; @@ -1470,7 +1473,9 @@ int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start) { if (r < 0) return r; - *ret = ((uint8_t*) d)[0]; + if (ret) + *ret = ((uint8_t*) d)[0]; + return 0; } @@ -1500,7 +1505,8 @@ int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start) { if (r < 0) return r; - *ret = unaligned_read_be32(d); + if (ret) + *ret = unaligned_read_be32(d); return 0; } @@ -1513,6 +1519,7 @@ int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start) { int r; assert(p); + assert(ret); r = dns_packet_read_uint8(p, &c, NULL); if (r < 0) @@ -2401,6 +2408,7 @@ static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) { * a reply). */ assert(rr); + assert(rfc6975); assert(rr->key->type == DNS_TYPE_OPT); /* Check that the version is 0 */ @@ -2441,6 +2449,8 @@ static int dns_packet_extract_question(DnsPacket *p, DnsQuestion **ret_question) unsigned n; int r; + assert(ret_question); + n = DNS_PACKET_QDCOUNT(p); if (n > 0) { question = dns_question_new(n); @@ -2494,6 +2504,8 @@ static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) { bool bad_opt = false; int r; + assert(ret_answer); + n = DNS_PACKET_RRCOUNT(p); if (n == 0) return 0; @@ -2849,7 +2861,7 @@ int dns_packet_ede_rcode(DnsPacket *p, int *ret_ede_rcode, char **ret_ede_msg) { return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "EDNS0 truncated EDE info code."); - r = make_cstring((char *) d + 6, length - 2U, MAKE_CSTRING_ALLOW_TRAILING_NUL, &msg); + r = make_cstring(d + 6, length - 2U, MAKE_CSTRING_ALLOW_TRAILING_NUL, &msg); if (r < 0) return log_debug_errno(r, "Invalid EDE text in opt."); diff --git a/src/shared/dns-rr.c b/src/shared/dns-rr.c index 0fa730c13baa2..0ad01de88a684 100644 --- a/src/shared/dns-rr.c +++ b/src/shared/dns-rr.c @@ -2012,6 +2012,7 @@ int dns_resource_record_get_cname_target(DnsResourceKey *key, DnsResourceRecord assert(key); assert(cname); + assert(ret); /* Checks if the RR `cname` is a CNAME/DNAME RR that matches the specified `key`. If so, returns the * target domain. If not, returns -EUNATCH */ @@ -2215,6 +2216,12 @@ int dns_resource_key_from_json(sd_json_variant *v, DnsResourceKey **ret) { if (r < 0) return r; + r = dns_name_is_valid(p.name); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + key = dns_resource_key_new(p.class, p.type, p.name); if (!key) return -ENOMEM; @@ -2302,7 +2309,6 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, sd_json_variant **ret) { int r; assert(rr); - assert(ret); r = dns_resource_key_to_json(rr->key, &k); if (r < 0) @@ -2508,11 +2514,103 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, sd_json_variant **ret) { default: /* Can't provide broken-down format */ - *ret = NULL; + if (ret) + *ret = NULL; return 0; } } +int dns_resource_record_from_json(sd_json_variant *v, DnsResourceRecord **ret) { + int r; + + assert(v); + + sd_json_variant *k = sd_json_variant_by_key(v, "key"); + if (!k) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Resource record entry lacks key field, refusing."); + + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + r = dns_resource_key_from_json(k, &key); + if (r < 0) + return r; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + rr = dns_resource_record_new(key); + if (!rr) + return log_oom_debug(); + + /* Note, for now we only support the most common subset of RRs for decoding here. Please send patches for more. */ + switch (key->type) { + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: { + _cleanup_free_ char *name = NULL; + + static const struct sd_json_dispatch_field table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, 0, SD_JSON_MANDATORY }, + { "key", SD_JSON_VARIANT_OBJECT, NULL, 0, SD_JSON_MANDATORY }, + {} + }; + + r = sd_json_dispatch(v, table, /* flags= */ 0, &name); + if (r < 0) + return r; + + r = dns_name_is_valid(name); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + rr->ptr.name = TAKE_PTR(name); + break; + } + + case DNS_TYPE_A: { + struct in_addr addr = {}; + + static const struct sd_json_dispatch_field table[] = { + { "address", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0, SD_JSON_MANDATORY }, + { "key", SD_JSON_VARIANT_OBJECT, NULL, 0, SD_JSON_MANDATORY }, + {} + }; + + r = sd_json_dispatch(v, table, /* flags= */ 0, &addr); + if (r < 0) + return r; + + rr->a.in_addr = addr; + break; + } + + case DNS_TYPE_AAAA: { + struct in6_addr addr = {}; + + static const struct sd_json_dispatch_field table[] = { + { "address", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0, SD_JSON_MANDATORY }, + { "key", SD_JSON_VARIANT_OBJECT, NULL, 0, SD_JSON_MANDATORY }, + {} + }; + + r = sd_json_dispatch(v, table, /* flags= */ 0, &addr); + if (r < 0) + return r; + + rr->aaaa.in6_addr = addr; + break; + } + + default: + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Decoding DNS type %s is currently not supported.", dns_type_to_string(key->type)); + } + + if (ret) + *ret = TAKE_PTR(rr); + return 0; +} + static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = { /* Mnemonics as listed on https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ [DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5", diff --git a/src/shared/dns-rr.h b/src/shared/dns-rr.h index c30cd71cfa5c7..d747083aa8a81 100644 --- a/src/shared/dns-rr.h +++ b/src/shared/dns-rr.h @@ -419,6 +419,7 @@ int dns_resource_record_new_from_raw(DnsResourceRecord **ret, const void *data, int dns_resource_key_to_json(DnsResourceKey *key, sd_json_variant **ret); int dns_resource_key_from_json(sd_json_variant *v, DnsResourceKey **ret); int dns_resource_record_to_json(DnsResourceRecord *rr, sd_json_variant **ret); +int dns_resource_record_from_json(sd_json_variant *v, DnsResourceRecord **ret); void dns_resource_key_hash_func(const DnsResourceKey *k, struct siphash *state); int dns_resource_key_compare_func(const DnsResourceKey *x, const DnsResourceKey *y); diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c index 1f4fc665c03b8..ce10a44d34ccc 100644 --- a/src/shared/efi-loader.c +++ b/src/shared/efi-loader.c @@ -8,6 +8,7 @@ #include "log.h" #include "parse-util.h" #include "path-util.h" +#include "proc-cmdline.h" #include "stat-util.h" #include "string-util.h" #include "strv.h" @@ -312,6 +313,30 @@ int efi_measured_uki(int log_level) { #endif } +int efi_measured_os(int log_level) { +#if ENABLE_EFI + static int cached = -1; + int r; + + /* Returns if we shall enable our measurement machinery */ + + if (cached >= 0) + return cached; + + bool b; + r = proc_cmdline_get_bool("systemd.tpm2_measured_os", /* flags= */ 0, &b); + if (r < 0) + log_debug_errno(r, "Failed to parse systemd.tpm2_measured_os= kernel command line argument, ignoring: %m"); + else if (r > 0) + return (cached = b); + + /* If nothing is explicitly configured, just assume that if we booted with a measured UKI we also want a measured OS */ + return (cached = efi_measured_uki(log_level)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), "Compiled without support for EFI"); +#endif +} + int efi_loader_get_config_timeout_one_shot(usec_t *ret) { #if ENABLE_EFI _cleanup_free_ char *v = NULL; diff --git a/src/shared/efi-loader.h b/src/shared/efi-loader.h index 5b614cd0a7ee0..abf8bdc49ef04 100644 --- a/src/shared/efi-loader.h +++ b/src/shared/efi-loader.h @@ -15,6 +15,7 @@ int efi_loader_get_features(uint64_t *ret); int efi_stub_get_features(uint64_t *ret); int efi_measured_uki(int log_level); +int efi_measured_os(int log_level); int efi_loader_get_config_timeout_one_shot(usec_t *ret); int efi_loader_update_entry_one_shot_cache(char **cache, struct stat *cache_stat); diff --git a/src/shared/elf-util.c b/src/shared/elf-util.c index e199b42c82a24..97188ca41483e 100644 --- a/src/shared/elf-util.c +++ b/src/shared/elf-util.c @@ -8,6 +8,7 @@ #endif #include +#include "sd-dlopen.h" #include "sd-json.h" #include "alloc-util.h" @@ -90,17 +91,18 @@ static DLSYM_PROTOTYPE(gelf_getnote) = NULL; #endif -int dlopen_dw(void) { +int dlopen_dw(int log_level) { #if HAVE_ELFUTILS int r; - ELF_NOTE_DLOPEN("dw", + SD_ELF_NOTE_DLOPEN( + "dw", "Support for backtrace and ELF package metadata decoding from core files", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libdw.so.1"); r = dlopen_many_sym_or_warn( - &dw_dl, "libdw.so.1", LOG_DEBUG, + &dw_dl, "libdw.so.1", log_level, DLSYM_ARG(dwarf_getscopes), DLSYM_ARG(dwarf_getscopes_die), DLSYM_ARG(dwarf_tag), @@ -139,21 +141,23 @@ int dlopen_dw(void) { return 1; #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libdw support is not compiled in."); #endif } -int dlopen_elf(void) { +int dlopen_elf(int log_level) { #if HAVE_ELFUTILS int r; - ELF_NOTE_DLOPEN("elf", + SD_ELF_NOTE_DLOPEN( + "elf", "Support for backtraces and reading ELF package metadata from core files", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libelf.so.1"); r = dlopen_many_sym_or_warn( - &elf_dl, "libelf.so.1", LOG_DEBUG, + &elf_dl, "libelf.so.1", log_level, DLSYM_ARG(elf_begin), DLSYM_ARG(elf_end), DLSYM_ARG(elf_getphdrnum), @@ -170,7 +174,8 @@ int dlopen_elf(void) { return 1; #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libelf support is not compiled in."); #endif } @@ -406,7 +411,7 @@ static int parse_metadata(const char *name, sd_json_variant *id_json, Elf *elf, /* Package metadata might have different owners, but the * magic ID is always the same. */ - if (!IN_SET(note_header.n_type, ELF_PACKAGE_METADATA_ID, ELF_NOTE_DLOPEN_TYPE)) + if (!IN_SET(note_header.n_type, ELF_PACKAGE_METADATA_ID, SD_ELF_NOTE_DLOPEN_TYPE)) continue; _cleanup_free_ char *payload_0suffixed = NULL; @@ -821,11 +826,11 @@ int parse_elf_object( assert(fd >= 0); - r = dlopen_dw(); + r = dlopen_dw(LOG_DEBUG); if (r < 0) return r; - r = dlopen_elf(); + r = dlopen_elf(LOG_DEBUG); if (r < 0) return r; diff --git a/src/shared/elf-util.h b/src/shared/elf-util.h index b5c3db80ee217..e9d9e959dab3d 100644 --- a/src/shared/elf-util.h +++ b/src/shared/elf-util.h @@ -3,8 +3,8 @@ #include "shared-forward.h" -int dlopen_dw(void); -int dlopen_elf(void); +int dlopen_dw(int log_level); +int dlopen_elf(int log_level); /* Parse an ELF object in a forked process, so that errors while iterating over * untrusted and potentially malicious data do not propagate to the main caller's process. diff --git a/src/shared/exec-util.c b/src/shared/exec-util.c index 2e15f311b8853..420803c77d43d 100644 --- a/src/shared/exec-util.c +++ b/src/shared/exec-util.c @@ -500,7 +500,7 @@ assert_cc((1 << ELEMENTSOF(exec_command_strings)) - 1 == _EXEC_COMMAND_FLAGS_ALL const char* exec_command_flags_to_string(ExecCommandFlags i) { for (size_t idx = 0; idx < ELEMENTSOF(exec_command_strings); idx++) - if (i == (1 << idx)) + if (i == (ExecCommandFlags) (1U << idx)) return exec_command_strings[idx]; return NULL; @@ -516,7 +516,7 @@ ExecCommandFlags exec_command_flags_from_string(const char *s) { if (idx < 0) return _EXEC_COMMAND_FLAGS_INVALID; - return 1 << idx; + return 1U << idx; } int fexecve_or_execve(int executable_fd, const char *executable, char *const argv[], char *const envp[]) { diff --git a/src/shared/extension-util.c b/src/shared/extension-util.c index f2361b3fd1d39..1f99f46e8dfdd 100644 --- a/src/shared/extension-util.c +++ b/src/shared/extension-util.c @@ -140,6 +140,7 @@ int parse_env_extension_hierarchies(char ***ret_hierarchies, const char *hierarc _cleanup_free_ char **l = NULL; int r; + assert(ret_hierarchies); assert(hierarchy_env); r = getenv_path_list(hierarchy_env, &l); if (r == -ENXIO) { diff --git a/src/shared/facts.c b/src/shared/facts.c new file mode 100644 index 0000000000000..fa16c7b7cb8ff --- /dev/null +++ b/src/shared/facts.c @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink.h" + +#include "facts.h" +#include "json-util.h" +#include "log.h" +#include "varlink-io.systemd.Facts.h" + +int facts_add_to_varlink_server( + sd_varlink_server *server, + sd_varlink_method_t vl_method_list_cb, + sd_varlink_method_t vl_method_describe_cb) { + + int r; + + assert(server); + assert(vl_method_list_cb); + assert(vl_method_describe_cb); + + r = sd_varlink_server_add_interface(server, &vl_interface_io_systemd_Facts); + if (r < 0) + return log_debug_errno(r, "Failed to add varlink facts interface to varlink server: %m"); + + r = sd_varlink_server_bind_method_many( + server, + "io.systemd.Facts.List", vl_method_list_cb, + "io.systemd.Facts.Describe", vl_method_describe_cb); + if (r < 0) + return log_debug_errno(r, "Failed to register varlink facts methods: %m"); + + return 0; +} + +static int fact_family_build_json(const FactFamily *ff, sd_json_variant **ret) { + assert(ff); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("name", ff->name), + SD_JSON_BUILD_PAIR_STRING("description", ff->description)); +} + +int facts_method_describe( + const FactFamily fact_family_table[], + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(fact_family_table); + assert(link); + assert(parameters); + assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + r = sd_varlink_set_sentinel(link, "io.systemd.Facts.NoSuchFact"); + if (r < 0) + return r; + + for (const FactFamily *ff = fact_family_table; ff && ff->name; ff++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + r = fact_family_build_json(ff, &v); + if (r < 0) + return log_debug_errno(r, "Failed to describe fact family '%s': %m", ff->name); + + r = sd_varlink_reply(link, v); + if (r < 0) + return log_debug_errno(r, "Failed to send varlink reply: %m"); + } + + return 0; +} + +int facts_method_list( + const FactFamily fact_family_table[], + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(fact_family_table); + assert(link); + assert(parameters); + assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + r = sd_varlink_set_sentinel(link, "io.systemd.Facts.NoSuchFact"); + if (r < 0) + return r; + + FactFamilyContext ctx = { .link = link }; + for (const FactFamily *ff = fact_family_table; ff && ff->name; ff++) { + assert(ff->generate); + + ctx.fact_family = ff; + r = ff->generate(&ctx, userdata); + if (r < 0) + return log_debug_errno( + r, "Failed to list facts for fact family '%s': %m", ff->name); + } + + return 0; +} + +static int fact_build_send(FactFamilyContext *context, const char *object, sd_json_variant *value) { + assert(context); + assert(value); + assert(context->link); + assert(context->fact_family); + + return sd_varlink_replybo(context->link, + SD_JSON_BUILD_PAIR_STRING("name", context->fact_family->name), + JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object), + SD_JSON_BUILD_PAIR_VARIANT("value", value)); +} + +int fact_build_send_string(FactFamilyContext *context, const char *object, const char *value) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + assert(value); + + r = sd_json_variant_new_string(&v, value); + if (r < 0) + return log_debug_errno(r, "Failed to allocate JSON string: %m"); + + return fact_build_send(context, object, v); +} + +int fact_build_send_unsigned(FactFamilyContext *context, const char *object, uint64_t value) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + r = sd_json_variant_new_unsigned(&v, value); + if (r < 0) + return log_debug_errno(r, "Failed to allocate JSON unsigned: %m"); + + return fact_build_send(context, object, v); +} diff --git a/src/shared/facts.h b/src/shared/facts.h new file mode 100644 index 0000000000000..8a8a94cd91f77 --- /dev/null +++ b/src/shared/facts.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +typedef struct FactFamily FactFamily; + +typedef struct FactFamilyContext { + const FactFamily *fact_family; + sd_varlink *link; +} FactFamilyContext; + +typedef int (*fact_family_generate_func_t)(FactFamilyContext *ffc, void *userdata); + +typedef struct FactFamily { + const char *name; + const char *description; + fact_family_generate_func_t generate; +} FactFamily; + +/* Add io.systemd.Facts interface + methods to an existing varlink server */ +int facts_add_to_varlink_server( + sd_varlink_server *server, + sd_varlink_method_t vl_method_list_cb, + sd_varlink_method_t vl_method_describe_cb); + +int facts_method_describe(const FactFamily fact_family_table[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int facts_method_list(const FactFamily fact_family_table[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); + +int fact_build_send_string(FactFamilyContext *context, const char *object, const char *value); +int fact_build_send_unsigned(FactFamilyContext *context, const char *object, uint64_t value); diff --git a/src/shared/fdisk-util.c b/src/shared/fdisk-util.c index 2a0b7d765b360..5eaa91160acd4 100644 --- a/src/shared/fdisk-util.c +++ b/src/shared/fdisk-util.c @@ -1,17 +1,164 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "fdisk-util.h" +#include "log.h" + +#if HAVE_LIBFDISK + +#include "sd-dlopen.h" + #include "alloc-util.h" #include "bitfield.h" #include "dissect-image.h" +#include "dlfcn-util.h" #include "extract-word.h" #include "fd-util.h" -#include "fdisk-util.h" -#include "log.h" #include "parse-util.h" #include "string-util.h" +static void *fdisk_dl = NULL; + +DLSYM_PROTOTYPE(fdisk_add_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_apply_table) = NULL; +DLSYM_PROTOTYPE(fdisk_ask_get_type) = NULL; +DLSYM_PROTOTYPE(fdisk_ask_string_set_result) = NULL; +DLSYM_PROTOTYPE(fdisk_assign_device) = NULL; +DLSYM_PROTOTYPE(fdisk_create_disklabel) = NULL; +DLSYM_PROTOTYPE(fdisk_delete_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_get_devfd) = NULL; +DLSYM_PROTOTYPE(fdisk_get_disklabel_id) = NULL; +DLSYM_PROTOTYPE(fdisk_get_first_lba) = NULL; +DLSYM_PROTOTYPE(fdisk_get_grain_size) = NULL; +DLSYM_PROTOTYPE(fdisk_get_last_lba) = NULL; +DLSYM_PROTOTYPE(fdisk_get_npartitions) = NULL; +DLSYM_PROTOTYPE(fdisk_get_nsectors) = NULL; +DLSYM_PROTOTYPE(fdisk_get_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_get_partitions) = NULL; +DLSYM_PROTOTYPE(fdisk_get_sector_size) = NULL; +DLSYM_PROTOTYPE(fdisk_has_label) = NULL; +DLSYM_PROTOTYPE(fdisk_is_labeltype) = NULL; +DLSYM_PROTOTYPE(fdisk_new_context) = NULL; +DLSYM_PROTOTYPE(fdisk_new_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_new_parttype) = NULL; +DLSYM_PROTOTYPE(fdisk_partname) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_attrs) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_end) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_name) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_partno) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_size) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_start) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_type) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_uuid) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_has_end) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_has_partno) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_has_size) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_has_start) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_is_used) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_partno_follow_default) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_set_attrs) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_set_name) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_set_partno) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_set_size) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_set_start) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_set_type) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_set_uuid) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_size_explicit) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_to_string) = NULL; +DLSYM_PROTOTYPE(fdisk_parttype_get_string) = NULL; +DLSYM_PROTOTYPE(fdisk_parttype_set_typestr) = NULL; +DLSYM_PROTOTYPE(fdisk_ref_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_save_user_sector_size) = NULL; +DLSYM_PROTOTYPE(fdisk_set_ask) = NULL; +DLSYM_PROTOTYPE(fdisk_set_disklabel_id) = NULL; +DLSYM_PROTOTYPE(fdisk_set_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_table_get_nents) = NULL; +DLSYM_PROTOTYPE(fdisk_table_get_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_unref_context) = NULL; +DLSYM_PROTOTYPE(fdisk_unref_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_unref_parttype) = NULL; +DLSYM_PROTOTYPE(fdisk_unref_table) = NULL; +DLSYM_PROTOTYPE(fdisk_write_disklabel) = NULL; +#endif + +int dlopen_fdisk(int log_level) { #if HAVE_LIBFDISK + SD_ELF_NOTE_DLOPEN( + "fdisk", + "Support for reading and writing partition tables", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libfdisk.so.1"); + + return dlopen_many_sym_or_warn( + &fdisk_dl, + "libfdisk.so.1", + log_level, + DLSYM_ARG(fdisk_add_partition), + DLSYM_ARG(fdisk_apply_table), + DLSYM_ARG(fdisk_ask_get_type), + DLSYM_ARG(fdisk_ask_string_set_result), + DLSYM_ARG(fdisk_assign_device), + DLSYM_ARG(fdisk_create_disklabel), + DLSYM_ARG(fdisk_delete_partition), + DLSYM_ARG(fdisk_get_devfd), + DLSYM_ARG(fdisk_get_disklabel_id), + DLSYM_ARG(fdisk_get_first_lba), + DLSYM_ARG(fdisk_get_grain_size), + DLSYM_ARG(fdisk_get_last_lba), + DLSYM_ARG(fdisk_get_npartitions), + DLSYM_ARG(fdisk_get_nsectors), + DLSYM_ARG(fdisk_get_partition), + DLSYM_ARG(fdisk_get_partitions), + DLSYM_ARG(fdisk_get_sector_size), + DLSYM_ARG(fdisk_has_label), + DLSYM_ARG(fdisk_is_labeltype), + DLSYM_ARG(fdisk_new_context), + DLSYM_ARG(fdisk_new_partition), + DLSYM_ARG(fdisk_new_parttype), + DLSYM_ARG(fdisk_partname), + DLSYM_ARG(fdisk_partition_get_attrs), + DLSYM_ARG(fdisk_partition_get_end), + DLSYM_ARG(fdisk_partition_get_name), + DLSYM_ARG(fdisk_partition_get_partno), + DLSYM_ARG(fdisk_partition_get_size), + DLSYM_ARG(fdisk_partition_get_start), + DLSYM_ARG(fdisk_partition_get_type), + DLSYM_ARG(fdisk_partition_get_uuid), + DLSYM_ARG(fdisk_partition_has_end), + DLSYM_ARG(fdisk_partition_has_partno), + DLSYM_ARG(fdisk_partition_has_size), + DLSYM_ARG(fdisk_partition_has_start), + DLSYM_ARG(fdisk_partition_is_used), + DLSYM_ARG(fdisk_partition_partno_follow_default), + DLSYM_ARG(fdisk_partition_set_attrs), + DLSYM_ARG(fdisk_partition_set_name), + DLSYM_ARG(fdisk_partition_set_partno), + DLSYM_ARG(fdisk_partition_set_size), + DLSYM_ARG(fdisk_partition_set_start), + DLSYM_ARG(fdisk_partition_set_type), + DLSYM_ARG(fdisk_partition_set_uuid), + DLSYM_ARG(fdisk_partition_size_explicit), + DLSYM_ARG(fdisk_partition_to_string), + DLSYM_ARG(fdisk_parttype_get_string), + DLSYM_ARG(fdisk_parttype_set_typestr), + DLSYM_ARG(fdisk_ref_partition), + DLSYM_ARG(fdisk_save_user_sector_size), + DLSYM_ARG(fdisk_set_ask), + DLSYM_ARG(fdisk_set_disklabel_id), + DLSYM_ARG(fdisk_set_partition), + DLSYM_ARG(fdisk_table_get_nents), + DLSYM_ARG(fdisk_table_get_partition), + DLSYM_ARG(fdisk_unref_context), + DLSYM_ARG(fdisk_unref_partition), + DLSYM_ARG(fdisk_unref_parttype), + DLSYM_ARG(fdisk_unref_table), + DLSYM_ARG(fdisk_write_disklabel)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libfdisk support is not compiled in."); +#endif +} +#if HAVE_LIBFDISK int fdisk_new_context_at( int dir_fd, const char *path, @@ -34,7 +181,7 @@ int fdisk_new_context_at( dir_fd = fd; } - c = fdisk_new_context(); + c = sym_fdisk_new_context(); if (!c) return -ENOMEM; @@ -45,12 +192,12 @@ int fdisk_new_context_at( } if (sector_size != 0) { - r = fdisk_save_user_sector_size(c, /* phy= */ 0, sector_size); + r = sym_fdisk_save_user_sector_size(c, /* phy= */ 0, sector_size); if (r < 0) return r; } - r = fdisk_assign_device(c, FORMAT_PROC_FD_PATH(dir_fd), read_only); + r = sym_fdisk_assign_device(c, FORMAT_PROC_FD_PATH(dir_fd), read_only); if (r < 0) return r; @@ -64,7 +211,7 @@ int fdisk_partition_get_uuid_as_id128(struct fdisk_partition *p, sd_id128_t *ret assert(p); assert(ret); - ids = fdisk_partition_get_uuid(p); + ids = sym_fdisk_partition_get_uuid(p); if (!ids) return -ENXIO; @@ -78,11 +225,11 @@ int fdisk_partition_get_type_as_id128(struct fdisk_partition *p, sd_id128_t *ret assert(p); assert(ret); - pt = fdisk_partition_get_type(p); + pt = sym_fdisk_partition_get_type(p); if (!pt) return -ENXIO; - pts = fdisk_parttype_get_string(pt); + pts = sym_fdisk_parttype_get_string(pt); if (!pts) return -ENXIO; @@ -99,7 +246,7 @@ int fdisk_partition_get_attrs_as_uint64(struct fdisk_partition *pa, uint64_t *re /* Retrieve current flags as uint64_t mask */ - a = fdisk_partition_get_attrs(pa); + a = sym_fdisk_partition_get_attrs(pa); if (!a) { *ret = 0; return 0; @@ -161,7 +308,7 @@ int fdisk_partition_set_attrs_as_uint64(struct fdisk_partition *pa, uint64_t fla return r; } - return fdisk_partition_set_attrs(pa, strempty(attrs)); + return sym_fdisk_partition_set_attrs(pa, strempty(attrs)); } #endif diff --git a/src/shared/fdisk-util.h b/src/shared/fdisk-util.h index 98a2ed9948fbf..bdac11c5071aa 100644 --- a/src/shared/fdisk-util.h +++ b/src/shared/fdisk-util.h @@ -1,16 +1,79 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "shared-forward.h" + #if HAVE_LIBFDISK #include /* IWYU pragma: export */ -#include "shared-forward.h" +#include "dlfcn-util.h" -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct fdisk_context*, fdisk_unref_context, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct fdisk_partition*, fdisk_unref_partition, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct fdisk_parttype*, fdisk_unref_parttype, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct fdisk_table*, fdisk_unref_table, NULL); +extern DLSYM_PROTOTYPE(fdisk_add_partition); +extern DLSYM_PROTOTYPE(fdisk_apply_table); +extern DLSYM_PROTOTYPE(fdisk_ask_get_type); +extern DLSYM_PROTOTYPE(fdisk_ask_string_set_result); +extern DLSYM_PROTOTYPE(fdisk_assign_device); +extern DLSYM_PROTOTYPE(fdisk_create_disklabel); +extern DLSYM_PROTOTYPE(fdisk_delete_partition); +extern DLSYM_PROTOTYPE(fdisk_get_devfd); +extern DLSYM_PROTOTYPE(fdisk_get_disklabel_id); +extern DLSYM_PROTOTYPE(fdisk_get_first_lba); +extern DLSYM_PROTOTYPE(fdisk_get_grain_size); +extern DLSYM_PROTOTYPE(fdisk_get_last_lba); +extern DLSYM_PROTOTYPE(fdisk_get_npartitions); +extern DLSYM_PROTOTYPE(fdisk_get_nsectors); +extern DLSYM_PROTOTYPE(fdisk_get_partition); +extern DLSYM_PROTOTYPE(fdisk_get_partitions); +extern DLSYM_PROTOTYPE(fdisk_get_sector_size); +extern DLSYM_PROTOTYPE(fdisk_has_label); +extern DLSYM_PROTOTYPE(fdisk_is_labeltype); +extern DLSYM_PROTOTYPE(fdisk_new_context); +extern DLSYM_PROTOTYPE(fdisk_new_partition); +extern DLSYM_PROTOTYPE(fdisk_new_parttype); +extern DLSYM_PROTOTYPE(fdisk_partname); +extern DLSYM_PROTOTYPE(fdisk_partition_get_attrs); +extern DLSYM_PROTOTYPE(fdisk_partition_get_end); +extern DLSYM_PROTOTYPE(fdisk_partition_get_name); +extern DLSYM_PROTOTYPE(fdisk_partition_get_partno); +extern DLSYM_PROTOTYPE(fdisk_partition_get_size); +extern DLSYM_PROTOTYPE(fdisk_partition_get_start); +extern DLSYM_PROTOTYPE(fdisk_partition_get_type); +extern DLSYM_PROTOTYPE(fdisk_partition_get_uuid); +extern DLSYM_PROTOTYPE(fdisk_partition_has_end); +extern DLSYM_PROTOTYPE(fdisk_partition_has_partno); +extern DLSYM_PROTOTYPE(fdisk_partition_has_size); +extern DLSYM_PROTOTYPE(fdisk_partition_has_start); +extern DLSYM_PROTOTYPE(fdisk_partition_is_used); +extern DLSYM_PROTOTYPE(fdisk_partition_partno_follow_default); +extern DLSYM_PROTOTYPE(fdisk_partition_set_attrs); +extern DLSYM_PROTOTYPE(fdisk_partition_set_name); +extern DLSYM_PROTOTYPE(fdisk_partition_set_partno); +extern DLSYM_PROTOTYPE(fdisk_partition_set_size); +extern DLSYM_PROTOTYPE(fdisk_partition_set_start); +extern DLSYM_PROTOTYPE(fdisk_partition_set_type); +extern DLSYM_PROTOTYPE(fdisk_partition_set_uuid); +extern DLSYM_PROTOTYPE(fdisk_partition_size_explicit); +extern DLSYM_PROTOTYPE(fdisk_partition_to_string); +extern DLSYM_PROTOTYPE(fdisk_parttype_get_string); +extern DLSYM_PROTOTYPE(fdisk_parttype_set_typestr); +extern DLSYM_PROTOTYPE(fdisk_ref_partition); +extern DLSYM_PROTOTYPE(fdisk_save_user_sector_size); +extern DLSYM_PROTOTYPE(fdisk_set_ask); +extern DLSYM_PROTOTYPE(fdisk_set_disklabel_id); +extern DLSYM_PROTOTYPE(fdisk_set_partition); +extern DLSYM_PROTOTYPE(fdisk_table_get_nents); +extern DLSYM_PROTOTYPE(fdisk_table_get_partition); +extern DLSYM_PROTOTYPE(fdisk_unref_context); +extern DLSYM_PROTOTYPE(fdisk_unref_partition); +extern DLSYM_PROTOTYPE(fdisk_unref_parttype); +extern DLSYM_PROTOTYPE(fdisk_unref_table); +extern DLSYM_PROTOTYPE(fdisk_write_disklabel); + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct fdisk_context*, sym_fdisk_unref_context, fdisk_unref_contextp, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct fdisk_partition*, sym_fdisk_unref_partition, fdisk_unref_partitionp, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct fdisk_parttype*, sym_fdisk_unref_parttype, fdisk_unref_parttypep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct fdisk_table*, sym_fdisk_unref_table, fdisk_unref_tablep, NULL); int fdisk_new_context_at(int dir_fd, const char *path, bool read_only, uint32_t sector_size, struct fdisk_context **ret); @@ -21,3 +84,5 @@ int fdisk_partition_get_attrs_as_uint64(struct fdisk_partition *pa, uint64_t *re int fdisk_partition_set_attrs_as_uint64(struct fdisk_partition *pa, uint64_t flags); #endif + +int dlopen_fdisk(int log_level); diff --git a/src/shared/fido2-util.c b/src/shared/fido2-util.c index 06c74a4851a2c..50e5147b1f986 100644 --- a/src/shared/fido2-util.c +++ b/src/shared/fido2-util.c @@ -14,6 +14,8 @@ int fido2_generate_salt(struct iovec *ret_salt) { _cleanup_(iovec_done) struct iovec salt = {}; int r; + assert(ret_salt); + r = crypto_random_bytes_allocate_iovec(FIDO2_SALT_SIZE, &salt); if (r < 0) return log_error_errno(r, "Failed to generate FIDO2 salt: %m"); @@ -27,6 +29,8 @@ int fido2_read_salt_file(const char *filename, uint64_t offset, const char *clie _cleanup_free_ char *bind_name = NULL; int r; + assert(ret_salt); + /* If we read the salt via AF_UNIX, make the client recognizable */ if (asprintf(&bind_name, "@%" PRIx64"/%s-fido2-salt/%s", random_u64(), client, node) < 0) return log_oom(); diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index 3ba9b8a4b601b..7337d9f999018 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -19,7 +19,6 @@ #include "errno-util.h" #include "fd-util.h" #include "find-esp.h" -#include "mount-util.h" #include "parse-util.h" #include "path-util.h" #include "stat-util.h" @@ -32,6 +31,7 @@ typedef enum VerifyESPFlags { VERIFY_ESP_UNPRIVILEGED_MODE = 1 << 1, /* Call into udev rather than blkid */ VERIFY_ESP_SKIP_FSTYPE_CHECK = 1 << 2, /* Skip filesystem check */ VERIFY_ESP_SKIP_DEVICE_CHECK = 1 << 3, /* Skip device node check */ + VERIFY_ESP_SKIP_FSROOT_CHECK = 1 << 4, /* Skip fsroot check */ } VerifyESPFlags; static VerifyESPFlags verify_esp_flags_init(int unprivileged_mode, const char *env_name_for_relaxing) { @@ -49,7 +49,7 @@ static VerifyESPFlags verify_esp_flags_init(int unprivileged_mode, const char *e if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $%s environment variable, assuming false.", env_name_for_relaxing); else if (r > 0) - flags |= VERIFY_ESP_SKIP_FSTYPE_CHECK | VERIFY_ESP_SKIP_DEVICE_CHECK; + flags |= VERIFY_ESP_SKIP_FSTYPE_CHECK | VERIFY_ESP_SKIP_DEVICE_CHECK | VERIFY_ESP_SKIP_FSROOT_CHECK; if (detect_container() > 0) flags |= VERIFY_ESP_SKIP_DEVICE_CHECK; @@ -76,9 +76,9 @@ static int verify_esp_blkid( const char *v; int r; - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "No libblkid support: %m"); + return r; r = devname_from_devnum(S_IFBLK, devid, &node); if (r < 0) @@ -260,33 +260,25 @@ static int verify_esp_udev( } static int verify_fsroot_dir( - int dir_fd, const char *path, + int fd, VerifyESPFlags flags, dev_t *ret_dev) { bool searching = FLAGS_SET(flags, VERIFY_ESP_SEARCHING), unprivileged_mode = FLAGS_SET(flags, VERIFY_ESP_UNPRIVILEGED_MODE); - _cleanup_free_ char *f = NULL; - struct statx sx; int r; /* Checks if the specified directory is at the root of its file system, and returns device * major/minor of the device, if it is. */ - assert(dir_fd >= 0); assert(path); + assert(fd >= 0); - /* We pass the full path from the root directory file descriptor so we can use it for logging, but - * dir_fd points to the parent directory of the final component of the given path, so we extract the - * filename and operate on that. */ - - r = path_extract_filename(path, &f); - if (r < 0 && r != -EADDRNOTAVAIL) - return log_error_errno(r, "Failed to extract filename of \"%s\": %m", path); - - r = xstatx_full(dir_fd, f, - AT_SYMLINK_NOFOLLOW, + struct statx sx; + r = xstatx_full(fd, /* path= */ NULL, + /* statx_flags= */ 0, + /* xstatx_flags= */ 0, STATX_TYPE|STATX_INO, /* optional_mask = */ 0, STATX_ATTR_MOUNT_ROOT, @@ -296,8 +288,9 @@ static int verify_fsroot_dir( (unprivileged_mode && ERRNO_IS_NEG_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR, r, "Failed to determine block device node of \"%s\": %m", path); - if (!S_ISDIR(sx.stx_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Path \"%s\" is not a directory", path); + r = statx_verify_directory(&sx); + if (r < 0) + return log_error_errno(r, "Path \"%s\" is not a directory", path); if (!FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT)) return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, @@ -308,7 +301,7 @@ static int verify_fsroot_dir( return 0; if (sx.stx_dev_major == 0) /* Hmm, maybe a btrfs device, and the caller asked for the backing device? Then let's try to get it. */ - return btrfs_get_block_device_at(dir_fd, strempty(f), ret_dev); + return btrfs_get_block_device_fd(fd, ret_dev); *ret_dev = makedev(sx.stx_dev_major, sx.stx_dev_minor); return 0; @@ -318,6 +311,7 @@ static int verify_esp( int rfd, const char *path, char **ret_path, + int *ret_fd, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, @@ -327,9 +321,6 @@ static int verify_esp( bool searching = FLAGS_SET(flags, VERIFY_ESP_SEARCHING), unprivileged_mode = FLAGS_SET(flags, VERIFY_ESP_UNPRIVILEGED_MODE); - _cleanup_free_ char *p = NULL; - _cleanup_close_ int pfd = -EBADF; - dev_t devid = 0; int r; assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); @@ -345,94 +336,81 @@ static int verify_esp( /* Non-root user can only check the status, so if an error occurred in the following, it does not cause any * issues. Let's also, silence the error messages. */ - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_TRIGGER_AUTOFS, &p, &pfd); + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -EBADF; + r = chaseat(rfd, rfd, path, CHASE_TRIGGER_AUTOFS, &p, &fd); if (r < 0) return log_full_errno((searching && r == -ENOENT) || (unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR, - r, "Failed to open parent directory of \"%s\": %m", path); + r, "Failed to open directory \"%s\": %m", path); if (!FLAGS_SET(flags, VERIFY_ESP_SKIP_FSTYPE_CHECK)) { - _cleanup_free_ char *f = NULL; - struct statfs sfs; - - r = path_extract_filename(p, &f); - if (r < 0 && r != -EADDRNOTAVAIL) - return log_error_errno(r, "Failed to extract filename of \"%s\": %m", p); - /* Trigger any automounts so that xstatfsat() operates on the mount instead of the mountpoint - * directory. */ - r = trigger_automount_at(pfd, f); + r = fd_is_fs_type(fd, MSDOS_SUPER_MAGIC); if (r < 0) - return log_error_errno(r, "Failed to trigger automount at \"%s\": %m", p); - - r = xstatfsat(pfd, strempty(f), &sfs); - if (r < 0) - /* If we are searching for the mount point, don't generate a log message if we can't find the path */ - return log_full_errno((searching && r == -ENOENT) || - (unprivileged_mode && r == -EACCES) ? LOG_DEBUG : LOG_ERR, r, + return log_full_errno((unprivileged_mode && r == -EACCES) ? LOG_DEBUG : LOG_ERR, r, "Failed to check file system type of \"%s\": %m", p); - - if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC)) + if (!r) return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), "File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p); } - r = verify_fsroot_dir(pfd, p, flags, FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK) ? NULL : &devid); - if (r < 0) - return r; + dev_t devid = 0; + if (!FLAGS_SET(flags, VERIFY_ESP_SKIP_FSROOT_CHECK)) { + r = verify_fsroot_dir(p, fd, flags, FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK) ? NULL : &devid); + if (r < 0) + return r; + } /* In a container we don't have access to block devices, skip this part of the verification, we trust * the container manager set everything up correctly on its own. */ - if (FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK)) - goto finish; + if (FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK)) { - if (devnum_is_zero(devid)) - return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, - SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), - "Could not determine backing block device of directory \"%s\" (btrfs RAID?).", p); + if (ret_part) + *ret_part = 0; + if (ret_pstart) + *ret_pstart = 0; + if (ret_psize) + *ret_psize = 0; + if (ret_uuid) + *ret_uuid = SD_ID128_NULL; - /* If we are unprivileged we ask udev for the metadata about the partition. If we are privileged we - * use blkid instead. Why? Because this code is called from 'bootctl' which is pretty much an - * emergency recovery tool that should also work when udev isn't up (i.e. from the emergency shell), - * however blkid can't work if we have no privileges to access block devices directly, which is why - * we use udev in that case. */ - if (unprivileged_mode) - r = verify_esp_udev(devid, flags, ret_part, ret_pstart, ret_psize, ret_uuid); - else - r = verify_esp_blkid(devid, flags, ret_part, ret_pstart, ret_psize, ret_uuid); - if (r < 0) - return r; + } else { + if (devnum_is_zero(devid)) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), + "Could not determine backing block device of directory \"%s\" (btrfs RAID?).", p); + + /* If we are unprivileged we ask udev for the metadata about the partition. If we are privileged we + * use blkid instead. Why? Because this code is called from 'bootctl' which is pretty much an + * emergency recovery tool that should also work when udev isn't up (i.e. from the emergency shell), + * however blkid can't work if we have no privileges to access block devices directly, which is why + * we use udev in that case. */ + if (unprivileged_mode) + r = verify_esp_udev(devid, flags, ret_part, ret_pstart, ret_psize, ret_uuid); + else + r = verify_esp_blkid(devid, flags, ret_part, ret_pstart, ret_psize, ret_uuid); + if (r < 0) + return r; + } if (ret_path) *ret_path = TAKE_PTR(p); + if (ret_fd) + *ret_fd = TAKE_FD(fd); if (ret_devid) *ret_devid = devid; return 0; - -finish: - if (ret_path) - *ret_path = TAKE_PTR(p); - if (ret_part) - *ret_part = 0; - if (ret_pstart) - *ret_pstart = 0; - if (ret_psize) - *ret_psize = 0; - if (ret_uuid) - *ret_uuid = SD_ID128_NULL; - if (ret_devid) - *ret_devid = 0; - - return 0; } -int find_esp_and_warn_at( +int find_esp_and_warn_at_full( int rfd, const char *path, int unprivileged_mode, char **ret_path, + int *ret_fd, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, @@ -453,7 +431,7 @@ int find_esp_and_warn_at( flags = verify_esp_flags_init(unprivileged_mode, "SYSTEMD_RELAX_ESP_CHECKS"); if (path) - return verify_esp(rfd, path, ret_path, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, flags); + return verify_esp(rfd, path, ret_path, ret_fd, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, flags); path = getenv("SYSTEMD_ESP_PATH"); if (path) { @@ -466,7 +444,7 @@ int find_esp_and_warn_at( "$SYSTEMD_ESP_PATH does not refer to an absolute path, refusing to use it: \"%s\"", path); - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &p, &fd); + r = chaseat(rfd, rfd, path, CHASE_TRIGGER_AUTOFS, &p, &fd); if (r < 0) return log_error_errno(r, "Failed to resolve path \"%s\": %m", path); @@ -476,11 +454,14 @@ int find_esp_and_warn_at( if (fstat(fd, &st) < 0) return log_error_errno(errno, "Failed to stat '%s': %m", p); - if (!S_ISDIR(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "ESP path '%s' is not a directory.", p); + r = stat_verify_directory(&st); + if (r < 0) + return log_error_errno(r, "ESP path '%s' is not a directory.", p); if (ret_path) *ret_path = TAKE_PTR(p); + if (ret_fd) + *ret_fd = TAKE_FD(fd); if (ret_part) *ret_part = 0; if (ret_pstart) @@ -496,7 +477,15 @@ int find_esp_and_warn_at( } FOREACH_STRING(dir, "/efi", "/boot", "/boot/efi") { - r = verify_esp(rfd, dir, ret_path, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, + r = verify_esp(rfd, + dir, + ret_path, + ret_fd, + ret_part, + ret_pstart, + ret_psize, + ret_uuid, + ret_devid, flags | VERIFY_ESP_SEARCHING); if (r >= 0) return 0; @@ -508,25 +497,21 @@ int find_esp_and_warn_at( return -ENOKEY; } -int find_esp_and_warn( +int find_esp_and_warn_full( const char *root, const char *path, int unprivileged_mode, char **ret_path, + int *ret_fd, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid) { - _cleanup_close_ int rfd = -EBADF; - _cleanup_free_ char *p = NULL; - uint32_t part; - uint64_t pstart, psize; - sd_id128_t uuid; - dev_t devid; int r; + _cleanup_close_ int rfd = -EBADF; if (empty_or_root(root)) rfd = XAT_FDROOT; else { @@ -535,11 +520,18 @@ int find_esp_and_warn( return -errno; } - r = find_esp_and_warn_at( + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *p = NULL; + uint32_t part; + uint64_t pstart, psize; + sd_id128_t uuid; + dev_t devid; + r = find_esp_and_warn_at_full( rfd, path, unprivileged_mode, ret_path ? &p : NULL, + ret_fd ? &fd : NULL, ret_part ? &part : NULL, ret_pstart ? &pstart : NULL, ret_psize ? &psize : NULL, @@ -553,6 +545,8 @@ int find_esp_and_warn( if (r < 0) return r; } + if (ret_fd) + *ret_fd = TAKE_FD(fd); if (ret_part) *ret_part = part; if (ret_pstart) @@ -581,9 +575,9 @@ static int verify_xbootldr_blkid( const char *type, *v; int r; - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "No libblkid support: %m"); + return r; r = devname_from_devnum(S_IFBLK, devid, &node); if (r < 0) @@ -731,71 +725,69 @@ static int verify_xbootldr( const char *path, VerifyESPFlags flags, char **ret_path, + int *ret_fd, sd_id128_t *ret_uuid, dev_t *ret_devid) { - _cleanup_free_ char *p = NULL; - _cleanup_close_ int pfd = -EBADF; bool searching = FLAGS_SET(flags, VERIFY_ESP_SEARCHING), unprivileged_mode = FLAGS_SET(flags, VERIFY_ESP_UNPRIVILEGED_MODE); - dev_t devid = 0; int r; assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); assert(path); - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_TRIGGER_AUTOFS, &p, &pfd); + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -EBADF; + r = chaseat(rfd, rfd, path, CHASE_TRIGGER_AUTOFS, &p, &fd); if (r < 0) return log_full_errno((searching && r == -ENOENT) || (unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR, - r, "Failed to open parent directory of \"%s\": %m", path); + r, "Failed to open directory \"%s\": %m", path); - r = verify_fsroot_dir(pfd, p, flags, FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK) ? NULL : &devid); - if (r < 0) - return r; - - if (FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK)) - goto finish; - - if (devnum_is_zero(devid)) - return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, - SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), - "Could not determine backing block device of directory \"%s\" (btrfs RAID?).%s", - p, - searching ? "" : - "\nHint: set $SYSTEMD_RELAX_XBOOTLDR_CHECKS=yes environment variable " - "to bypass this and further verifications for the directory."); + dev_t devid = 0; + if (!FLAGS_SET(flags, VERIFY_ESP_SKIP_FSROOT_CHECK)) { + r = verify_fsroot_dir(p, fd, flags, FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK) ? NULL : &devid); + if (r < 0) + return r; + } - if (unprivileged_mode) - r = verify_xbootldr_udev(devid, flags, ret_uuid); - else - r = verify_xbootldr_blkid(devid, flags, ret_uuid); - if (r < 0) - return r; + if (FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK)) { + if (ret_uuid) + *ret_uuid = SD_ID128_NULL; + } else { + if (devnum_is_zero(devid)) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), + "Could not determine backing block device of directory \"%s\" (btrfs RAID?).%s", + p, + searching ? "" : + "\nHint: set $SYSTEMD_RELAX_XBOOTLDR_CHECKS=yes environment variable " + "to bypass this and further verifications for the directory."); + + if (unprivileged_mode) + r = verify_xbootldr_udev(devid, flags, ret_uuid); + else + r = verify_xbootldr_blkid(devid, flags, ret_uuid); + if (r < 0) + return r; + } if (ret_path) *ret_path = TAKE_PTR(p); + if (ret_fd) + *ret_fd = TAKE_FD(fd); if (ret_devid) *ret_devid = devid; return 0; - -finish: - if (ret_path) - *ret_path = TAKE_PTR(p); - if (ret_uuid) - *ret_uuid = SD_ID128_NULL; - if (ret_devid) - *ret_devid = 0; - - return 0; } -int find_xbootldr_and_warn_at( +int find_xbootldr_and_warn_at_full( int rfd, const char *path, int unprivileged_mode, char **ret_path, + int *ret_fd, sd_id128_t *ret_uuid, dev_t *ret_devid) { @@ -809,7 +801,7 @@ int find_xbootldr_and_warn_at( flags = verify_esp_flags_init(unprivileged_mode, "SYSTEMD_RELAX_XBOOTLDR_CHECKS"); if (path) - return verify_xbootldr(rfd, path, flags, ret_path, ret_uuid, ret_devid); + return verify_xbootldr(rfd, path, flags, ret_path, ret_fd, ret_uuid, ret_devid); path = getenv("SYSTEMD_XBOOTLDR_PATH"); if (path) { @@ -822,17 +814,20 @@ int find_xbootldr_and_warn_at( "$SYSTEMD_XBOOTLDR_PATH does not refer to an absolute path, refusing to use it: \"%s\"", path); - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &p, &fd); + r = chaseat(rfd, rfd, path, CHASE_TRIGGER_AUTOFS, &p, &fd); if (r < 0) return log_error_errno(r, "Failed to resolve path \"%s\": %m", p); if (fstat(fd, &st) < 0) return log_error_errno(errno, "Failed to stat '%s': %m", p); - if (!S_ISDIR(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "XBOOTLDR path '%s' is not a directory.", p); + r = stat_verify_directory(&st); + if (r < 0) + return log_error_errno(r, "XBOOTLDR path '%s' is not a directory.", p); if (ret_path) *ret_path = TAKE_PTR(p); + if (ret_fd) + *ret_fd = TAKE_FD(fd); if (ret_uuid) *ret_uuid = SD_ID128_NULL; if (ret_devid) @@ -841,7 +836,14 @@ int find_xbootldr_and_warn_at( return 0; } - r = verify_xbootldr(rfd, "/boot", flags | VERIFY_ESP_SEARCHING, ret_path, ret_uuid, ret_devid); + r = verify_xbootldr( + rfd, + "/boot", + flags | VERIFY_ESP_SEARCHING, + ret_path, + ret_fd, + ret_uuid, + ret_devid); if (r < 0) { if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL, -ENOTDIR, -ENOTTY)) /* This one is not it */ return r; @@ -852,20 +854,18 @@ int find_xbootldr_and_warn_at( return 0; } -int find_xbootldr_and_warn( +int find_xbootldr_and_warn_full( const char *root, const char *path, int unprivileged_mode, char **ret_path, + int *ret_fd, sd_id128_t *ret_uuid, dev_t *ret_devid) { - _cleanup_close_ int rfd = -EBADF; - _cleanup_free_ char *p = NULL; - sd_id128_t uuid; - dev_t devid; int r; + _cleanup_close_ int rfd = -EBADF; if (empty_or_root(root)) rfd = XAT_FDROOT; else { @@ -874,11 +874,16 @@ int find_xbootldr_and_warn( return -errno; } - r = find_xbootldr_and_warn_at( + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *p = NULL; + sd_id128_t uuid; + dev_t devid; + r = find_xbootldr_and_warn_at_full( rfd, path, unprivileged_mode, ret_path ? &p : NULL, + ret_fd ? &fd : NULL, ret_uuid ? &uuid : NULL, ret_devid ? &devid : NULL); if (r < 0) @@ -889,6 +894,8 @@ int find_xbootldr_and_warn( if (r < 0) return r; } + if (ret_fd) + *ret_fd = TAKE_FD(fd); if (ret_uuid) *ret_uuid = uuid; if (ret_devid) diff --git a/src/shared/find-esp.h b/src/shared/find-esp.h index ac62e6c51e519..ad02bcd8f3f60 100644 --- a/src/shared/find-esp.h +++ b/src/shared/find-esp.h @@ -4,8 +4,22 @@ #include "shared-forward.h" -int find_esp_and_warn_at(int rfd, const char *path, int unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); -int find_esp_and_warn(const char *root, const char *path, int unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); +int find_esp_and_warn_at_full(int rfd, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); +int find_esp_and_warn_full(const char *root, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); -int find_xbootldr_and_warn_at(int rfd, const char *path, int unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid); -int find_xbootldr_and_warn(const char *root, const char *path, int unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid); +static inline int find_esp_and_warn_at(int rfd, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd) { + return find_esp_and_warn_at_full(rfd, path, unprivileged_mode, ret_path, ret_fd, NULL, NULL, NULL, NULL, NULL); +} +static inline int find_esp_and_warn(const char *root, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd) { + return find_esp_and_warn_full(root, path, unprivileged_mode, ret_path, ret_fd, NULL, NULL, NULL, NULL, NULL); +} + +int find_xbootldr_and_warn_at_full(int rfd, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd, sd_id128_t *ret_uuid, dev_t *ret_devid); +int find_xbootldr_and_warn_full(const char *root, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd, sd_id128_t *ret_uuid, dev_t *ret_devid); + +static inline int find_xbootldr_and_warn_at(int rfd, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd) { + return find_xbootldr_and_warn_at_full(rfd, path, unprivileged_mode, ret_path, ret_fd, NULL, NULL); +} +static inline int find_xbootldr_and_warn(const char *root, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd) { + return find_xbootldr_and_warn_full(root, path, unprivileged_mode, ret_path, ret_fd, NULL, NULL); +} diff --git a/src/shared/firewall-util.c b/src/shared/firewall-util.c index 93eef4eecf598..651870e369889 100644 --- a/src/shared/firewall-util.c +++ b/src/shared/firewall-util.c @@ -50,19 +50,7 @@ static const char* dnat_map_name(void) { return cached; } -static sd_netlink_message** netlink_message_unref_many(sd_netlink_message **m) { - if (!m) - return NULL; - - /* This does not free array. The end of the array must be NULL. */ - - for (sd_netlink_message **p = m; *p; p++) - *p = sd_netlink_message_unref(*p); - - return m; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(sd_netlink_message**, netlink_message_unref_many); +static DEFINE_ARRAY_DONE_FUNC(sd_netlink_message*, sd_netlink_message_unref); static int nfnl_open_expr_container(sd_netlink_message *m, const char *name) { int r; @@ -736,8 +724,7 @@ static uint32_t concat_types2(enum nft_key_types a, enum nft_key_types b) { } static int fw_nftables_init_family(sd_netlink *nfnl, int family) { - sd_netlink_message *messages[10] = {}; - _unused_ _cleanup_(netlink_message_unref_manyp) sd_netlink_message **unref = messages; + _cleanup_(sd_netlink_message_unref_many) sd_netlink_message *messages[10] = {}; size_t msgcnt = 0, ip_type_size; uint32_t set_id = 0; int ip_type, r; @@ -1058,8 +1045,7 @@ static int fw_nftables_add_local_dnat_internal( uint16_t remote_port, const union in_addr_union *previous_remote) { - sd_netlink_message *messages[3] = {}; - _unused_ _cleanup_(netlink_message_unref_manyp) sd_netlink_message **unref = messages; + _cleanup_(sd_netlink_message_unref_many) sd_netlink_message *messages[3] = {}; uint32_t data[5], key[2], dlen; size_t msgcnt = 0; int r; diff --git a/src/shared/fork-notify.c b/src/shared/fork-notify.c index 6f87a2fdce2b2..066ee29115faa 100644 --- a/src/shared/fork-notify.c +++ b/src/shared/fork-notify.c @@ -3,14 +3,19 @@ #include #include +#include "alloc-util.h" #include "build-path.h" +#include "chase.h" +#include "chattr-util.h" #include "escape.h" #include "event-util.h" #include "exit-status.h" +#include "fd-util.h" #include "fork-notify.h" #include "log.h" #include "notify-recv.h" #include "parse-util.h" +#include "path-util.h" #include "pidref.h" #include "process-util.h" #include "runtime-scope.h" @@ -90,7 +95,6 @@ static int on_child_notify(sd_event_source *s, int fd, uint32_t revents, void *u int fork_notify(char * const *argv, PidRef *ret_pidref) { int r; - assert(!strv_isempty(argv)); assert(ret_pidref); if (!is_main_thread()) @@ -119,7 +123,7 @@ int fork_notify(char * const *argv, PidRef *ret_pidref) { if (r < 0) return r; - if (DEBUG_LOGGING) { + if (DEBUG_LOGGING && argv) { _cleanup_free_ char *l = quote_command_line(argv, SHELL_ESCAPE_EMPTY); log_debug("Invoking '%s' as child.", strnull(l)); } @@ -141,6 +145,11 @@ int fork_notify(char * const *argv, PidRef *ret_pidref) { _exit(EXIT_MEMORY); } + if (!argv) { + *ret_pidref = TAKE_PIDREF(child); + return 0; /* Let the caller run custom code in the child */ + } + r = invoke_callout_binary(argv[0], argv); log_debug_errno(r, "Failed to invoke %s: %m", argv[0]); _exit(EXIT_EXEC); @@ -164,7 +173,7 @@ int fork_notify(char * const *argv, PidRef *ret_pidref) { *ret_pidref = TAKE_PIDREF(child); - return 0; + return 1; /* In the parent */ } static void fork_notify_terminate_internal(PidRef *pidref) { @@ -205,7 +214,7 @@ void fork_notify_terminate_many(sd_event_source **array, size_t n) { free(array); } -int journal_fork(RuntimeScope scope, char * const* units, PidRef *ret_pidref) { +int journal_fork(RuntimeScope scope, char * const* units, OutputMode output, PidRef *ret_pidref) { assert(scope >= 0); assert(scope < _RUNTIME_SCOPE_MAX); @@ -228,5 +237,98 @@ int journal_fork(RuntimeScope scope, char * const* units, PidRef *ret_pidref) { *u) < 0) return log_oom_debug(); + if (output >= 0) + if (strv_extendf(&argv, "--output=%s", output_mode_to_string(output)) < 0) + return log_oom_debug(); + return fork_notify(argv, ret_pidref); } + +int fork_journal_remote( + const char *listen_address, + const char *output, + uint64_t max_use, + uint64_t keep_free, + uint64_t max_file_size, + uint64_t max_files, + PidRef *ret_pidref) { + + int r; + + assert(listen_address); + assert(output); + assert(ret_pidref); + + ChaseFlags chase_flags = CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY; + if (endswith(output, ".journal")) + chase_flags |= CHASE_PARENT; + + _cleanup_close_ int fd = -EBADF; + r = chase(output, /* root= */ NULL, chase_flags, /* ret_path= */ NULL, &fd); + if (r < 0) + return log_error_errno(r, "Failed to create journal directory for '%s': %m", output); + + r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL); + if (r < 0) + log_debug_errno(r, "Failed to set NOCOW flag on journal directory for '%s', ignoring: %m", output); + + _cleanup_free_ char *sd_socket_activate = NULL; + r = find_executable("systemd-socket-activate", &sd_socket_activate); + if (r < 0) + return log_error_errno(r, "Failed to find systemd-socket-activate binary: %m"); + + _cleanup_free_ char *sd_journal_remote = NULL; + r = find_executable_full( + "systemd-journal-remote", + /* root= */ NULL, + STRV_MAKE(LIBEXECDIR), + /* use_path_envvar= */ true, + &sd_journal_remote, + /* ret_fd= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to find systemd-journal-remote binary: %m"); + + _cleanup_strv_free_ char **argv = strv_new( + sd_socket_activate, + "--listen", listen_address, + sd_journal_remote, + "--output", output, + "--split-mode", endswith(output, ".journal") ? "none" : "host"); + if (!argv) + return log_oom(); + + if (max_use != UINT64_MAX && + strv_extendf(&argv, "--max-use=%" PRIu64, max_use) < 0) + return log_oom(); + + if (keep_free != UINT64_MAX && + strv_extendf(&argv, "--keep-free=%" PRIu64, keep_free) < 0) + return log_oom(); + + if (max_file_size != UINT64_MAX && + strv_extendf(&argv, "--max-file-size=%" PRIu64, max_file_size) < 0) + return log_oom(); + + if (max_files != UINT64_MAX && + strv_extendf(&argv, "--max-files=%" PRIu64, max_files) < 0) + return log_oom(); + + r = fork_notify(/* argv= */ NULL, ret_pidref); + if (r < 0) + return r; + if (r == 0) { + /* In the child */ + if (setenv("SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE", + "/dev/null", + /* overwrite= */ true) < 0) { + log_debug_errno(errno, "Failed to set $SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE: %m"); + _exit(EXIT_MEMORY); + } + + r = invoke_callout_binary(argv[0], argv); + log_debug_errno(r, "Failed to invoke %s: %m", argv[0]); + _exit(EXIT_EXEC); + } + + return 0; +} diff --git a/src/shared/fork-notify.h b/src/shared/fork-notify.h index 103ab78983371..cc241beff9335 100644 --- a/src/shared/fork-notify.h +++ b/src/shared/fork-notify.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "output-mode.h" #include "shared-forward.h" int fork_notify(char * const *argv, PidRef *ret_pidref); @@ -9,4 +10,13 @@ void fork_notify_terminate(PidRef *pidref); void fork_notify_terminate_many(sd_event_source **array, size_t n); -int journal_fork(RuntimeScope scope, char * const *units, PidRef *ret_pidref); +int journal_fork(RuntimeScope scope, char * const *units, OutputMode output, PidRef *ret_pidref); + +int fork_journal_remote( + const char *listen_address, + const char *output, + uint64_t max_use, + uint64_t keep_free, + uint64_t max_file_size, + uint64_t max_files, + PidRef *ret_pidref); diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 8b5b88370f13c..f678a6722cec9 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -87,6 +87,7 @@ typedef struct TableData { union { uint8_t data[0]; /* data is generic array */ bool boolean; + int tristate; usec_t timestamp; usec_t timespan; uint64_t size; @@ -159,7 +160,7 @@ struct Table { bool *reverse_map; }; -Table *table_new_raw(size_t n_columns) { +Table* table_new_raw(size_t n_columns) { _cleanup_(table_unrefp) Table *t = NULL; assert(n_columns > 0); @@ -179,7 +180,7 @@ Table *table_new_raw(size_t n_columns) { return TAKE_PTR(t); } -Table *table_new_internal(const char *first_header, ...) { +Table* table_new_internal(const char *first_header, ...) { _cleanup_(table_unrefp) Table *t = NULL; size_t n_columns = 1; va_list ap; @@ -216,7 +217,7 @@ Table *table_new_internal(const char *first_header, ...) { return TAKE_PTR(t); } -Table *table_new_vertical(void) { +Table* table_new_vertical(void) { _cleanup_(table_unrefp) Table *t = NULL; TableCell *cell; @@ -242,7 +243,7 @@ Table *table_new_vertical(void) { return TAKE_PTR(t); } -static TableData *table_data_free(TableData *d) { +static TableData* table_data_free(TableData *d) { assert(d); free(d->formatted); @@ -260,7 +261,7 @@ static TableData *table_data_free(TableData *d) { DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free); DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref); -Table *table_unref(Table *t) { +Table* table_unref(Table *t) { if (!t) return NULL; @@ -342,6 +343,7 @@ static size_t table_data_size(TableDataType type, const void *data) { case TABLE_PERCENT: case TABLE_IFINDEX: case TABLE_SIGNAL: + case TABLE_TRISTATE: return sizeof(int); case TABLE_IN_ADDR: @@ -425,7 +427,7 @@ static bool table_data_matches( return memcmp_safe(data, d->data, l) == 0; } -static TableData *table_data_new( +static TableData* table_data_new( TableDataType type, const void *data, size_t minimum_width, @@ -935,6 +937,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { uint64_t uint64; int percent; int ifindex; + int tristate; bool b; union in_addr_union address; sd_id128_t id128; @@ -972,6 +975,11 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { data = &buffer.b; break; + case TABLE_TRISTATE: + buffer.tristate = va_arg(ap, int); + data = &buffer.tristate; + break; + case TABLE_TIMESTAMP: case TABLE_TIMESTAMP_UTC: case TABLE_TIMESTAMP_RELATIVE: @@ -1073,12 +1081,12 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { break; case TABLE_IN_ADDR: - buffer.address = *va_arg(ap, union in_addr_union *); + buffer.address.in = *va_arg(ap, struct in_addr *); data = &buffer.address.in; break; case TABLE_IN6_ADDR: - buffer.address = *va_arg(ap, union in_addr_union *); + buffer.address.in6 = *va_arg(ap, struct in6_addr *); data = &buffer.address.in6; break; @@ -1434,12 +1442,25 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t return strv_compare(a->strv, b->strv); case TABLE_BOOLEAN: + case TABLE_BOOLEAN_CHECKMARK: if (!a->boolean && b->boolean) return -1; if (a->boolean && !b->boolean) return 1; return 0; + case TABLE_TRISTATE: + /* NB: we do not use CMP() here, since we want to collapse all negative and all + * positive into one bucket each. */ + if ((a->tristate < 0 && b->tristate >= 0) || + (a->tristate == 0 && b->tristate > 0)) + return -1; + + if ((b->tristate < 0 && a->tristate >= 0) || + (b->tristate == 0 && a->tristate > 0)) + return 1; + return 0; + case TABLE_TIMESTAMP: case TABLE_TIMESTAMP_UTC: case TABLE_TIMESTAMP_RELATIVE: @@ -1543,6 +1564,10 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t static int table_data_compare(const size_t *a, const size_t *b, Table *t) { int r; + /* This is called from qsort()s inner loops. Correctly implemented qsort will never pass NULL so we + just suppress the check via POINTER_MAY_BE_NULL instead of assert() to avoid the runtime cost. */ + POINTER_MAY_BE_NULL(a); + POINTER_MAY_BE_NULL(b); assert(t); assert(t->sort_map); @@ -1602,7 +1627,7 @@ static char* format_strv_width(char **strv, size_t column_width) { return buf; } -static const char *table_data_format( +static const char* table_data_format( Table *t, TableData *d, bool avoid_uppercasing, @@ -1616,6 +1641,8 @@ static const char *table_data_format( (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width)) return d->formatted; + d->formatted = mfree(d->formatted); + switch (d->type) { case TABLE_EMPTY: return table_ersatz_string(t); @@ -1649,18 +1676,13 @@ static const char *table_data_format( *q = 0; return d->formatted; - } else if (d->type == TABLE_FIELD) { - d->formatted = strjoin(s, ":"); - if (!d->formatted) - return NULL; - - return d->formatted; } - if (bn) { - d->formatted = TAKE_PTR(bn); - return d->formatted; - } + if (d->type == TABLE_FIELD) + return (d->formatted = strjoin(s, ":")); + + if (bn) + return (d->formatted = TAKE_PTR(bn)); return d->string; } @@ -1669,26 +1691,20 @@ static const char *table_data_format( if (strv_isempty(d->strv)) return table_ersatz_string(t); - d->formatted = strv_join(d->strv, "\n"); - if (!d->formatted) - return NULL; - break; + return (d->formatted = strv_join(d->strv, "\n")); - case TABLE_STRV_WRAPPED: { + case TABLE_STRV_WRAPPED: if (strv_isempty(d->strv)) return table_ersatz_string(t); - char *buf = format_strv_width(d->strv, column_width); - if (!buf) + d->formatted = format_strv_width(d->strv, column_width); + if (!d->formatted) return NULL; - free_and_replace(d->formatted, buf); d->formatted_for_width = column_width; if (have_soft) *have_soft = true; - - break; - } + return d->formatted; case TABLE_BOOLEAN: return yes_no(d->boolean); @@ -1696,18 +1712,24 @@ static const char *table_data_format( case TABLE_BOOLEAN_CHECKMARK: return glyph(d->boolean ? GLYPH_CHECK_MARK : GLYPH_CROSS_MARK); + case TABLE_TRISTATE: + if (d->tristate < 0) + return table_ersatz_string(t); + + return yes_no(d->tristate); + case TABLE_TIMESTAMP: case TABLE_TIMESTAMP_UTC: case TABLE_TIMESTAMP_RELATIVE: case TABLE_TIMESTAMP_RELATIVE_MONOTONIC: case TABLE_TIMESTAMP_LEFT: case TABLE_TIMESTAMP_DATE: { - _cleanup_free_ char *p = NULL; char *ret; - p = new(char, - IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, TABLE_TIMESTAMP_LEFT) ? - FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX); + _cleanup_free_ char *p = new( + char, + IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, TABLE_TIMESTAMP_LEFT) ? + FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX); if (!p) return NULL; @@ -1726,16 +1748,13 @@ static const char *table_data_format( if (!ret) return "-"; - d->formatted = TAKE_PTR(p); - break; + return (d->formatted = TAKE_PTR(p)); } case TABLE_TIMESPAN: case TABLE_TIMESPAN_MSEC: case TABLE_TIMESPAN_DAY: { - _cleanup_free_ char *p = NULL; - - p = new(char, FORMAT_TIMESPAN_MAX); + _cleanup_free_ char *p = new(char, FORMAT_TIMESPAN_MAX); if (!p) return NULL; @@ -1744,344 +1763,137 @@ static const char *table_data_format( d->type == TABLE_TIMESPAN_MSEC ? USEC_PER_MSEC : USEC_PER_DAY)) return "-"; - d->formatted = TAKE_PTR(p); - break; + return (d->formatted = TAKE_PTR(p)); } case TABLE_SIZE: { - _cleanup_free_ char *p = NULL; - - p = new(char, FORMAT_BYTES_MAX); + _cleanup_free_ char *p = new(char, FORMAT_BYTES_MAX); if (!p) return NULL; if (!format_bytes(p, FORMAT_BYTES_MAX, d->size)) return table_ersatz_string(t); - d->formatted = TAKE_PTR(p); - break; + return (d->formatted = TAKE_PTR(p)); } case TABLE_BPS: { - _cleanup_free_ char *p = NULL; - size_t n; - - p = new(char, FORMAT_BYTES_MAX+2); + _cleanup_free_ char *p = new(char, FORMAT_BYTES_MAX+2); if (!p) return NULL; if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT)) return table_ersatz_string(t); - n = strlen(p); + size_t n = strlen(p); strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps"); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_INT: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1); - if (!p) - return NULL; - - sprintf(p, "%i", d->int_val); - d->formatted = TAKE_PTR(p); - break; + return (d->formatted = TAKE_PTR(p)); } - case TABLE_INT8: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->int8) + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIi8, d->int8); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_INT16: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->int16) + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIi16, d->int16); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_INT32: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIi32, d->int32); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_INT64: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIi64, d->int64); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_UINT: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1); - if (!p) - return NULL; - - sprintf(p, "%u", d->uint_val); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_UINT8: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->uint8) + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIu8, d->uint8); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_UINT16: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->uint16) + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIu16, d->uint16); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_UINT32: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIu32, d->uint32); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_UINT32_HEX: { - _cleanup_free_ char *p = NULL; - - p = new(char, 8 + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIx32, d->uint32); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_UINT32_HEX_0x: { - _cleanup_free_ char *p = NULL; - - p = new(char, 2 + 8 + 1); - if (!p) - return NULL; - - sprintf(p, "0x%" PRIx32, d->uint32); - d->formatted = TAKE_PTR(p); - break; - } + case TABLE_INT: + return (d->formatted = asprintf_safe("%i", d->int_val)); - case TABLE_UINT64: { - _cleanup_free_ char *p = NULL; + case TABLE_INT8: + return (d->formatted = asprintf_safe("%" PRIi8, d->int8)); - p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1); - if (!p) - return NULL; + case TABLE_INT16: + return (d->formatted = asprintf_safe("%" PRIi16, d->int16)); - sprintf(p, "%" PRIu64, d->uint64); - d->formatted = TAKE_PTR(p); - break; - } + case TABLE_INT32: + return (d->formatted = asprintf_safe("%" PRIi32, d->int32)); - case TABLE_UINT64_HEX: { - _cleanup_free_ char *p = NULL; + case TABLE_INT64: + return (d->formatted = asprintf_safe("%" PRIi64, d->int64)); - p = new(char, 16 + 1); - if (!p) - return NULL; + case TABLE_UINT: + return (d->formatted = asprintf_safe("%u", d->uint_val)); - sprintf(p, "%" PRIx64, d->uint64); - d->formatted = TAKE_PTR(p); - break; - } + case TABLE_UINT8: + return (d->formatted = asprintf_safe("%" PRIu8, d->uint8)); - case TABLE_UINT64_HEX_0x: { - _cleanup_free_ char *p = NULL; + case TABLE_UINT16: + return (d->formatted = asprintf_safe("%" PRIu16, d->uint16)); - p = new(char, 2 + 16 + 1); - if (!p) - return NULL; + case TABLE_UINT32: + return (d->formatted = asprintf_safe("%" PRIu32, d->uint32)); - sprintf(p, "0x%" PRIx64, d->uint64); - d->formatted = TAKE_PTR(p); - break; - } + case TABLE_UINT32_HEX: + return (d->formatted = asprintf_safe("%" PRIx32, d->uint32)); - case TABLE_PERCENT: { - _cleanup_free_ char *p = NULL; + case TABLE_UINT32_HEX_0x: + return (d->formatted = asprintf_safe("0x%" PRIx32, d->uint32)); - p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2); - if (!p) - return NULL; + case TABLE_UINT64: + return (d->formatted = asprintf_safe("%" PRIu64, d->uint64)); - sprintf(p, "%i%%" , d->percent); - d->formatted = TAKE_PTR(p); - break; - } + case TABLE_UINT64_HEX: + return (d->formatted = asprintf_safe("%" PRIx64, d->uint64)); - case TABLE_IFINDEX: { - _cleanup_free_ char *p = NULL; + case TABLE_UINT64_HEX_0x: + return (d->formatted = asprintf_safe("0x%" PRIx64, d->uint64)); - if (format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &p) < 0) - return NULL; + case TABLE_PERCENT: + return (d->formatted = asprintf_safe("%i%%" , d->percent)); - d->formatted = TAKE_PTR(p); - break; - } + case TABLE_IFINDEX: + (void) format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &d->formatted); + return d->formatted; case TABLE_IN_ADDR: - case TABLE_IN6_ADDR: { - _cleanup_free_ char *p = NULL; - - if (in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6, - &d->address, &p) < 0) - return NULL; - - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_ID128: { - char *p; + case TABLE_IN6_ADDR: + (void) in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6, + &d->address, + &d->formatted); + return d->formatted; - p = new(char, SD_ID128_STRING_MAX); - if (!p) + case TABLE_ID128: + d->formatted = new(char, SD_ID128_STRING_MAX); + if (!d->formatted) return NULL; - d->formatted = sd_id128_to_string(d->id128, p); - break; - } + return sd_id128_to_string(d->id128, d->formatted); - case TABLE_UUID: { - char *p; - - p = new(char, SD_ID128_UUID_STRING_MAX); - if (!p) + case TABLE_UUID: + d->formatted = new(char, SD_ID128_UUID_STRING_MAX); + if (!d->formatted) return NULL; - d->formatted = sd_id128_to_uuid_string(d->id128, p); - break; - } - - case TABLE_UID: { - char *p; + return sd_id128_to_uuid_string(d->id128, d->formatted); + case TABLE_UID: if (!uid_is_valid(d->uid)) return table_ersatz_string(t); - p = new(char, DECIMAL_STR_WIDTH(d->uid) + 1); - if (!p) - return NULL; - sprintf(p, UID_FMT, d->uid); - - d->formatted = p; - break; - } - - case TABLE_GID: { - char *p; + return (d->formatted = asprintf_safe(UID_FMT, d->uid)); + case TABLE_GID: if (!gid_is_valid(d->gid)) return table_ersatz_string(t); - p = new(char, DECIMAL_STR_WIDTH(d->gid) + 1); - if (!p) - return NULL; - sprintf(p, GID_FMT, d->gid); - - d->formatted = p; - break; - } - - case TABLE_PID: { - char *p; + return (d->formatted = asprintf_safe(GID_FMT, d->gid)); + case TABLE_PID: if (!pid_is_valid(d->pid)) return table_ersatz_string(t); - p = new(char, DECIMAL_STR_WIDTH(d->pid) + 1); - if (!p) - return NULL; - sprintf(p, PID_FMT, d->pid); - - d->formatted = p; - break; - } + return (d->formatted = asprintf_safe(PID_FMT, d->pid)); case TABLE_SIGNAL: { - const char *suffix; - char *p; - - suffix = signal_to_string(d->int_val); + const char *suffix = signal_to_string(d->int_val); if (!suffix) return table_ersatz_string(t); - p = strjoin("SIG", suffix); - if (!p) - return NULL; - - d->formatted = p; - break; + return (d->formatted = strjoin("SIG", suffix)); } - case TABLE_MODE: { - char *p; - + case TABLE_MODE: if (d->mode == MODE_INVALID) return table_ersatz_string(t); - p = new(char, 4 + 1); - if (!p) - return NULL; - - sprintf(p, "%04o", d->mode & 07777); - d->formatted = p; - break; - } + return (d->formatted = asprintf_safe("%04o", d->mode & 07777)); case TABLE_MODE_INODE_TYPE: - if (d->mode == MODE_INVALID) return table_ersatz_string(t); @@ -2091,31 +1903,21 @@ static const char *table_data_format( if (devnum_is_zero(d->devnum)) return table_ersatz_string(t); - if (asprintf(&d->formatted, DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d->devnum)) < 0) - return NULL; + return (d->formatted = asprintf_safe(DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d->devnum))); - break; - - case TABLE_JSON: { + case TABLE_JSON: if (!d->json) return table_ersatz_string(t); - char *p; - if (sd_json_variant_format(d->json, /* flags= */ 0, &p) < 0) - return NULL; - - d->formatted = p; - break; - } + (void) sd_json_variant_format(d->json, /* flags= */ 0, &d->formatted); + return d->formatted; default: assert_not_reached(); } - - return d->formatted; } -static const char *table_data_format_strip_ansi( +static const char* table_data_format_strip_ansi( Table *t, TableData *d, bool avoid_uppercasing, @@ -2252,7 +2054,7 @@ static int table_data_requested_width_height( return truncation_applied; } -static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) { +static char* align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) { size_t w = 0, space, lspace, old_length, clickable_length; _cleanup_free_ char *clickable = NULL; const char *p; @@ -2368,7 +2170,85 @@ static const char* table_data_rgap_underline(const TableData *d) { return NULL; } -int table_print(Table *t, FILE *f) { +int table_data_requested_width(Table *table, size_t column, size_t *ret) { + size_t width = 0; + int r; + + assert(table); + assert(ret); + + for (size_t row = 0; row < table_get_rows(table); row++) { + TableCell *cell = table_get_cell(table, row, column); + if (!cell) + continue; + + TableData *data = table_get_data(table, cell); + if (!data) + continue; + + size_t w; + + r = table_data_requested_width_height( + table, data, SIZE_MAX, &w, /* ret_height= */ NULL, /* have_soft= */ NULL); + if (r < 0) + return r; + + width = MAX(width, w); + } + + *ret = width; + return 0; +} + +int table_set_column_width(Table *t, size_t column, size_t width) { + int r = 0; + + assert(t); + + for (size_t row = 0; row < table_get_rows(t); row++) { + TableCell *cell = table_get_cell(t, row, column); + if (!cell) + continue; + + RET_GATHER(r, table_set_minimum_width(t, cell, width)); + } + + return r; +} + +int _table_sync_column_widths(size_t column, Table *a, ...) { + size_t max = 0; + va_list ap; + int r = 0; + + assert(a); + + /* Make the specified column have the same width in the tables. */ + + va_start(ap, a); + for (Table *t = a; t; t = va_arg(ap, Table*)) { + size_t w; + + r = table_data_requested_width(t, column, &w); + if (r < 0) + break; + + max = MAX(max, w); + } + va_end(ap); + if (r < 0) + return log_error_errno(r, "Failed to query table column width: %m"); + + r = 0; + va_start(ap, a); + for (Table *t = a; t; t = va_arg(ap, Table*)) + RET_GATHER(r, table_set_column_width(t, column, max)); + va_end(ap); + + return r; +} + +int table_print_full(Table *t, FILE *f, bool flush) { size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width, *width = NULL; @@ -2788,9 +2668,21 @@ int table_print(Table *t, FILE *f) { } while (more_sublines); } + if (!flush) + return 0; + return fflush_and_check(f); } +int table_print_or_warn(Table *t) { + int r; + + r = table_print(t); + if (r < 0) + return table_log_print_error(r); + return 0; +} + int table_format(Table *t, char **ret) { _cleanup_(memstream_done) MemStream m = {}; FILE *f; @@ -2803,7 +2695,7 @@ int table_format(Table *t, char **ret) { if (!f) return -ENOMEM; - r = table_print(t, f); + r = table_print_full(t, f, /* flush= */ true); if (r < 0) return r; @@ -2851,7 +2743,7 @@ int table_set_reverse(Table *t, size_t column, bool b) { return 0; } -TableCell *table_get_cell(Table *t, size_t row, size_t column) { +TableCell* table_get_cell(Table *t, size_t row, size_t column) { size_t i; assert(t); @@ -2866,7 +2758,7 @@ TableCell *table_get_cell(Table *t, size_t row, size_t column) { return TABLE_INDEX_TO_CELL(i); } -const void *table_get(Table *t, TableCell *cell) { +const void* table_get(Table *t, TableCell *cell) { TableData *d; assert(t); @@ -2911,6 +2803,12 @@ static int table_data_to_json(TableData *d, sd_json_variant **ret) { case TABLE_BOOLEAN: return sd_json_variant_new_boolean(ret, d->boolean); + case TABLE_TRISTATE: + if (d->tristate < 0) + return sd_json_variant_new_null(ret); + + return sd_json_variant_new_boolean(ret, d->tristate); + case TABLE_TIMESTAMP: case TABLE_TIMESTAMP_UTC: case TABLE_TIMESTAMP_RELATIVE: @@ -3124,7 +3022,7 @@ static int table_make_json_field_name(Table *t, TableData *d, char **ret) { return 0; } -static const char *table_get_json_field_name(Table *t, size_t idx) { +static const char* table_get_json_field_name(Table *t, size_t idx) { assert(t); return idx < t->n_json_fields ? t->json_fields[idx] : NULL; @@ -3292,7 +3190,7 @@ int table_print_json(Table *t, FILE *f, sd_json_format_flags_t flags) { assert(t); if (!sd_json_format_enabled(flags)) /* If JSON output is turned off, use regular output */ - return table_print(t, f); + return table_print_full(t, f, /* flush= */ true); if (!f) f = stdout; diff --git a/src/shared/format-table.h b/src/shared/format-table.h index ec9989c54c88a..5b98d49017524 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -20,6 +20,7 @@ typedef enum TableDataType { TABLE_VERSION, /* just like TABLE_STRING, but uses version comparison when sorting */ TABLE_BOOLEAN, TABLE_BOOLEAN_CHECKMARK, + TABLE_TRISTATE, TABLE_TIMESTAMP, TABLE_TIMESTAMP_UTC, TABLE_TIMESTAMP_RELATIVE, @@ -93,13 +94,14 @@ typedef enum TableErsatz { typedef struct Table Table; typedef struct TableCell TableCell; -Table *table_new_internal(const char *first_header, ...) _sentinel_; +Table* table_new_internal(const char *first_header, ...) _sentinel_; #define table_new(...) table_new_internal(__VA_ARGS__, NULL) -Table *table_new_raw(size_t n_columns); -Table *table_new_vertical(void); -Table *table_unref(Table *t); +Table* table_new_raw(size_t n_columns); +Table* table_new_vertical(void); +Table* table_unref(Table *t); DEFINE_TRIVIAL_CLEANUP_FUNC(Table*, table_unref); +static inline DEFINE_ARRAY_DONE_FUNC(Table*, table_unref); int table_add_cell_full(Table *t, TableCell **ret_cell, TableDataType dt, const void *data, size_t minimum_width, size_t maximum_width, unsigned weight, unsigned align_percent, unsigned ellipsize_percent); static inline int table_add_cell(Table *t, TableCell **ret_cell, TableDataType dt, const void *data) { @@ -141,7 +143,18 @@ int table_set_reverse(Table *t, size_t column, bool b); int table_hide_column_from_display_internal(Table *t, ...); #define table_hide_column_from_display(t, ...) table_hide_column_from_display_internal(t, __VA_ARGS__, SIZE_MAX) -int table_print(Table *t, FILE *f); +int table_data_requested_width(Table *table, size_t column, size_t *ret); + +int table_set_column_width(Table *t, size_t column, size_t width); +int _table_sync_column_widths(size_t column, Table *a, ...); +#define table_sync_column_widths(column, a, ...) _table_sync_column_widths(column, a, __VA_ARGS__, NULL) + +int table_print_full(Table *t, FILE *f, bool flush); +static inline int table_print(Table *t) { + return table_print_full(t, /* f= */ NULL, /* flush= */ false); +} +int table_print_or_warn(Table *t); + int table_format(Table *t, char **ret); static inline TableCell* TABLE_HEADER_CELL(size_t i) { @@ -159,10 +172,10 @@ size_t table_get_columns(Table *t); size_t table_get_current_column(Table *t); -TableCell *table_get_cell(Table *t, size_t row, size_t column); +TableCell* table_get_cell(Table *t, size_t row, size_t column); -const void *table_get(Table *t, TableCell *cell); -const void *table_get_at(Table *t, size_t row, size_t column); +const void* table_get(Table *t, TableCell *cell); +const void* table_get_at(Table *t, size_t row, size_t column); int table_to_json(Table *t, sd_json_variant **ret); int table_print_json(Table *t, FILE *f, sd_json_format_flags_t json_flags); diff --git a/src/shared/generator.c b/src/shared/generator.c index 603e969436e9b..fd527b3e6d65a 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -490,9 +490,8 @@ int generator_write_network_device_deps( assert(dir); assert(what); - assert(where); - if (fstab_is_extrinsic(where, opts)) + if (where && fstab_is_extrinsic(where, opts)) return 0; if (!fstab_test_option(opts, "_netdev\0")) diff --git a/src/shared/gnutls-util.c b/src/shared/gnutls-util.c new file mode 100644 index 0000000000000..1f17c4f5a1693 --- /dev/null +++ b/src/shared/gnutls-util.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dlopen.h" + +#include "gnutls-util.h" +#include "log.h" /* IWYU pragma: keep */ + +#if HAVE_GNUTLS +static void *gnutls_dl = NULL; + +DLSYM_PROTOTYPE(gnutls_certificate_get_peers) = NULL; +DLSYM_PROTOTYPE(gnutls_certificate_type_get) = NULL; +DLSYM_PROTOTYPE(gnutls_certificate_verification_status_print) = NULL; +DLSYM_PROTOTYPE(gnutls_certificate_verify_peers2) = NULL; +DLSYM_PROTOTYPE(gnutls_free) = NULL; +DLSYM_PROTOTYPE(gnutls_global_set_log_function) = NULL; +DLSYM_PROTOTYPE(gnutls_global_set_log_level) = NULL; +DLSYM_PROTOTYPE(gnutls_x509_crt_deinit) = NULL; +DLSYM_PROTOTYPE(gnutls_x509_crt_get_dn) = NULL; +DLSYM_PROTOTYPE(gnutls_x509_crt_import) = NULL; +DLSYM_PROTOTYPE(gnutls_x509_crt_init) = NULL; +#endif + +int dlopen_gnutls(int log_level) { +#if HAVE_GNUTLS + SD_ELF_NOTE_DLOPEN( + "gnutls", + "Support for TLS via GnuTLS", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libgnutls.so.30"); + + return dlopen_many_sym_or_warn( + &gnutls_dl, + "libgnutls.so.30", + log_level, + DLSYM_ARG(gnutls_certificate_get_peers), + DLSYM_ARG(gnutls_certificate_type_get), + DLSYM_ARG(gnutls_certificate_verification_status_print), + DLSYM_ARG(gnutls_certificate_verify_peers2), + DLSYM_ARG(gnutls_free), + DLSYM_ARG(gnutls_global_set_log_function), + DLSYM_ARG(gnutls_global_set_log_level), + DLSYM_ARG(gnutls_x509_crt_deinit), + DLSYM_ARG(gnutls_x509_crt_get_dn), + DLSYM_ARG(gnutls_x509_crt_import), + DLSYM_ARG(gnutls_x509_crt_init)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "gnutls support is not compiled in."); +#endif +} diff --git a/src/shared/gnutls-util.h b/src/shared/gnutls-util.h new file mode 100644 index 0000000000000..a110b437c3823 --- /dev/null +++ b/src/shared/gnutls-util.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +int dlopen_gnutls(int log_level); + +#if HAVE_GNUTLS +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ + +/* gnutls.h installs a function-like macro that wraps gnutls_free() and NULLs the passed pointer. We use + * dlsym to resolve the underlying function pointer variable, so undef the macro here to keep the variable + * name visible for DLSYM_PROTOTYPE/DLSYM_ARG. */ +# ifdef gnutls_free +# undef gnutls_free +# endif + +# include "dlfcn-util.h" + +extern DLSYM_PROTOTYPE(gnutls_certificate_get_peers); +extern DLSYM_PROTOTYPE(gnutls_certificate_type_get); +extern DLSYM_PROTOTYPE(gnutls_certificate_verification_status_print); +extern DLSYM_PROTOTYPE(gnutls_certificate_verify_peers2); +extern DLSYM_PROTOTYPE(gnutls_free); +extern DLSYM_PROTOTYPE(gnutls_global_set_log_function); +extern DLSYM_PROTOTYPE(gnutls_global_set_log_level); +extern DLSYM_PROTOTYPE(gnutls_x509_crt_deinit); +extern DLSYM_PROTOTYPE(gnutls_x509_crt_get_dn); +extern DLSYM_PROTOTYPE(gnutls_x509_crt_import); +extern DLSYM_PROTOTYPE(gnutls_x509_crt_init); +#endif diff --git a/src/shared/gpt.c b/src/shared/gpt.c index 9308159ebe9f0..d6f264fbe980b 100644 --- a/src/shared/gpt.c +++ b/src/shared/gpt.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "alloc-util.h" #include "gpt.h" #include "string-table.h" @@ -391,3 +393,86 @@ bool gpt_header_has_signature(const GptHeader *p) { return true; } + +ssize_t gpt_probe( + int fd, + GptHeader *ret_header, + void **ret_entries, + uint32_t *ret_n_entries, + uint32_t *ret_entry_size) { + + assert(fd >= 0); + + /* Disk images might be for 512B or for 4096 sector sizes, let's try to auto-detect that by searching + * for the GPT headers at the relevant byte offsets. */ + + assert_cc(sizeof(GptHeader) == 92); + + /* We expect a sector size in the range 512…4096. The GPT header is located in the second + * sector. Hence it could be at byte 512 at the earliest, and at byte 4096 at the latest. And we must + * read with granularity of the largest sector size we care about. Which means 8K. */ + uint8_t sectors[2 * 4096]; + + ssize_t n = pread(fd, sectors, sizeof(sectors), 0); + if (n < 0) + return -errno; + if ((size_t) n < sizeof(sectors)) + return 0; /* too short */ + + /* Let's see if we find the GPT partition header with various expected sector sizes */ + uint32_t found = 0; + for (uint32_t sz = 512; sz <= 4096; sz <<= 1) { + const GptHeader *p = (const GptHeader *) (sectors + sz); + + if (!gpt_header_has_signature(p)) + continue; + + if (found != 0) + return -ENOTUNIQ; + + found = sz; + } + + if (found == 0) + return 0; + + const GptHeader *h = (const GptHeader *) (sectors + found); + + uint32_t entry_sz = le32toh(h->size_of_partition_entry); + uint32_t entry_count = le32toh(h->number_of_partition_entries); + + if (ret_entries) { + uint64_t entry_lba = le64toh(h->partition_entry_lba); + if (entry_lba > (uint64_t) INT64_MAX / found) + return -EBADMSG; + + uint64_t entry_offset = entry_lba * found; + + if (entry_sz < sizeof(GptPartitionEntry) || entry_count == 0 || entry_count > 1024) + return -EBADMSG; + if (entry_sz > SIZE_MAX / entry_count) + return -EBADMSG; + + size_t entries_size = (size_t) entry_sz * entry_count; + _cleanup_free_ void *entries = malloc(entries_size); + if (!entries) + return -ENOMEM; + + n = pread(fd, entries, entries_size, entry_offset); + if (n < 0) + return -errno; + if ((size_t) n < entries_size) + return -EBADMSG; + + *ret_entries = TAKE_PTR(entries); + } + + if (ret_header) + *ret_header = *h; + if (ret_n_entries) + *ret_n_entries = entry_count; + if (ret_entry_size) + *ret_entry_size = entry_sz; + + return found; /* sector size */ +} diff --git a/src/shared/gpt.h b/src/shared/gpt.h index f59f2da29fdcb..18d665441c679 100644 --- a/src/shared/gpt.h +++ b/src/shared/gpt.h @@ -113,3 +113,10 @@ typedef struct { } _packed_ GptHeader; bool gpt_header_has_signature(const GptHeader *p) _pure_; + +ssize_t gpt_probe( + int fd, + GptHeader *ret_header, + void **ret_entries, + uint32_t *ret_n_entries, + uint32_t *ret_entry_size); diff --git a/src/shared/help-util.c b/src/shared/help-util.c new file mode 100644 index 0000000000000..7e9d3e70be0c6 --- /dev/null +++ b/src/shared/help-util.c @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "ansi-color.h" +#include "help-util.h" +#include "pretty-print.h" + +/* These are helpers for putting together --help texts in a uniform way with a common output style. Each + * function generates a separate part of the --help text: + * + * 1. help_cmdline() outputs a brief summary of the command line syntax. This shall be used at least once, + * in some cases multiple times. This generally comes first in the output. + * + * 2. help_abstract() outputs a brief prose abstract of the command, should carry a single line of text + * that gives the user a hint what this tool does. Use only once, right after the last help_cmdline(). + * + * 3. help_section() can be used to format multiple sections of the --help text. It should be used at least + * once for an "Options:" section, but can be used more than once, for programs with many + * options/verbs. The first invocation should come right after help_abstract(). + * + * 4. Finally, help_man_page_reference() adds a final line linking the man page of the tool. This should be + * used only once, and terminates the --help text. + * + * Switches and verbs documentation should be inserted after each help_section(). For that ideally use + * options.[ch] APIs. */ + +void help_cmdline(const char *arguments) { + assert(arguments); + + printf("%s>%s %s %s\n", + ansi_grey(), + ansi_normal(), + program_invocation_short_name, + arguments); +} + +void help_abstract(const char *text) { + assert(text); + + printf("\n%s%s%s%s\n", + ansi_highlight(), + ansi_add_italics(), + text, + ansi_normal()); +} + +void help_section(const char *title) { + assert(title); + + printf("\n%s%s%s\n", + ansi_underline(), + title, + ansi_normal()); +} + +void help_man_page_reference(const char *page, const char *section) { + assert(page); + assert(section); + + /* Displaying --help texts generally should not fail, hence let's fall back to a simple string in + * case of OOM. */ + _cleanup_free_ char *link = NULL; + if (terminal_urlify_man(page, section, &link) < 0) + printf("\nSee the %s(%s) man page for details.\n", page, section); + else + printf("\nSee the %s for details.\n", link); +} diff --git a/src/shared/help-util.h b/src/shared/help-util.h new file mode 100644 index 0000000000000..380487213ff3f --- /dev/null +++ b/src/shared/help-util.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +void help_cmdline(const char *arguments); + +void help_abstract(const char *text); + +void help_section(const char *title); + +void help_man_page_reference(const char *page, const char *section); diff --git a/src/shared/hibernate-util.c b/src/shared/hibernate-util.c index 001eaa920cc99..f9e095f27384d 100644 --- a/src/shared/hibernate-util.c +++ b/src/shared/hibernate-util.c @@ -37,14 +37,16 @@ int read_fiemap(int fd, struct fiemap **ret) { uint32_t result_extents = 0; uint64_t fiemap_start = 0, fiemap_length; const size_t n_extra = DIV_ROUND_UP(sizeof(struct fiemap), sizeof(struct fiemap_extent)); + int r; assert(fd >= 0); assert(ret); if (fstat(fd, &statinfo) < 0) return log_debug_errno(errno, "Cannot determine file size: %m"); - if (!S_ISREG(statinfo.st_mode)) - return -ENOTTY; + r = stat_verify_regular(&statinfo); + if (r < 0) + return r; fiemap_length = statinfo.st_size; /* Zero this out in case we run on a file with no extents */ @@ -209,8 +211,9 @@ static int swap_entry_get_resume_config(SwapEntry *swap) { return -errno; if (!swap->swapfile) { - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; + r = stat_verify_block(&st); + if (r < 0) + return r; swap->devno = st.st_rdev; swap->offset = 0; diff --git a/src/shared/hostname-setup.c b/src/shared/hostname-setup.c index 50a0f077fa7bf..ee88e2a877b77 100644 --- a/src/shared/hostname-setup.c +++ b/src/shared/hostname-setup.c @@ -284,8 +284,8 @@ int hostname_substitute_wildcards(char *name) { struct siphash state; siphash24_init(&state, key.bytes); - siphash24_compress(&mid, sizeof(mid), &state); - siphash24_compress(&counter, sizeof(counter), &state); /* counter mode */ + siphash24_compress_typesafe(mid, &state); + siphash24_compress_typesafe(counter, &state); /* counter mode */ h = siphash24_finalize(&state); left_bits = sizeof(h) * 8; counter++; diff --git a/src/shared/hwdb-util.c b/src/shared/hwdb-util.c index 29942254f37d7..46c3f26a9b807 100644 --- a/src/shared/hwdb-util.c +++ b/src/shared/hwdb-util.c @@ -190,6 +190,8 @@ static int trie_insert(struct trie *trie, struct trie_node *node, const char *se const char *filename, uint16_t file_priority, uint32_t line_number, bool compat) { int r = 0; + assert(node); + for (size_t i = 0;; i++) { size_t p; char c; @@ -608,7 +610,7 @@ int hwdb_update(const char *root, const char *hwdb_bin_dir, bool strict, bool co ConfFile **files = NULL; size_t n_files = 0; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); r = conf_files_list_strv_full(".hwdb", root, CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED | CONF_FILES_WARN, diff --git a/src/shared/idn-util.c b/src/shared/idn-util.c index aad0db1426c53..01512b95dda41 100644 --- a/src/shared/idn-util.c +++ b/src/shared/idn-util.c @@ -3,21 +3,33 @@ #include "idn-util.h" #include "log.h" /* IWYU pragma: keep */ +#if HAVE_LIBIDN2 + +#include "sd-dlopen.h" + static void* idn_dl = NULL; DLSYM_PROTOTYPE(idn2_lookup_u8) = NULL; const char *(*sym_idn2_strerror)(int rc) _const_ = NULL; DLSYM_PROTOTYPE(idn2_to_unicode_8z8z) = NULL; -int dlopen_idn(void) { - ELF_NOTE_DLOPEN("idn", +#endif + +int dlopen_idn(int log_level) { +#if HAVE_LIBIDN2 + SD_ELF_NOTE_DLOPEN( + "idn", "Support for internationalized domain names", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libidn2.so.0"); return dlopen_many_sym_or_warn( - &idn_dl, "libidn2.so.0", LOG_DEBUG, + &idn_dl, "libidn2.so.0", log_level, DLSYM_ARG(idn2_lookup_u8), DLSYM_ARG(idn2_strerror), DLSYM_ARG(idn2_to_unicode_8z8z)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libidn2 support is not compiled in."); +#endif } diff --git a/src/shared/idn-util.h b/src/shared/idn-util.h index c971be9aefebe..918f8c0a57fbd 100644 --- a/src/shared/idn-util.h +++ b/src/shared/idn-util.h @@ -11,10 +11,6 @@ extern DLSYM_PROTOTYPE(idn2_lookup_u8); extern const char *(*sym_idn2_strerror)(int rc) _const_; extern DLSYM_PROTOTYPE(idn2_to_unicode_8z8z); - -int dlopen_idn(void); -#else -static inline int dlopen_idn(void) { - return -EOPNOTSUPP; -} #endif + +int dlopen_idn(int log_level); diff --git a/src/shared/import-util.c b/src/shared/import-util.c index 35079a653786c..8bc90003b2c5e 100644 --- a/src/shared/import-util.c +++ b/src/shared/import-util.c @@ -142,6 +142,8 @@ int tar_strip_suffixes(const char *name, char **ret) { const char *e; char *s; + assert(ret); + e = endswith(name, ".tar"); if (!e) e = endswith(name, ".tar.xz"); @@ -183,6 +185,8 @@ int raw_strip_suffixes(const char *name, char **ret) { _cleanup_free_ char *q = NULL; + assert(ret); + q = strdup(name); if (!q) return -ENOMEM; diff --git a/src/shared/install-printf.c b/src/shared/install-printf.c index 81a5e4a73e6e3..db93e90b27fa2 100644 --- a/src/shared/install-printf.c +++ b/src/shared/install-printf.c @@ -12,6 +12,8 @@ static int specifier_prefix_and_instance(char specifier, const void *data, const _cleanup_free_ char *prefix = NULL; int r; + assert(ret); + r = unit_name_to_prefix_and_instance(i->name, &prefix); if (r < 0) return r; @@ -49,6 +51,8 @@ static int specifier_instance(char specifier, const void *data, const char *root char *instance; int r; + assert(ret); + r = unit_name_to_instance(i->name, &instance); if (r < 0) return r; diff --git a/src/shared/install.c b/src/shared/install.c index d3e5a1fb5ae66..d01906c205d9d 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -319,17 +319,15 @@ InstallChangeType install_changes_add( return type; } -void install_changes_free(InstallChange *changes, size_t n_changes) { - assert(changes || n_changes == 0); - - FOREACH_ARRAY(i, changes, n_changes) { - free(i->path); - free(i->source); - } +static void install_change_done(InstallChange *change) { + assert(change); - free(changes); + change->path = mfree(change->path); + change->source = mfree(change->source); } +DEFINE_ARRAY_FREE_FUNC(install_changes_free, InstallChange, install_change_done); + static void install_change_dump_success(const InstallChange *change) { assert(change); assert(change->path); @@ -646,6 +644,7 @@ static int mark_symlink_for_removal( char *n; int r; + assert(remove_symlinks_to); assert(p); r = set_ensure_allocated(remove_symlinks_to, &path_hash_ops_free); @@ -1037,6 +1036,7 @@ static int find_symlinks_in_scope( assert(lp); assert(info); + assert(state); /* As we iterate over the list of search paths in lp->search_path, we may encounter "same name" * symlinks. The ones which are "below" (i.e. have lower priority) than the unit file itself are @@ -1842,6 +1842,8 @@ static int install_info_discover_and_check( int r; + POINTER_MAY_BE_NULL(ret); + r = install_info_discover(ctx, lp, name_or_path, flags, ret, changes, n_changes); if (r < 0) return r; @@ -1859,6 +1861,8 @@ int unit_file_verify_alias( _cleanup_free_ char *dst_updated = NULL; int r; + assert(ret_dst); + /* Verify that dst is a valid either a valid alias or a valid .wants/.requires symlink for the target * unit *i. Return negative on error or if not compatible, zero on success. * @@ -2901,6 +2905,9 @@ static int do_unit_file_disable( bool has_install_info = false; int r; + assert(changes); + assert(n_changes); + STRV_FOREACH(name, names) { InstallInfo *info; @@ -3499,7 +3506,7 @@ static int pattern_match_multiple_instances( if (r < 0) return r; - if (strv_find(rule.instances, instance_name)) + if (strv_contains(rule.instances, instance_name)) return 1; } return 0; @@ -3510,6 +3517,7 @@ static int query_presets(const char *name, const UnitFilePresets *presets, char assert(name); assert(presets); + POINTER_MAY_BE_NULL(instance_name_list); if (!unit_name_is_valid(name, UNIT_NAME_ANY)) return -EINVAL; diff --git a/src/shared/install.h b/src/shared/install.h index ad5803e32168e..f184b7191cad7 100644 --- a/src/shared/install.h +++ b/src/shared/install.h @@ -202,7 +202,7 @@ static inline int unit_file_exists(RuntimeScope scope, const LookupPaths *lp, co int unit_file_get_list(RuntimeScope scope, const char *root_dir, char * const *states, char * const *patterns, Hashmap **ret); InstallChangeType install_changes_add(InstallChange **changes, size_t *n_changes, InstallChangeType type, const char *path, const char *source); -void install_changes_free(InstallChange *changes, size_t n_changes); +void install_changes_free(InstallChange *array, size_t n); int install_change_dump_error(const InstallChange *change, char **ret_errmsg, const char **ret_bus_error); int install_changes_dump( diff --git a/src/shared/journal-importer.c b/src/shared/journal-importer.c index 3aa472a2d1cf6..87286a33b288c 100644 --- a/src/shared/journal-importer.c +++ b/src/shared/journal-importer.c @@ -50,6 +50,8 @@ static int get_line(JournalImporter *imp, char **line, size_t *size) { char *c = NULL; assert(imp); + assert(line); + assert(size); assert(imp->state == IMPORTER_STATE_LINE); assert(imp->offset <= imp->filled); assert(imp->filled <= MALLOC_SIZEOF_SAFE(imp->buf)); diff --git a/src/shared/journal-importer.h b/src/shared/journal-importer.h index f218d80dfd938..21de703781b89 100644 --- a/src/shared/journal-importer.h +++ b/src/shared/journal-importer.h @@ -8,22 +8,8 @@ #include "iovec-wrapper.h" #include "time-util.h" -/* Make sure not to make this smaller than the maximum coredump size. - * See JOURNAL_SIZE_MAX in coredump.c */ -#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -#define ENTRY_SIZE_MAX (1024*1024*770u) -#define ENTRY_SIZE_UNPRIV_MAX (1024*1024*32u) -#define DATA_SIZE_MAX (1024*1024*768u) -#else -#define ENTRY_SIZE_MAX (1024*1024*13u) -#define ENTRY_SIZE_UNPRIV_MAX (1024*1024*8u) -#define DATA_SIZE_MAX (1024*1024*11u) -#endif #define LINE_CHUNK 8*1024u -/* The maximum number of fields in an entry */ -#define ENTRY_FIELD_COUNT_MAX 1024u - typedef struct JournalImporter { int fd; bool passive_fd; diff --git a/src/shared/kbd-util.c b/src/shared/kbd-util.c index 84031df784354..28ea2e8612e5d 100644 --- a/src/shared/kbd-util.c +++ b/src/shared/kbd-util.c @@ -5,12 +5,10 @@ #include "errno-util.h" #include "kbd-util.h" #include "log.h" -#include "path-util.h" #include "recurse-dir.h" #include "set.h" #include "string-util.h" #include "strv.h" -#include "utf8.h" #define KBD_KEYMAP_DIRS \ "/usr/share/keymaps/", \ @@ -129,21 +127,12 @@ int get_keymaps(char ***ret) { } bool keymap_is_valid(const char *name) { - if (isempty(name)) + if (!string_is_safe(name, STRING_FILENAME)) return false; if (strlen(name) >= 128) return false; - if (!utf8_is_valid(name)) - return false; - - if (!filename_is_valid(name)) - return false; - - if (!string_is_safe(name)) - return false; - return true; } diff --git a/src/shared/kernel-image.c b/src/shared/kernel-image.c index d3e18d19f41a8..e2db555cb4491 100644 --- a/src/shared/kernel-image.c +++ b/src/shared/kernel-image.c @@ -28,7 +28,6 @@ static int uki_read_pretty_name( char **ret) { _cleanup_free_ char *pname = NULL, *name = NULL; - _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ void *osrel = NULL; size_t osrel_size; int r; @@ -51,16 +50,12 @@ static int uki_read_pretty_name( return 0; } - f = fmemopen(osrel, osrel_size, "r"); - if (!f) - return log_error_errno(errno, "Failed to open embedded os-release file: %m"); - - r = parse_env_file( - f, NULL, + r = parse_env_data( + osrel, osrel_size, ".osrel", "PRETTY_NAME", &pname, "NAME", &name); if (r < 0) - return log_error_errno(r, "Failed to parse embedded os-release file: %m"); + return log_debug_errno(r, "Failed to parse embedded os-release file: %m"); /* follow the same logic as os_release_pretty_name() */ if (!isempty(pname)) @@ -70,7 +65,7 @@ static int uki_read_pretty_name( else { char *n = strdup("Linux"); if (!n) - return log_oom(); + return -ENOMEM; *ret = n; } @@ -120,7 +115,7 @@ static int inspect_uki( return 0; } -int inspect_kernel( +int inspect_kernel_full( int dir_fd, const char *filename, KernelImageType *ret_type, @@ -136,23 +131,22 @@ int inspect_kernel( int r; assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); - assert(filename); fd = xopenat(dir_fd, filename, O_RDONLY|O_CLOEXEC); if (fd < 0) - return log_error_errno(fd, "Failed to open kernel image file '%s': %m", filename); + return log_debug_errno(fd, "Failed to open kernel image file '%s': %m", strna(filename)); r = pe_load_headers(fd, &dos_header, &pe_header); if (r == -EBADMSG) /* not a valid PE file */ goto not_uki; if (r < 0) - return log_error_errno(r, "Failed to parse kernel image file '%s': %m", filename); + return log_debug_errno(r, "Failed to parse kernel image file '%s': %m", strna(filename)); r = pe_load_sections(fd, dos_header, pe_header, §ions); if (r == -EBADMSG) /* not a valid PE file */ goto not_uki; if (r < 0) - return log_error_errno(r, "Failed to load PE sections from kernel image file '%s': %m", filename); + return log_debug_errno(r, "Failed to load PE sections from kernel image file '%s': %m", strna(filename)); if (pe_is_uki(pe_header, sections)) { r = inspect_uki(fd, pe_header, sections, ret_cmdline, ret_uname, ret_pretty_name); diff --git a/src/shared/kernel-image.h b/src/shared/kernel-image.h index 85a3308986c5c..c0b8847cafb54 100644 --- a/src/shared/kernel-image.h +++ b/src/shared/kernel-image.h @@ -14,10 +14,17 @@ typedef enum KernelImageType { DECLARE_STRING_TABLE_LOOKUP_TO_STRING(kernel_image_type, KernelImageType); -int inspect_kernel( +int inspect_kernel_full( int dir_fd, const char *filename, KernelImageType *ret_type, char **ret_cmdline, char **ret_uname, char **ret_pretty_name); + +static inline int inspect_kernel( + int dir_fd, + const char *filename, + KernelImageType *ret_type) { + return inspect_kernel_full(dir_fd, filename, ret_type, NULL, NULL, NULL); +} diff --git a/src/shared/libarchive-util.c b/src/shared/libarchive-util.c index d2714bd81c183..2ef0b37959b93 100644 --- a/src/shared/libarchive-util.c +++ b/src/shared/libarchive-util.c @@ -2,7 +2,10 @@ #include +#include "sd-dlopen.h" + #include "libarchive-util.h" +#include "log.h" /* IWYU pragma: keep */ #include "user-util.h" /* IWYU pragma: keep */ #if HAVE_LIBARCHIVE @@ -77,17 +80,20 @@ DLSYM_PROTOTYPE(archive_write_open_FILE) = NULL; DLSYM_PROTOTYPE(archive_write_open_fd) = NULL; DLSYM_PROTOTYPE(archive_write_set_format_filter_by_ext) = NULL; DLSYM_PROTOTYPE(archive_write_set_format_pax) = NULL; +#endif -int dlopen_libarchive(void) { - ELF_NOTE_DLOPEN("archive", +int dlopen_libarchive(int log_level) { +#if HAVE_LIBARCHIVE + SD_ELF_NOTE_DLOPEN( + "archive", "Support for decompressing archive files", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libarchive.so.13"); return dlopen_many_sym_or_warn( &libarchive_dl, "libarchive.so.13", - LOG_DEBUG, + log_level, DLSYM_ARG(archive_entry_acl_add_entry), DLSYM_ARG(archive_entry_acl_next), DLSYM_ARG(archive_entry_acl_reset), @@ -149,8 +155,13 @@ int dlopen_libarchive(void) { DLSYM_ARG(archive_write_open_fd), DLSYM_ARG(archive_write_set_format_filter_by_ext), DLSYM_ARG(archive_write_set_format_pax)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libarchive support is not compiled in."); +#endif } +#if HAVE_LIBARCHIVE /* libarchive uses its own file type macros. They happen to be defined the same way as the Linux ones, and * we'd like to rely on it. Let's verify this first though. */ assert_cc(S_IFDIR == AE_IFDIR); diff --git a/src/shared/libarchive-util.h b/src/shared/libarchive-util.h index db1c31d2239f7..f1f78aad5d1eb 100644 --- a/src/shared/libarchive-util.h +++ b/src/shared/libarchive-util.h @@ -79,16 +79,13 @@ extern DLSYM_PROTOTYPE(archive_write_open_fd); extern DLSYM_PROTOTYPE(archive_write_set_format_filter_by_ext); extern DLSYM_PROTOTYPE(archive_write_set_format_pax); -int dlopen_libarchive(void); - DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct archive_entry*, sym_archive_entry_free, archive_entry_freep, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct archive*, sym_archive_write_free, archive_write_freep, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct archive*, sym_archive_read_free, archive_read_freep, NULL); #else -static inline int dlopen_libarchive(void) { - return -EOPNOTSUPP; -} #endif + +int dlopen_libarchive(int log_level); diff --git a/src/shared/libaudit-util.c b/src/shared/libaudit-util.c index 4bdd4c1317edf..478d3d33b6518 100644 --- a/src/shared/libaudit-util.c +++ b/src/shared/libaudit-util.c @@ -4,6 +4,8 @@ #include #include +#include "sd-dlopen.h" + #include "errno-util.h" #include "fd-util.h" #include "iovec-util.h" @@ -21,24 +23,26 @@ DLSYM_PROTOTYPE(audit_log_user_comm_message) = NULL; static DLSYM_PROTOTYPE(audit_open) = NULL; #endif -int dlopen_libaudit(void) { +int dlopen_libaudit(int log_level) { #if HAVE_AUDIT - ELF_NOTE_DLOPEN("audit", + SD_ELF_NOTE_DLOPEN( + "audit", "Support for Audit logging", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "libaudit.so.1"); return dlopen_many_sym_or_warn( &libaudit_dl, "libaudit.so.1", - LOG_DEBUG, + log_level, DLSYM_ARG(audit_close), DLSYM_ARG(audit_log_acct_message), DLSYM_ARG(audit_log_user_avc_message), DLSYM_ARG(audit_log_user_comm_message), DLSYM_ARG(audit_open)); #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libaudit support is not compiled in."); #endif } @@ -90,7 +94,7 @@ bool use_audit(void) { if (cached_use >= 0) return cached_use; - if (dlopen_libaudit() < 0) + if (dlopen_libaudit(LOG_DEBUG) < 0) return (cached_use = false); _cleanup_close_ int fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT); @@ -138,7 +142,7 @@ int open_audit_fd_or_warn(void) { #if HAVE_AUDIT int r; - r = dlopen_libaudit(); + r = dlopen_libaudit(LOG_DEBUG); if (r < 0) return r; diff --git a/src/shared/libaudit-util.h b/src/shared/libaudit-util.h index 759b1c757497a..8bb05a2fbd3c1 100644 --- a/src/shared/libaudit-util.h +++ b/src/shared/libaudit-util.h @@ -3,7 +3,7 @@ #include "shared-forward.h" -int dlopen_libaudit(void); +int dlopen_libaudit(int log_level); #if HAVE_AUDIT # include /* IWYU pragma: export */ diff --git a/src/shared/libcrypt-util.c b/src/shared/libcrypt-util.c index 85069314be497..531684d64ef64 100644 --- a/src/shared/libcrypt-util.c +++ b/src/shared/libcrypt-util.c @@ -4,6 +4,8 @@ # include #endif +#include "sd-dlopen.h" + #include "alloc-util.h" #include "dlfcn-util.h" #include "errno-util.h" @@ -19,55 +21,6 @@ static DLSYM_PROTOTYPE(crypt_gensalt_ra) = NULL; static DLSYM_PROTOTYPE(crypt_preferred_method) = NULL; static DLSYM_PROTOTYPE(crypt_ra) = NULL; -int dlopen_libcrypt(void) { -#ifdef __GLIBC__ - static int cached = 0; - int r; - - if (libcrypt_dl) - return 0; /* Already loaded */ - - if (cached < 0) - return cached; /* Already tried, and failed. */ - - /* Several distributions like Debian/Ubuntu and OpenSUSE provide libxcrypt as libcrypt.so.1, - * while others like Fedora/CentOS and Arch provide it as libcrypt.so.2. */ - ELF_NOTE_DLOPEN("crypt", - "Support for hashing passwords", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libcrypt.so.2", "libcrypt.so.1"); - - _cleanup_(dlclosep) void *dl = NULL; - r = dlopen_safe("libcrypt.so.2", &dl, /* reterr_dlerror= */ NULL); - if (r < 0) { - const char *dle = NULL; - r = dlopen_safe("libcrypt.so.1", &dl, &dle); - if (r < 0) { - log_debug_errno(r, "libcrypt.so.2/libcrypt.so.1 is not available: %s", dle ?: STRERROR(r)); - return (cached = -EOPNOTSUPP); /* turn into recognizable error */ - } - log_debug("Loaded 'libcrypt.so.1' via dlopen()"); - } else - log_debug("Loaded 'libcrypt.so.2' via dlopen()"); - - r = dlsym_many_or_warn( - dl, LOG_DEBUG, - DLSYM_ARG(crypt_gensalt_ra), - DLSYM_ARG(crypt_preferred_method), - DLSYM_ARG(crypt_ra)); - if (r < 0) - return (cached = r); - - libcrypt_dl = TAKE_PTR(dl); -#else - libcrypt_dl = NULL; - sym_crypt_gensalt_ra = missing_crypt_gensalt_ra; - sym_crypt_preferred_method = missing_crypt_preferred_method; - sym_crypt_ra = missing_crypt_ra; -#endif - return 0; -} - int make_salt(char **ret) { const char *e; char *salt; @@ -75,7 +28,7 @@ int make_salt(char **ret) { assert(ret); - r = dlopen_libcrypt(); + r = dlopen_libcrypt(LOG_DEBUG); if (r < 0) return r; @@ -122,7 +75,7 @@ int test_password_one(const char *hashed_password, const char *password) { assert(hashed_password); assert(password); - r = dlopen_libcrypt(); + r = dlopen_libcrypt(LOG_DEBUG); if (r < 0) return r; @@ -170,3 +123,60 @@ bool looks_like_hashed_password(const char *s) { return !STR_IN_SET(s, "x", "*"); } + +int dlopen_libcrypt(int log_level) { +#if HAVE_LIBCRYPT +#ifdef __GLIBC__ + static int cached = 0; + int r; + + if (libcrypt_dl) + return 0; /* Already loaded */ + + if (cached < 0) + return cached; /* Already tried, and failed. */ + + /* Several distributions like Debian/Ubuntu and OpenSUSE provide libxcrypt as libcrypt.so.1 + * (libcrypt.so.1.1 on some architectures), while others like Fedora/CentOS and Arch provide it as + * libcrypt.so.2. */ + SD_ELF_NOTE_DLOPEN( + "crypt", + "Support for hashing passwords", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libcrypt.so.2", "libcrypt.so.1", "libcrypt.so.1.1"); + + _cleanup_(dlclosep) void *dl = NULL; + const char *dle = NULL; + FOREACH_STRING(soname, "libcrypt.so.2", "libcrypt.so.1", "libcrypt.so.1.1") { + r = dlopen_safe(soname, &dl, &dle); + if (r >= 0) { + log_debug("Loaded '%s' via dlopen().", soname); + break; + } + } + if (r < 0) { + log_full_errno(log_level, r, "Failed to load libcrypt: %s", dle ?: STRERROR(r)); + return (cached = -EOPNOTSUPP); /* turn into recognizable error */ + } + + r = dlsym_many_or_warn( + dl, log_level, + DLSYM_ARG(crypt_gensalt_ra), + DLSYM_ARG(crypt_preferred_method), + DLSYM_ARG(crypt_ra)); + if (r < 0) + return (cached = r); + + libcrypt_dl = TAKE_PTR(dl); +#else + libcrypt_dl = NULL; + sym_crypt_gensalt_ra = missing_crypt_gensalt_ra; + sym_crypt_preferred_method = missing_crypt_preferred_method; + sym_crypt_ra = missing_crypt_ra; +#endif + return 0; +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libcrypt support is not compiled in."); +#endif +} diff --git a/src/shared/libcrypt-util.h b/src/shared/libcrypt-util.h index 3f79916cbc0be..5c469b662cf02 100644 --- a/src/shared/libcrypt-util.h +++ b/src/shared/libcrypt-util.h @@ -4,7 +4,6 @@ #include "shared-forward.h" #if HAVE_LIBCRYPT -int dlopen_libcrypt(void); int make_salt(char **ret); int hash_password(const char *password, char **ret); int test_password_one(const char *hashed_password, const char *password); @@ -12,12 +11,11 @@ int test_password_many(char **hashed_password, const char *password); #else -static inline int dlopen_libcrypt(void) { - return -EOPNOTSUPP; -} static inline int hash_password(const char *password, char **ret) { return -EOPNOTSUPP; } #endif +int dlopen_libcrypt(int log_level); + bool looks_like_hashed_password(const char *s); diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index 338b52881e8ab..cc00006af9d54 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dlopen.h" + #include "libfido2-util.h" #include "log.h" @@ -78,17 +80,18 @@ static void fido_log_propagate_handler(const char *s) { #endif -int dlopen_libfido2(void) { +int dlopen_libfido2(int log_level) { #if HAVE_LIBFIDO2 int r; - ELF_NOTE_DLOPEN("fido2", + SD_ELF_NOTE_DLOPEN( + "fido2", "Support fido2 for encryption and authentication", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libfido2.so.1"); r = dlopen_many_sym_or_warn( - &libfido2_dl, "libfido2.so.1", LOG_DEBUG, + &libfido2_dl, "libfido2.so.1", log_level, DLSYM_ARG(fido_assert_allow_cred), DLSYM_ARG(fido_assert_free), DLSYM_ARG(fido_assert_hmac_secret_len), @@ -145,7 +148,8 @@ int dlopen_libfido2(void) { return 0; #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libfido2 support is not compiled in."); #endif } @@ -655,9 +659,9 @@ int fido2_use_hmac_hash( fido_dev_info_t *di = NULL; int r; - r = dlopen_libfido2(); + r = dlopen_libfido2(LOG_ERR); if (r < 0) - return log_error_errno(r, "FIDO2 support is not installed."); + return r; if (device) { r = fido2_is_cred_in_specific_token(device, rp_id, cid, cid_size, required); @@ -781,9 +785,9 @@ int fido2_generate_hmac_hash( assert((lock_with & ~(FIDO2ENROLL_PIN|FIDO2ENROLL_UP|FIDO2ENROLL_UV)) == 0); assert(iovec_is_set(salt)); - r = dlopen_libfido2(); + r = dlopen_libfido2(LOG_ERR); if (r < 0) - return log_error_errno(r, "FIDO2 token support is not installed."); + return r; d = sym_fido_dev_new(); if (!d) @@ -1150,6 +1154,12 @@ static int check_device_is_fido2_with_hmac_secret( _cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL; int r; + assert(ret_has_rk); + assert(ret_has_client_pin); + assert(ret_has_up); + assert(ret_has_uv); + assert(ret_has_always_uv); + d = sym_fido_dev_new(); if (!d) return log_oom(); @@ -1179,9 +1189,9 @@ int fido2_list_devices(void) { fido_dev_info_t *di = NULL; int r; - r = dlopen_libfido2(); + r = dlopen_libfido2(LOG_ERR); if (r < 0) - return log_error_errno(r, "FIDO2 token support is not installed."); + return r; di = sym_fido_dev_info_new(allocated); if (!di) @@ -1238,13 +1248,11 @@ int fido2_list_devices(void) { } } - r = table_print(t, stdout); - if (r < 0) { - log_error_errno(r, "Failed to show device table: %m"); + r = table_print_or_warn(t); + if (r < 0) goto finish; - } - if (table_get_rows(t) > 1) + if (!table_isempty(t)) printf("\n" "%1$sLegend: RK %2$s Resident key%3$s\n" "%1$s CLIENTPIN %2$s PIN request%3$s\n" @@ -1275,9 +1283,9 @@ int fido2_find_device_auto(char **ret) { const char *path; int r; - r = dlopen_libfido2(); + r = dlopen_libfido2(LOG_ERR); if (r < 0) - return log_error_errno(r, "FIDO2 token support is not installed."); + return r; di = sym_fido_dev_info_new(di_size); if (!di) @@ -1352,9 +1360,9 @@ int fido2_have_device(const char *device) { /* Return == 0 if not devices are found, > 0 if at least one is found */ - r = dlopen_libfido2(); + r = dlopen_libfido2(LOG_ERR); if (r < 0) - return log_error_errno(r, "FIDO2 support is not installed."); + return r; if (device) { if (access(device, F_OK) < 0) { diff --git a/src/shared/libfido2-util.h b/src/shared/libfido2-util.h index c5d3875a0d5b5..4f88100be700e 100644 --- a/src/shared/libfido2-util.h +++ b/src/shared/libfido2-util.h @@ -16,7 +16,7 @@ typedef enum Fido2EnrollFlags { _FIDO2ENROLL_TYPE_INVALID = -EINVAL, } Fido2EnrollFlags; -int dlopen_libfido2(void); +int dlopen_libfido2(int log_level); #if HAVE_LIBFIDO2 #include diff --git a/src/shared/libmount-util.c b/src/shared/libmount-util.c index c6c6074c25953..27e98888d02a8 100644 --- a/src/shared/libmount-util.c +++ b/src/shared/libmount-util.c @@ -1,10 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "libmount-util.h" +#include "log.h" + +#if HAVE_LIBMOUNT + #include +#include "sd-dlopen.h" + #include "fstab-util.h" -#include "libmount-util.h" -#include "log.h" static void *libmount_dl = NULL; @@ -40,49 +45,6 @@ DLSYM_PROTOTYPE(mnt_table_parse_stream) = NULL; DLSYM_PROTOTYPE(mnt_table_parse_swaps) = NULL; DLSYM_PROTOTYPE(mnt_unref_monitor) = NULL; -int dlopen_libmount(void) { - ELF_NOTE_DLOPEN("mount", - "Support for mount enumeration", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libmount.so.1"); - - return dlopen_many_sym_or_warn( - &libmount_dl, - "libmount.so.1", - LOG_DEBUG, - DLSYM_ARG(mnt_free_iter), - DLSYM_ARG(mnt_free_table), - DLSYM_ARG(mnt_fs_get_fs_options), - DLSYM_ARG(mnt_fs_get_fstype), - DLSYM_ARG(mnt_fs_get_id), - DLSYM_ARG(mnt_fs_get_option), - DLSYM_ARG(mnt_fs_get_options), - DLSYM_ARG(mnt_fs_get_passno), - DLSYM_ARG(mnt_fs_get_propagation), - DLSYM_ARG(mnt_fs_get_source), - DLSYM_ARG(mnt_fs_get_target), - DLSYM_ARG(mnt_fs_get_vfs_options), - DLSYM_ARG(mnt_get_builtin_optmap), - DLSYM_ARG(mnt_init_debug), - DLSYM_ARG(mnt_monitor_enable_kernel), - DLSYM_ARG(mnt_monitor_enable_userspace), - DLSYM_ARG(mnt_monitor_get_fd), - DLSYM_ARG(mnt_monitor_next_change), - DLSYM_ARG(mnt_new_iter), - DLSYM_ARG(mnt_new_monitor), - DLSYM_ARG(mnt_new_table), - DLSYM_ARG(mnt_optstr_get_flags), - DLSYM_ARG(mnt_table_find_devno), - DLSYM_ARG(mnt_table_find_target), - DLSYM_ARG(mnt_table_next_child_fs), - DLSYM_ARG(mnt_table_next_fs), - DLSYM_ARG(mnt_table_parse_file), - DLSYM_ARG(mnt_table_parse_mtab), - DLSYM_ARG(mnt_table_parse_stream), - DLSYM_ARG(mnt_table_parse_swaps), - DLSYM_ARG(mnt_unref_monitor)); -} - int libmount_parse_full( const char *path, FILE *source, @@ -97,8 +59,10 @@ int libmount_parse_full( /* Older libmount seems to require this. */ assert(!source || path); assert(IN_SET(direction, MNT_ITER_FORWARD, MNT_ITER_BACKWARD)); + assert(ret_table); + assert(ret_iter); - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0) return r; @@ -151,3 +115,54 @@ int libmount_is_leaf( return r == 1; } + +#endif + +int dlopen_libmount(int log_level) { +#if HAVE_LIBMOUNT + SD_ELF_NOTE_DLOPEN( + "mount", + "Support for mount enumeration", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libmount.so.1"); + + return dlopen_many_sym_or_warn( + &libmount_dl, + "libmount.so.1", + log_level, + DLSYM_ARG(mnt_free_iter), + DLSYM_ARG(mnt_free_table), + DLSYM_ARG(mnt_fs_get_fs_options), + DLSYM_ARG(mnt_fs_get_fstype), + DLSYM_ARG(mnt_fs_get_id), + DLSYM_ARG(mnt_fs_get_option), + DLSYM_ARG(mnt_fs_get_options), + DLSYM_ARG(mnt_fs_get_passno), + DLSYM_ARG(mnt_fs_get_propagation), + DLSYM_ARG(mnt_fs_get_source), + DLSYM_ARG(mnt_fs_get_target), + DLSYM_ARG(mnt_fs_get_vfs_options), + DLSYM_ARG(mnt_get_builtin_optmap), + DLSYM_ARG(mnt_init_debug), + DLSYM_ARG(mnt_monitor_enable_kernel), + DLSYM_ARG(mnt_monitor_enable_userspace), + DLSYM_ARG(mnt_monitor_get_fd), + DLSYM_ARG(mnt_monitor_next_change), + DLSYM_ARG(mnt_new_iter), + DLSYM_ARG(mnt_new_monitor), + DLSYM_ARG(mnt_new_table), + DLSYM_ARG(mnt_optstr_get_flags), + DLSYM_ARG(mnt_table_find_devno), + DLSYM_ARG(mnt_table_find_target), + DLSYM_ARG(mnt_table_next_child_fs), + DLSYM_ARG(mnt_table_next_fs), + DLSYM_ARG(mnt_table_parse_file), + DLSYM_ARG(mnt_table_parse_mtab), + DLSYM_ARG(mnt_table_parse_stream), + DLSYM_ARG(mnt_table_parse_swaps), + DLSYM_ARG(mnt_unref_monitor)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libmount support is not compiled in."); +#endif +} diff --git a/src/shared/libmount-util.h b/src/shared/libmount-util.h index bfbb00cd4afd4..7eb7b230b2ff3 100644 --- a/src/shared/libmount-util.h +++ b/src/shared/libmount-util.h @@ -42,8 +42,6 @@ extern DLSYM_PROTOTYPE(mnt_table_parse_stream); extern DLSYM_PROTOTYPE(mnt_table_parse_swaps); extern DLSYM_PROTOTYPE(mnt_unref_monitor); -int dlopen_libmount(void); - DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct libmnt_table*, sym_mnt_free_table, mnt_free_tablep, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct libmnt_iter*, sym_mnt_free_iter, mnt_free_iterp, NULL); @@ -79,9 +77,6 @@ int libmount_is_leaf( struct libmnt_monitor; -static inline int dlopen_libmount(void) { - return -EOPNOTSUPP; -} static inline void* sym_mnt_unref_monitor(struct libmnt_monitor *p) { assert(p == NULL); @@ -89,3 +84,5 @@ static inline void* sym_mnt_unref_monitor(struct libmnt_monitor *p) { } #endif + +int dlopen_libmount(int log_level); diff --git a/src/shared/local-addresses.c b/src/shared/local-addresses.c index c1c07c9c5ff76..7e0843e8d3d03 100644 --- a/src/shared/local-addresses.c +++ b/src/shared/local-addresses.c @@ -53,6 +53,9 @@ bool has_local_address(const struct local_address *addresses, size_t n_addresses static void suppress_duplicates(struct local_address *list, size_t *n_list) { size_t old_size, new_size; + POINTER_MAY_BE_NULL(list); + assert(n_list); + /* Removes duplicate entries, assumes the list of addresses is already sorted. Updates in-place. */ if (*n_list < 2) /* list with less than two entries can't have duplicates */ @@ -88,6 +91,7 @@ static int add_local_address_full( assert(ifindex > 0); assert(IN_SET(family, AF_INET, AF_INET6)); assert(address); + POINTER_MAY_BE_NULL(prefsrc); if (!GREEDY_REALLOC(*list, *n_list + 1)) return -ENOMEM; diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 3666c1639845c..d125662410ba2 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -1099,9 +1099,9 @@ static JsonData* json_data_free(JsonData *d) { DEFINE_TRIVIAL_CLEANUP_FUNC(JsonData*, json_data_free); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(json_data_hash_ops_free, - char, string_hash_func, string_compare_func, - JsonData, json_data_free); +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(json_data_hash_ops_free, + char, string_hash_func, string_compare_func, + JsonData, json_data_free); static int update_json_data( Hashmap *h, diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 8c924296488d2..e6fe4dbb49d7a 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -33,6 +33,8 @@ #include "time-util.h" static void cleanup_clear_loop_close(int *fd) { + assert(fd); + if (*fd < 0) return; @@ -376,9 +378,9 @@ static int loop_configure( } static int fd_get_max_discard(int fd, uint64_t *ret) { - struct stat st; char sysfs_path[STRLEN("/sys/dev/block/" ":" "/queue/discard_max_bytes") + DECIMAL_STR_MAX(dev_t) * 2 + 1]; _cleanup_free_ char *buffer = NULL; + struct stat st; int r; assert(ret); @@ -386,8 +388,9 @@ static int fd_get_max_discard(int fd, uint64_t *ret) { if (fstat(ASSERT_FD(fd), &st) < 0) return -errno; - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; + r = stat_verify_block(&st); + if (r < 0) + return r; xsprintf(sysfs_path, "/sys/dev/block/" DEVNUM_FORMAT_STR "/queue/discard_max_bytes", DEVNUM_FORMAT_VAL(st.st_rdev)); @@ -399,20 +402,49 @@ static int fd_get_max_discard(int fd, uint64_t *ret) { } static int fd_set_max_discard(int fd, uint64_t max_discard) { - struct stat st; char sysfs_path[STRLEN("/sys/dev/block/" ":" "/queue/discard_max_bytes") + DECIMAL_STR_MAX(dev_t) * 2 + 1]; + struct stat st; + int r; if (fstat(ASSERT_FD(fd), &st) < 0) return -errno; - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; + r = stat_verify_block(&st); + if (r < 0) + return r; xsprintf(sysfs_path, "/sys/dev/block/" DEVNUM_FORMAT_STR "/queue/discard_max_bytes", DEVNUM_FORMAT_VAL(st.st_rdev)); return write_string_filef(sysfs_path, WRITE_STRING_FILE_DISABLE_BUFFER, "%" PRIu64, max_discard); } +static int probe_sector_size_harder(int fd, uint32_t *ret) { + _cleanup_close_ int non_direct_io_fd = -EBADF; + int probe_fd, f_flags; + + assert(fd >= 0); + assert(ret); + + /* Wraps probe_sector_size() but handles O_DIRECT: if the fd is opened with O_DIRECT there are + * strict alignment requirements for reads, so we temporarily reopen it without O_DIRECT for the + * probing logic. */ + + f_flags = fcntl(fd, F_GETFL); + if (f_flags < 0) + return -errno; + + if (FLAGS_SET(f_flags, O_DIRECT)) { + non_direct_io_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK); + if (non_direct_io_fd < 0) + return non_direct_io_fd; + + probe_fd = non_direct_io_fd; + } else + probe_fd = fd; + + return probe_sector_size(probe_fd, ret); +} + static int loop_device_make_internal( const char *path, int fd, @@ -435,6 +467,11 @@ static int loop_device_make_internal( assert(open_flags < 0 || IN_SET(open_flags, O_RDWR, O_RDONLY)); assert(ret); + /* sector_size interpretation: + * 0 → use device sector size for block devices, 512 for regular files + * UINT32_MAX → probe GPT header to find the right sector size, fall back to 0 behavior + * other → use the specified sector size explicitly */ + f_flags = fcntl(fd, F_GETFL); if (f_flags < 0) return -errno; @@ -449,19 +486,45 @@ static int loop_device_make_internal( return log_debug_errno(SYNTHETIC_ERRNO(EBADFD), "Access mode of image file is write only (?)"); } + if (sector_size == UINT32_MAX) { + /* If sector size is specified as UINT32_MAX, we'll try to probe the right sector size + * by looking for the GPT partition header at various offsets. This of course only works + * if the image already has a disk label. */ + + r = probe_sector_size_harder(fd, §or_size); + if (r < 0) + return r; + if (r == 0) + sector_size = 0; /* If we can't probe anything, use default sector size. */ + } + if (fstat(fd, &st) < 0) return -errno; if (S_ISBLK(st.st_mode)) { - if (offset == 0 && IN_SET(size, 0, UINT64_MAX)) + uint32_t device_ssz; + r = blockdev_get_sector_size(fd, &device_ssz); + if (r < 0) + return r; + + if (sector_size == 0) + sector_size = device_ssz; + + if (offset == 0 && IN_SET(size, 0, UINT64_MAX) && sector_size == device_ssz) /* If this is already a block device and we are supposed to cover the whole of it - * then store an fd to the original open device node — and do not actually create an - * unnecessary loopback device for it. */ + * then store an fd to the original open device node — and do not actually create + * an unnecessary loopback device for it. If an explicit sector size was requested + * that differs from the device sector size, or if the probed GPT sector size + * differs (e.g. CD-ROMs with 2048-byte blocks but a 512-byte sector GPT), create + * a real loop device to change the sector size. */ return loop_device_open_from_fd(fd, open_flags, lock_op, ret); } else { r = stat_verify_regular(&st); if (r < 0) return r; + + if (sector_size == 0) + sector_size = 512; } if (path) { @@ -500,53 +563,26 @@ static int loop_device_make_internal( if (control < 0) return -errno; - if (sector_size == 0) - /* If no sector size is specified, default to the classic default */ - sector_size = 512; - else if (sector_size == UINT32_MAX) { - - if (S_ISBLK(st.st_mode)) - /* If the sector size is specified as UINT32_MAX we'll propagate the sector size of - * the underlying block device. */ - r = blockdev_get_sector_size(fd, §or_size); - else { - _cleanup_close_ int non_direct_io_fd = -EBADF; - int probe_fd; - - assert(S_ISREG(st.st_mode)); - - /* If sector size is specified as UINT32_MAX, we'll try to probe the right sector - * size of the image in question by looking for the GPT partition header at various - * offsets. This of course only works if the image already has a disk label. - * - * So here we actually want to read the file contents ourselves. This is quite likely - * not going to work if we managed to enable O_DIRECT, because in such a case there - * are some pretty strict alignment requirements to offset, size and target, but - * there's no way to query what alignment specifically is actually required. Hence, - * let's avoid the mess, and temporarily open an fd without O_DIRECT for the probing - * logic. */ - - if (FLAGS_SET(loop_flags, LO_FLAGS_DIRECT_IO)) { - non_direct_io_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK); - if (non_direct_io_fd < 0) - return non_direct_io_fd; - - probe_fd = non_direct_io_fd; - } else - probe_fd = fd; - - r = probe_sector_size(probe_fd, §or_size); - } - if (r < 0) - return r; - } + /* Strip LO_FLAGS_PARTSCAN from LOOP_CONFIGURE and enable it afterwards via + * LOOP_SET_STATUS64 to work around a kernel race: LOOP_CONFIGURE sends a uevent with + * GD_NEED_PART_SCAN set before calling loop_reread_partitions(). If udev opens the device in + * response, blkdev_get_whole() triggers a first scan, then loop_reread_partitions() does a + * second scan that briefly drops all partitions. By configuring without partscan, + * GD_SUPPRESS_PART_SCAN stays set, making any concurrent open harmless. LOOP_SET_STATUS64 + * doesn't call disk_force_media_change() so it doesn't set GD_NEED_PART_SCAN. + * + * See: https://lore.kernel.org/linux-block/20260330081819.652890-1-daan@amutable.com/T/#u + * Drop this workaround once the kernel fix is widely available. */ + bool deferred_partscan = FLAGS_SET(loop_flags, LO_FLAGS_PARTSCAN); config = (struct loop_config) { .fd = fd, .block_size = sector_size, .info = { /* Use the specified flags, but configure the read-only flag from the open flags, and force autoclear */ - .lo_flags = (loop_flags & ~LO_FLAGS_READ_ONLY) | ((open_flags & O_ACCMODE_STRICT) == O_RDONLY ? LO_FLAGS_READ_ONLY : 0) | LO_FLAGS_AUTOCLEAR, + .lo_flags = ((loop_flags & ~(LO_FLAGS_READ_ONLY|LO_FLAGS_PARTSCAN)) | + ((open_flags & O_ACCMODE_STRICT) == O_RDONLY ? LO_FLAGS_READ_ONLY : 0) | + LO_FLAGS_AUTOCLEAR), .lo_offset = offset, .lo_sizelimit = size == UINT64_MAX ? 0 : size, }, @@ -638,6 +674,24 @@ static int loop_device_make_internal( } } + if (deferred_partscan) { + /* Open+close to drain GD_NEED_PART_SCAN harmlessly (GD_SUPPRESS_PART_SCAN is still + * set so no partitions appear). Then enable partscan via LOOP_SET_STATUS64. */ + int tmp_fd = fd_reopen(d->fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK); + if (tmp_fd < 0) + return log_debug_errno(tmp_fd, "Failed to reopen loop device to drain partscan flag: %m"); + safe_close(tmp_fd); + + struct loop_info64 info; + if (ioctl(d->fd, LOOP_GET_STATUS64, &info) < 0) + return log_debug_errno(errno, "Failed to get loop device status: %m"); + + info.lo_flags |= LO_FLAGS_PARTSCAN; + + if (ioctl(d->fd, LOOP_SET_STATUS64, &info) < 0) + return log_debug_errno(errno, "Failed to enable partscan on loop device: %m"); + } + d->backing_file = TAKE_PTR(backing_file); d->backing_inode = st.st_ino; d->backing_devno = st.st_dev; @@ -769,24 +823,25 @@ int loop_device_make_by_path_memory( _cleanup_close_ int fd = -EBADF, mfd = -EBADF; _cleanup_free_ char *fn = NULL; - struct stat st; int r; assert(path); - assert(IN_SET(open_flags, O_RDWR, O_RDONLY)); + assert(open_flags < 0 || IN_SET(open_flags, O_RDWR, O_RDONLY)); assert(ret); + /* memfds are always writable, so default to O_RDWR when auto-detecting. */ + if (open_flags < 0) + open_flags = O_RDWR; + loop_flags &= ~LO_FLAGS_DIRECT_IO; /* memfds don't support O_DIRECT, hence LO_FLAGS_DIRECT_IO can't be used either */ fd = open(path, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_RDONLY); if (fd < 0) return -errno; - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISREG(st.st_mode) && !S_ISBLK(st.st_mode)) - return -EBADF; + r = fd_verify_regular_or_block(fd); + if (r < 0) + return r; r = path_extract_filename(path, &fn); if (r < 0) @@ -1178,6 +1233,9 @@ int loop_device_set_autoclear(LoopDevice *d, bool autoclear) { assert(d); + if (LOOP_DEVICE_IS_FOREIGN(d)) + return 0; + if (ioctl(ASSERT_FD(d->fd), LOOP_GET_STATUS64, &info) < 0) return -errno; diff --git a/src/shared/machine-bind-user.c b/src/shared/machine-bind-user.c index 278f7c99d0ccc..f65f32ca1948b 100644 --- a/src/shared/machine-bind-user.c +++ b/src/shared/machine-bind-user.c @@ -107,6 +107,8 @@ static int convert_user( assert(u); assert(g); assert(user_record_gid(u) == g->gid); + assert(ret_converted_user); + assert(ret_converted_group); if (shell_copy) shell = u->shell; diff --git a/src/shared/machine-register.c b/src/shared/machine-register.c new file mode 100644 index 0000000000000..a161d1b0508d3 --- /dev/null +++ b/src/shared/machine-register.c @@ -0,0 +1,335 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-bus.h" +#include "sd-id128.h" +#include "sd-json.h" +#include "sd-varlink.h" + +#include "bus-error.h" +#include "bus-locator.h" +#include "bus-util.h" +#include "errno-util.h" +#include "json-util.h" +#include "log.h" +#include "machine-register.h" +#include "path-lookup.h" +#include "pidref.h" +#include "runtime-scope.h" +#include "socket-util.h" +#include "string-util.h" +#include "terminal-util.h" + +static int register_machine_dbus_ex( + sd_bus *bus, + const MachineRegistration *reg, + sd_bus_error *error) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(bus); + assert(reg); + assert(reg->name); + assert(reg->service); + assert(reg->class); + assert(error); + + r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "RegisterMachineEx"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", reg->name); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "(sv)(sv)(sv)", + "Id", "ay", SD_BUS_MESSAGE_APPEND_ID128(reg->id), + "Service", "s", reg->service, + "Class", "s", reg->class); + if (r < 0) + return bus_log_create_error(r); + + if (pidref_is_set(reg->pidref)) { + if (reg->pidref->fd >= 0) { + r = sd_bus_message_append(m, "(sv)", "LeaderPIDFD", "h", reg->pidref->fd); + if (r < 0) + return bus_log_create_error(r); + } + + if (reg->pidref->fd_id > 0) { + r = sd_bus_message_append(m, "(sv)", "LeaderPIDFDID", "t", reg->pidref->fd_id); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "(sv)", "LeaderPID", "u", reg->pidref->pid); + if (r < 0) + return bus_log_create_error(r); + } + } + + if (!isempty(reg->root_directory)) { + r = sd_bus_message_append(m, "(sv)", "RootDirectory", "s", reg->root_directory); + if (r < 0) + return bus_log_create_error(r); + } + + if (reg->local_ifindex > 0) { + r = sd_bus_message_append(m, "(sv)", "NetworkInterfaces", "ai", 1, reg->local_ifindex); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + return sd_bus_call(bus, m, 0, error, NULL); +} + +static int register_machine_dbus( + sd_bus *bus, + const MachineRegistration *reg) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(reg); + assert(reg->name); + assert(reg->service); + assert(reg->class); + + /* First try RegisterMachineEx which supports PIDFD-based leader tracking. */ + r = register_machine_dbus_ex(bus, reg, &error); + if (r >= 0) + return 0; + if (!sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) + return log_debug_errno(r, "Failed to register machine via D-Bus: %s", bus_error_message(&error, r)); + + sd_bus_error_free(&error); + + r = bus_call_method( + bus, + bus_machine_mgr, + "RegisterMachineWithNetwork", + &error, + NULL, + "sayssusai", + reg->name, + SD_BUS_MESSAGE_APPEND_ID128(reg->id), + reg->service, + reg->class, + pidref_is_set(reg->pidref) ? (uint32_t) reg->pidref->pid : 0, + strempty(reg->root_directory), + reg->local_ifindex > 0 ? 1 : 0, reg->local_ifindex); + if (r < 0) + return log_debug_errno(r, "Failed to register machine via D-Bus: %s", bus_error_message(&error, r)); + + return 0; +} + +int register_machine( + sd_bus *bus, + const MachineRegistration *reg, + RuntimeScope scope) { + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + int r; + + assert(reg); + assert(reg->name); + assert(reg->service); + assert(reg->class); + + /* First try to use varlink, as it provides more features (such as SSH support). */ + _cleanup_free_ char *p = NULL; + r = runtime_directory_generic(scope, "systemd/machine/io.systemd.Machine", &p); + if (r >= 0) + r = sd_varlink_connect_address(&vl, p); + if (r == -ENOENT || ERRNO_IS_DISCONNECT(r)) { + log_debug_errno(r, "Failed to connect to machined via varlink%s%s, falling back to D-Bus: %m", + p ? " on " : "", strempty(p)); + + /* In case we are running with an older machined, fall back to D-Bus. Note that the D-Bus + * methods do not support the allocateUnit feature — machined will look up the caller's + * existing cgroup unit instead of creating a dedicated scope. Callers that skip client-side + * scope allocation when allocate_unit is set should be aware that on the D-Bus path no scope + * will be created at all. */ + if (!bus) + return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "Varlink connection to machined not available and no bus provided."); + + return register_machine_dbus(bus, reg); + } + if (r < 0) + return log_debug_errno(r, "Failed to connect to machined on %s: %m", strna(p)); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.Machine.Register", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_STRING("name", reg->name), + SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(reg->id), "id", SD_JSON_BUILD_ID128(reg->id)), + SD_JSON_BUILD_PAIR_STRING("service", reg->service), + SD_JSON_BUILD_PAIR_STRING("class", reg->class), + SD_JSON_BUILD_PAIR_CONDITION(VSOCK_CID_IS_REGULAR(reg->vsock_cid), "vSockCid", SD_JSON_BUILD_UNSIGNED(reg->vsock_cid)), + SD_JSON_BUILD_PAIR_CONDITION(reg->local_ifindex > 0, "ifIndices", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_INTEGER(reg->local_ifindex))), + SD_JSON_BUILD_PAIR_CONDITION(!!reg->root_directory, "rootDirectory", SD_JSON_BUILD_STRING(reg->root_directory)), + SD_JSON_BUILD_PAIR_CONDITION(!!reg->ssh_address, "sshAddress", SD_JSON_BUILD_STRING(reg->ssh_address)), + SD_JSON_BUILD_PAIR_CONDITION(!!reg->ssh_private_key_path, "sshPrivateKeyPath", SD_JSON_BUILD_STRING(reg->ssh_private_key_path)), + SD_JSON_BUILD_PAIR_CONDITION(!!reg->control_address, "controlAddress", SD_JSON_BUILD_STRING(reg->control_address)), + SD_JSON_BUILD_PAIR_CONDITION(isatty_safe(STDIN_FILENO), "allowInteractiveAuthentication", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(reg->allocate_unit, "allocateUnit", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(reg->pidref), "leaderProcessId", JSON_BUILD_PIDREF(reg->pidref))); + if (r < 0) + return log_debug_errno(r, "Failed to register machine via varlink: %m"); + if (error_id) + return log_debug_errno(sd_varlink_error_to_errno(error_id, reply), + "Failed to register machine via varlink: %s", error_id); + + return 0; +} + +static const char* machine_registration_scope_string(RuntimeScope scope, bool registered_system, bool registered_user) { + if (scope == _RUNTIME_SCOPE_INVALID) { + if (!registered_system && !registered_user) + return "system and user"; + if (!registered_system) + return "system"; + return "user"; + } + + return runtime_scope_to_string(scope); +} + +int register_machine_with_fallback_and_log( + MachineRegistrationContext *ctx, + const MachineRegistration *reg, + bool graceful) { + + int r = 0; + + assert(ctx); + assert(IN_SET(ctx->scope, RUNTIME_SCOPE_SYSTEM, RUNTIME_SCOPE_USER, _RUNTIME_SCOPE_INVALID)); + assert(ctx->system_bus || !IN_SET(ctx->scope, RUNTIME_SCOPE_SYSTEM, _RUNTIME_SCOPE_INVALID)); + assert(ctx->user_bus || !IN_SET(ctx->scope, RUNTIME_SCOPE_USER, _RUNTIME_SCOPE_INVALID)); + assert(reg); + assert(reg->name); + assert(reg->service); + assert(reg->class); + + if (IN_SET(ctx->scope, RUNTIME_SCOPE_SYSTEM, _RUNTIME_SCOPE_INVALID)) { + MachineRegistration system_reg = *reg; + if (ctx->scope != RUNTIME_SCOPE_SYSTEM) + system_reg.allocate_unit = false; + + int q = register_machine(ctx->system_bus, &system_reg, RUNTIME_SCOPE_SYSTEM); + if (q < 0) + RET_GATHER(r, q); + else + ctx->registered_system = true; + } + + if (IN_SET(ctx->scope, RUNTIME_SCOPE_USER, _RUNTIME_SCOPE_INVALID)) { + int q = register_machine(ctx->user_bus, reg, RUNTIME_SCOPE_USER); + if (q < 0) + RET_GATHER(r, q); + else + ctx->registered_user = true; + } + + if (r < 0) { + if (graceful) { + log_notice_errno(r, "Failed to register machine in %s context, ignoring: %m", + machine_registration_scope_string(ctx->scope, ctx->registered_system, ctx->registered_user)); + r = 0; + } else + r = log_error_errno(r, "Failed to register machine in %s context: %m", + machine_registration_scope_string(ctx->scope, ctx->registered_system, ctx->registered_user)); + } + + return r; +} + +void unregister_machine_with_fallback_and_log( + const MachineRegistrationContext *ctx, + const char *machine_name) { + + int r = 0; + bool failed_system = false, failed_user = false; + + assert(ctx); + + if (ctx->registered_system) { + int q = unregister_machine(ctx->system_bus, machine_name, RUNTIME_SCOPE_SYSTEM); + if (q < 0) { + RET_GATHER(r, q); + failed_system = true; + } + } + + if (ctx->registered_user) { + int q = unregister_machine(ctx->user_bus, machine_name, RUNTIME_SCOPE_USER); + if (q < 0) { + RET_GATHER(r, q); + failed_user = true; + } + } + + if (r < 0) + log_notice_errno(r, "Failed to unregister machine in %s context, ignoring: %m", + machine_registration_scope_string( + ctx->registered_system && ctx->registered_user ? _RUNTIME_SCOPE_INVALID : + ctx->registered_system ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, + !failed_system, !failed_user)); +} + +int unregister_machine(sd_bus *bus, const char *machine_name, RuntimeScope scope) { + int r; + + assert(machine_name); + + /* First try varlink */ + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + _cleanup_free_ char *p = NULL; + r = runtime_directory_generic(scope, "systemd/machine/io.systemd.Machine", &p); + if (r >= 0) + r = sd_varlink_connect_address(&vl, p); + if (r >= 0) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.Machine.Unregister", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_STRING("name", machine_name)); + if (r >= 0 && !error_id) + return 0; + if (r >= 0) + r = sd_varlink_error_to_errno(error_id, reply); + } + + log_debug_errno(r, "Failed to unregister machine via varlink, falling back to D-Bus: %m"); + + /* Fall back to D-Bus */ + if (!bus) + return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "Varlink connection to machined not available and no bus provided."); + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = bus_call_method(bus, bus_machine_mgr, "UnregisterMachine", &error, NULL, "s", machine_name); + if (r < 0) + return log_debug_errno(r, "Failed to unregister machine via D-Bus: %s", bus_error_message(&error, r)); + + return 0; +} diff --git a/src/shared/machine-register.h b/src/shared/machine-register.h new file mode 100644 index 0000000000000..ffec15a8812ab --- /dev/null +++ b/src/shared/machine-register.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-id128.h" + +#include "runtime-scope.h" +#include "shared-forward.h" + +typedef struct MachineRegistration { + const char *name; + sd_id128_t id; + const char *service; + const char *class; + const PidRef *pidref; + const char *root_directory; + unsigned vsock_cid; + int local_ifindex; + const char *ssh_address; + const char *ssh_private_key_path; + const char *control_address; + bool allocate_unit; +} MachineRegistration; + +typedef struct MachineRegistrationContext { + RuntimeScope scope; + sd_bus *system_bus; + sd_bus *user_bus; + bool registered_system; + bool registered_user; +} MachineRegistrationContext; + +int register_machine( + sd_bus *bus, + const MachineRegistration *reg, + RuntimeScope scope); +int register_machine_with_fallback_and_log( + MachineRegistrationContext *ctx, + const MachineRegistration *reg, + bool graceful); + +int unregister_machine(sd_bus *bus, const char *machine_name, RuntimeScope scope); +void unregister_machine_with_fallback_and_log( + const MachineRegistrationContext *ctx, + const char *machine_name); diff --git a/src/shared/machine-util.c b/src/shared/machine-util.c new file mode 100644 index 0000000000000..fa5e46ace1e53 --- /dev/null +++ b/src/shared/machine-util.c @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "extract-word.h" +#include "machine-util.h" +#include "parse-argument.h" +#include "string-table.h" + +static const char *const image_format_table[_IMAGE_FORMAT_MAX] = { + [IMAGE_FORMAT_RAW] = "raw", + [IMAGE_FORMAT_QCOW2] = "qcow2", +}; + +DEFINE_STRING_TABLE_LOOKUP(image_format, ImageFormat); + +static const char *const disk_type_table[_DISK_TYPE_MAX] = { + [DISK_TYPE_VIRTIO_BLK] = "virtio-blk", + [DISK_TYPE_VIRTIO_SCSI] = "virtio-scsi", + [DISK_TYPE_NVME] = "nvme", + [DISK_TYPE_VIRTIO_SCSI_CDROM] = "scsi-cd", +}; + +DEFINE_STRING_TABLE_LOOKUP(disk_type, DiskType); + +/* Wire value for the io.systemd.VirtualMachineInstance.BlockDriver IDL enum. */ +static const char *const block_driver_table[_DISK_TYPE_MAX] = { + [DISK_TYPE_VIRTIO_BLK] = "virtio_blk", + [DISK_TYPE_VIRTIO_SCSI] = "scsi_hd", + [DISK_TYPE_NVME] = "nvme", + [DISK_TYPE_VIRTIO_SCSI_CDROM] = "scsi_cd", +}; + +DEFINE_STRING_TABLE_LOOKUP(block_driver, DiskType); + +/* QEMU -device driver name (e.g. "virtio-blk-pci"). */ +static const char *const qemu_device_driver_table[_DISK_TYPE_MAX] = { + [DISK_TYPE_VIRTIO_BLK] = "virtio-blk-pci", + [DISK_TYPE_VIRTIO_SCSI] = "scsi-hd", + [DISK_TYPE_NVME] = "nvme", + [DISK_TYPE_VIRTIO_SCSI_CDROM] = "scsi-cd", +}; + +DEFINE_STRING_TABLE_LOOKUP(qemu_device_driver, DiskType); + +int parse_disk_spec( + const char *arg, + ImageFormat *format, + DiskType *disk_type, + char **ret_path) { + + int r; + + assert(arg); + assert(format); + assert(disk_type); + assert(ret_path); + + ImageFormat parsed_format = *format; + DiskType parsed_disk_type = *disk_type; + const char *dp = arg; + + /* Format and disk-type vocabularies don't overlap, so prefixes may appear in any order. */ + for (;;) { + _cleanup_free_ char *word = NULL; + const char *save = dp; + + r = extract_first_word(&dp, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0 || !dp) { + /* No ':' remained after this word — rest is the path. */ + dp = save; + break; + } + + ImageFormat f = image_format_from_string(word); + if (f >= 0) { + parsed_format = f; + continue; + } + + DiskType dt = disk_type_from_string(word); + if (dt >= 0) { + parsed_disk_type = dt; + continue; + } + + /* Unknown prefix — rewind, remainder is the path. */ + dp = save; + break; + } + + _cleanup_free_ char *path = NULL; + r = parse_path_argument(dp, /* suppress_root= */ false, &path); + if (r < 0) + return r; + + *format = parsed_format; + *disk_type = parsed_disk_type; + *ret_path = TAKE_PTR(path); + return 0; +} diff --git a/src/shared/machine-util.h b/src/shared/machine-util.h new file mode 100644 index 0000000000000..3937ce170377e --- /dev/null +++ b/src/shared/machine-util.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +typedef enum ImageFormat { + IMAGE_FORMAT_RAW, + IMAGE_FORMAT_QCOW2, + _IMAGE_FORMAT_MAX, + _IMAGE_FORMAT_INVALID = -EINVAL, +} ImageFormat; + +typedef enum DiskType { + DISK_TYPE_VIRTIO_BLK, + DISK_TYPE_VIRTIO_SCSI, + DISK_TYPE_NVME, + DISK_TYPE_VIRTIO_SCSI_CDROM, + _DISK_TYPE_MAX, + _DISK_TYPE_INVALID = -EINVAL, +} DiskType; + +DECLARE_STRING_TABLE_LOOKUP(image_format, ImageFormat); +DECLARE_STRING_TABLE_LOOKUP(disk_type, DiskType); +DECLARE_STRING_TABLE_LOOKUP(block_driver, DiskType); +DECLARE_STRING_TABLE_LOOKUP(qemu_device_driver, DiskType); + +/* Parse "[FORMAT:][DISKTYPE:]PATH"; *format and *disk_type are in-out. */ +int parse_disk_spec( + const char *arg, + ImageFormat *format, + DiskType *disk_type, + char **ret_path); diff --git a/src/shared/meson.build b/src/shared/meson.build index bbc0307999324..84acaf698b9c4 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -3,6 +3,7 @@ shared_sources = files( 'acl-util.c', 'acpi-fpdt.c', + 'apparmor-util.c', 'ask-password-agent.c', 'ask-password-api.c', 'async.c', @@ -50,9 +51,11 @@ shared_sources = files( 'coredump-util.c', 'cpu-set-util.c', 'creds-util.c', + 'crypto-util.c', 'cryptsetup-fido2.c', 'cryptsetup-tpm2.c', 'cryptsetup-util.c', + 'curl-util.c', 'daemon-util.c', 'data-fd-util.c', 'dev-setup.c', @@ -77,6 +80,8 @@ shared_sources = files( 'exit-status.c', 'extension-util.c', 'factory-reset.c', + 'facts.c', + 'fdisk-util.c', 'fdset.c', 'fido2-util.c', 'find-esp.c', @@ -86,12 +91,15 @@ shared_sources = files( 'fstab-util.c', 'generator.c', 'geneve-util.c', + 'gnutls-util.c', 'gpt.c', 'group-record.c', + 'help-util.c', 'hibernate-util.c', 'hostname-setup.c', 'hwdb-util.c', 'id128-print.c', + 'idn-util.c', 'ima-util.c', 'image-policy.c', 'import-util.c', @@ -114,6 +122,7 @@ shared_sources = files( 'libaudit-util.c', 'libcrypt-util.c', 'libfido2-util.c', + 'libmount-util.c', 'local-addresses.c', 'locale-setup.c', 'log-assert-critical.c', @@ -124,9 +133,12 @@ shared_sources = files( 'machine-bind-user.c', 'machine-credential.c', 'machine-id-setup.c', + 'machine-register.c', + 'machine-util.c', 'macvlan-util.c', 'main-func.c', 'metrics.c', + 'microhttpd-util.c', 'mkdir-label.c', 'mkfs-util.c', 'module-util.c', @@ -142,10 +154,11 @@ shared_sources = files( 'nsresource.c', 'numa-util.c', 'open-file.c', - 'openssl-util.c', + 'options.c', 'osc-context.c', 'output-mode.c', 'pager.c', + 'pam-util.c', 'parse-argument.c', 'parse-helpers.c', 'password-quality-util-passwdqc.c', @@ -162,6 +175,7 @@ shared_sources = files( 'printk-util.c', 'prompt-util.c', 'ptyfwd.c', + 'qmp-client.c', 'qrcode-util.c', 'quota-util.c', 'reboot-util.c', @@ -181,10 +195,13 @@ shared_sources = files( 'smack-util.c', 'smbios11.c', 'snapshot-util.c', + 'socket-forward.c', 'socket-label.c', 'socket-netlink.c', 'specifier.c', + 'ssl-util.c', 'switch-root.c', + 'swtpm-util.c', 'tar-util.c', 'tmpfile-util-label.c', 'tomoyo-util.c', @@ -202,13 +219,16 @@ shared_sources = files( 'varlink-io.systemd.BootControl.c', 'varlink-io.systemd.Credentials.c', 'varlink-io.systemd.FactoryReset.c', + 'varlink-io.systemd.Facts.c', 'varlink-io.systemd.Hostname.c', 'varlink-io.systemd.Import.c', + 'varlink-io.systemd.InstanceMetadata.c', 'varlink-io.systemd.Journal.c', 'varlink-io.systemd.JournalAccess.c', 'varlink-io.systemd.Login.c', 'varlink-io.systemd.Machine.c', 'varlink-io.systemd.MachineImage.c', + 'varlink-io.systemd.MachineInstance.c', 'varlink-io.systemd.ManagedOOM.c', 'varlink-io.systemd.Manager.c', 'varlink-io.systemd.Metrics.c', @@ -223,9 +243,11 @@ shared_sources = files( 'varlink-io.systemd.Resolve.c', 'varlink-io.systemd.Resolve.Hook.c', 'varlink-io.systemd.Resolve.Monitor.c', + 'varlink-io.systemd.Shutdown.c', 'varlink-io.systemd.Udev.c', 'varlink-io.systemd.Unit.c', 'varlink-io.systemd.UserDatabase.c', + 'varlink-io.systemd.VirtualMachineInstance.c', 'varlink-io.systemd.oom.c', 'varlink-io.systemd.oom.Prekill.c', 'varlink-io.systemd.service.c', @@ -270,22 +292,6 @@ if conf.get('HAVE_LIBBPF') == 1 shared_sources += files('bpf-link.c') endif -if conf.get('HAVE_PAM') == 1 - shared_sources += files('pam-util.c') -endif - -if conf.get('HAVE_LIBMOUNT') == 1 - shared_sources += files('libmount-util.c') -endif - -if conf.get('HAVE_LIBIDN2') == 1 - shared_sources += files('idn-util.c') -endif - -if conf.get('HAVE_APPARMOR') == 1 - shared_sources += files('apparmor-util.c') -endif - generate_ip_protocol_list = files('generate-ip-protocol-list.sh') ip_protocol_list_txt = custom_target( input : [generate_ip_protocol_list, ipproto_sources], @@ -384,15 +390,19 @@ libshared_deps = [threads, libbpf_cflags, libcrypt_cflags, libcryptsetup_cflags, + libcurl_cflags, libdl, libdw_cflags, libelf_cflags, + libfdisk_cflags, libfido2_cflags, libgcrypt_cflags, + libgnutls_cflags, libidn2_cflags, libkmod_cflags, + libmicrohttpd_cflags, libmount_cflags, - libopenssl, + libopenssl_cflags, libp11kit_cflags, libpam_cflags, libpcre2_cflags, @@ -435,15 +445,3 @@ libshared = shared_library( userspace], install : true, install_dir : pkglibdir) - -shared_fdisk_sources = files('fdisk-util.c') - -libshared_fdisk = static_library( - 'shared-fdisk', - shared_fdisk_sources, - include_directories : includes, - implicit_include_directories : false, - dependencies : [libfdisk, - userspace], - c_args : ['-fvisibility=default'], - build_by_default : false) diff --git a/src/shared/metrics.c b/src/shared/metrics.c index 3b8965dbdc2d5..6c1490cbab8c1 100644 --- a/src/shared/metrics.c +++ b/src/shared/metrics.c @@ -90,7 +90,7 @@ int metrics_method_describe( if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.Metrics.NoSuchMetric"); + r = sd_varlink_set_sentinel(link, "io.systemd.Metrics.NoSuchMetric"); if (r < 0) return r; @@ -127,7 +127,7 @@ int metrics_method_list( if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.Metrics.NoSuchMetric"); + r = sd_varlink_set_sentinel(link, "io.systemd.Metrics.NoSuchMetric"); if (r < 0) return r; @@ -163,7 +163,7 @@ static int metric_build_send(MetricFamilyContext *context, const char *object, s return sd_varlink_replybo(context->link, SD_JSON_BUILD_PAIR_STRING("name", context->metric_family->name), JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object), - SD_JSON_BUILD_PAIR("value", SD_JSON_BUILD_VARIANT(value)), + SD_JSON_BUILD_PAIR_VARIANT("value", value), JSON_BUILD_PAIR_VARIANT_NON_NULL("fields", fields)); } diff --git a/src/journal-remote/microhttpd-util.c b/src/shared/microhttpd-util.c similarity index 65% rename from src/journal-remote/microhttpd-util.c rename to src/shared/microhttpd-util.c index e69f32f7ab536..6dd55d8a0769c 100644 --- a/src/journal-remote/microhttpd-util.c +++ b/src/shared/microhttpd-util.c @@ -2,17 +2,74 @@ #include -#if HAVE_GNUTLS -#include -#include -#endif +#include "sd-dlopen.h" #include "alloc-util.h" +#include "gnutls-util.h" #include "log.h" #include "microhttpd-util.h" #include "string-util.h" #include "strv.h" +#if HAVE_MICROHTTPD +static void *microhttpd_dl = NULL; + +DLSYM_PROTOTYPE(MHD_add_response_header) = NULL; +DLSYM_PROTOTYPE(MHD_create_response_from_buffer) = NULL; +DLSYM_PROTOTYPE(MHD_create_response_from_callback) = NULL; +#if MHD_VERSION < 0x00094203 +DLSYM_PROTOTYPE(MHD_create_response_from_fd_at_offset) = NULL; +#else +DLSYM_PROTOTYPE(MHD_create_response_from_fd_at_offset64) = NULL; +#endif +DLSYM_PROTOTYPE(MHD_destroy_response) = NULL; +DLSYM_PROTOTYPE(MHD_get_connection_info) = NULL; +DLSYM_PROTOTYPE(MHD_get_connection_values) = NULL; +DLSYM_PROTOTYPE(MHD_get_daemon_info) = NULL; +DLSYM_PROTOTYPE(MHD_get_timeout) = NULL; +DLSYM_PROTOTYPE(MHD_lookup_connection_value) = NULL; +DLSYM_PROTOTYPE(MHD_queue_response) = NULL; +DLSYM_PROTOTYPE(MHD_run) = NULL; +DLSYM_PROTOTYPE(MHD_start_daemon) = NULL; +DLSYM_PROTOTYPE(MHD_stop_daemon) = NULL; +#endif + +int dlopen_microhttpd(int log_level) { +#if HAVE_MICROHTTPD + SD_ELF_NOTE_DLOPEN( + "microhttpd", + "Support for embedded HTTP server via libmicrohttpd", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libmicrohttpd.so.12"); + + return dlopen_many_sym_or_warn( + µhttpd_dl, + "libmicrohttpd.so.12", + log_level, + DLSYM_ARG(MHD_add_response_header), + DLSYM_ARG(MHD_create_response_from_buffer), + DLSYM_ARG(MHD_create_response_from_callback), +#if MHD_VERSION < 0x00094203 + DLSYM_ARG(MHD_create_response_from_fd_at_offset), +#else + DLSYM_ARG(MHD_create_response_from_fd_at_offset64), +#endif + DLSYM_ARG(MHD_destroy_response), + DLSYM_ARG(MHD_get_connection_info), + DLSYM_ARG(MHD_get_connection_values), + DLSYM_ARG(MHD_get_daemon_info), + DLSYM_ARG(MHD_get_timeout), + DLSYM_ARG(MHD_lookup_connection_value), + DLSYM_ARG(MHD_queue_response), + DLSYM_ARG(MHD_run), + DLSYM_ARG(MHD_start_daemon), + DLSYM_ARG(MHD_stop_daemon)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libmicrohttpd support is not compiled in."); +#endif +} + #if HAVE_MICROHTTPD void microhttpd_logger(void *arg, const char *fmt, va_list ap) { @@ -36,18 +93,18 @@ int mhd_respond_internal( assert(connection); _cleanup_(MHD_destroy_responsep) struct MHD_Response *response - = MHD_create_response_from_buffer(size, (char*) buffer, mode); + = sym_MHD_create_response_from_buffer(size, (char*) buffer, mode); if (!response) return MHD_NO; log_debug("Queueing response %u: %s", code, buffer); if (encoding) - if (MHD_add_response_header(response, "Accept-Encoding", encoding) == MHD_NO) + if (sym_MHD_add_response_header(response, "Accept-Encoding", encoding) == MHD_NO) return MHD_NO; - if (MHD_add_response_header(response, "Content-Type", "text/plain") == MHD_NO) + if (sym_MHD_add_response_header(response, "Content-Type", "text/plain") == MHD_NO) return MHD_NO; - return MHD_queue_response(connection, code, response); + return sym_MHD_queue_response(connection, code, response); } int mhd_respond_oom(struct MHD_Connection *connection) { @@ -68,9 +125,7 @@ int mhd_respondf_internal( assert(connection); assert(format); - if (error < 0) - error = -error; - errno = -error; + errno = ERRNO_VALUE(error); va_start(ap, format); r = vasprintf(&m, format, ap); va_end(ap); @@ -118,7 +173,7 @@ static void log_reset_gnutls_level(void) { for (i = ELEMENTSOF(gnutls_log_map) - 1; i >= 0; i--) if (gnutls_log_map[i].enabled) { log_debug("Setting gnutls log level to %d", i); - gnutls_global_set_log_level(i); + sym_gnutls_global_set_log_level(i); break; } } @@ -142,7 +197,16 @@ static int log_enable_gnutls_category(const char *cat) { int setup_gnutls_logger(char **categories) { int r; - gnutls_global_set_log_function(log_func_gnutls); + r = dlopen_gnutls(LOG_DEBUG); + if (r < 0) { + if (categories) + log_notice("Ignoring specified gnutls logging categories -- gnutls not available."); + else + log_debug("GnuTLS not available, skipping logger setup."); + return 0; + } + + sym_gnutls_global_set_log_function(log_func_gnutls); if (categories) STRV_FOREACH(cat, categories) { @@ -162,17 +226,19 @@ static int verify_cert_authorized(gnutls_session_t session) { gnutls_datum_t out; int r; - r = gnutls_certificate_verify_peers2(session, &status); + r = sym_gnutls_certificate_verify_peers2(session, &status); if (r < 0) return log_error_errno(r, "gnutls_certificate_verify_peers2 failed: %m"); - type = gnutls_certificate_type_get(session); - r = gnutls_certificate_verification_status_print(status, type, &out, 0); + type = sym_gnutls_certificate_type_get(session); + r = sym_gnutls_certificate_verification_status_print(status, type, &out, 0); if (r < 0) return log_error_errno(r, "gnutls_certificate_verification_status_print failed: %m"); log_debug("Certificate status: %s", out.data); - gnutls_free(out.data); + /* gnutls_free is declared as a function pointer variable (not a function), so sym_gnutls_free + * ends up as a pointer-to-function-pointer and must be explicitly dereferenced to be called. */ + (*sym_gnutls_free)(out.data); return status == 0 ? 0 : -EPERM; } @@ -186,12 +252,12 @@ static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_c assert(session); assert(client_cert); - pcert = gnutls_certificate_get_peers(session, &listsize); + pcert = sym_gnutls_certificate_get_peers(session, &listsize); if (!pcert || !listsize) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to retrieve certificate chain"); - r = gnutls_x509_crt_init(&cert); + r = sym_gnutls_x509_crt_init(&cert); if (r < 0) { log_error("Failed to initialize client certificate"); return r; @@ -199,10 +265,10 @@ static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_c /* Note that by passing values between 0 and listsize here, you can get access to the CA's certs */ - r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER); + r = sym_gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER); if (r < 0) { log_error("Failed to import client certificate"); - gnutls_x509_crt_deinit(cert); + sym_gnutls_x509_crt_deinit(cert); return r; } @@ -217,7 +283,7 @@ static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) { assert(buf); assert(*buf == NULL); - r = gnutls_x509_crt_get_dn(client_cert, NULL, &len); + r = sym_gnutls_x509_crt_get_dn(client_cert, NULL, &len); if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) { log_error("gnutls_x509_crt_get_dn failed"); return r; @@ -227,12 +293,15 @@ static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) { if (!*buf) return log_oom(); - gnutls_x509_crt_get_dn(client_cert, *buf, &len); + sym_gnutls_x509_crt_get_dn(client_cert, *buf, &len); return 0; } static void gnutls_x509_crt_deinitp(gnutls_x509_crt_t *p) { - gnutls_x509_crt_deinit(*p); + assert(p); + + if (*p) + sym_gnutls_x509_crt_deinit(*p); } int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) { @@ -247,8 +316,12 @@ int check_permissions(struct MHD_Connection *connection, int *code, char **hostn *code = 0; - ci = MHD_get_connection_info(connection, - MHD_CONNECTION_INFO_GNUTLS_SESSION); + r = dlopen_gnutls(LOG_ERR); + if (r < 0) + return r; + + ci = sym_MHD_get_connection_info(connection, + MHD_CONNECTION_INFO_GNUTLS_SESSION); if (!ci) { log_error("MHD_get_connection_info failed: session is unencrypted"); *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN, diff --git a/src/journal-remote/microhttpd-util.h b/src/shared/microhttpd-util.h similarity index 76% rename from src/journal-remote/microhttpd-util.h rename to src/shared/microhttpd-util.h index 80142e24f59c5..488ba7ea6e883 100644 --- a/src/journal-remote/microhttpd-util.h +++ b/src/shared/microhttpd-util.h @@ -1,11 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "shared-forward.h" + +int dlopen_microhttpd(int log_level); + #if HAVE_MICROHTTPD #include -#include "shared-forward.h" +#include "dlfcn-util.h" /* Those defines are added when options are renamed. If the old names * are not '#define'd, then they are not deprecated yet and there are @@ -58,6 +62,26 @@ # define mhd_result int #endif +extern DLSYM_PROTOTYPE(MHD_add_response_header); +extern DLSYM_PROTOTYPE(MHD_create_response_from_buffer); +extern DLSYM_PROTOTYPE(MHD_create_response_from_callback); +#if MHD_VERSION < 0x00094203 +extern DLSYM_PROTOTYPE(MHD_create_response_from_fd_at_offset); +# define sym_MHD_create_response_from_fd_at_offset64 sym_MHD_create_response_from_fd_at_offset +#else +extern DLSYM_PROTOTYPE(MHD_create_response_from_fd_at_offset64); +#endif +extern DLSYM_PROTOTYPE(MHD_destroy_response); +extern DLSYM_PROTOTYPE(MHD_get_connection_info); +extern DLSYM_PROTOTYPE(MHD_get_connection_values); +extern DLSYM_PROTOTYPE(MHD_get_daemon_info); +extern DLSYM_PROTOTYPE(MHD_get_timeout); +extern DLSYM_PROTOTYPE(MHD_lookup_connection_value); +extern DLSYM_PROTOTYPE(MHD_queue_response); +extern DLSYM_PROTOTYPE(MHD_run); +extern DLSYM_PROTOTYPE(MHD_start_daemon); +extern DLSYM_PROTOTYPE(MHD_stop_daemon); + void microhttpd_logger(void *arg, const char *fmt, va_list ap) _printf_(2, 0); /* respond_oom() must be usable with return, hence this form. */ @@ -107,7 +131,7 @@ int check_permissions(struct MHD_Connection *connection, int *code, char **hostn */ int setup_gnutls_logger(char **categories); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct MHD_Daemon*, MHD_stop_daemon, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct MHD_Response*, MHD_destroy_response, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct MHD_Daemon*, sym_MHD_stop_daemon, MHD_stop_daemonp, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct MHD_Response*, sym_MHD_destroy_response, MHD_destroy_responsep, NULL); #endif diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c index b3e575efd37fd..e45a106ed5e03 100644 --- a/src/shared/mkfs-util.c +++ b/src/shared/mkfs-util.c @@ -74,11 +74,12 @@ static int mangle_linux_fs_label(const char *s, size_t max_len, char **ret) { } static int mangle_fat_label(const char *s, char **ret) { - assert(s); - _cleanup_free_ char *q = NULL; int r; + assert(s); + assert(ret); + r = utf8_to_ascii(s, '_', &q); if (r < 0) return r; diff --git a/src/shared/module-util.c b/src/shared/module-util.c index a7f9e178e3c2a..9bf1a827b008f 100644 --- a/src/shared/module-util.c +++ b/src/shared/module-util.c @@ -2,6 +2,8 @@ #include +#include "sd-dlopen.h" + #include "log.h" #include "module-util.h" #include "proc-cmdline.h" @@ -25,31 +27,6 @@ DLSYM_PROTOTYPE(kmod_set_log_fn) = NULL; DLSYM_PROTOTYPE(kmod_unref) = NULL; DLSYM_PROTOTYPE(kmod_validate_resources) = NULL; -int dlopen_libkmod(void) { - ELF_NOTE_DLOPEN("kmod", - "Support for loading kernel modules", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libkmod.so.2"); - - return dlopen_many_sym_or_warn( - &libkmod_dl, - "libkmod.so.2", - LOG_DEBUG, - DLSYM_ARG(kmod_list_next), - DLSYM_ARG(kmod_load_resources), - DLSYM_ARG(kmod_module_get_initstate), - DLSYM_ARG(kmod_module_get_module), - DLSYM_ARG(kmod_module_get_name), - DLSYM_ARG(kmod_module_new_from_lookup), - DLSYM_ARG(kmod_module_probe_insert_module), - DLSYM_ARG(kmod_module_unref), - DLSYM_ARG(kmod_module_unref_list), - DLSYM_ARG(kmod_new), - DLSYM_ARG(kmod_set_log_fn), - DLSYM_ARG(kmod_unref), - DLSYM_ARG(kmod_validate_resources)); -} - static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { char ***denylist = ASSERT_PTR(data); int r; @@ -177,7 +154,7 @@ int module_setup_context(struct kmod_ctx **ret) { assert(ret); - r = dlopen_libkmod(); + r = dlopen_libkmod(LOG_DEBUG); if (r < 0) return r; @@ -193,3 +170,34 @@ int module_setup_context(struct kmod_ctx **ret) { } #endif + +int dlopen_libkmod(int log_level) { +#if HAVE_KMOD + SD_ELF_NOTE_DLOPEN( + "kmod", + "Support for loading kernel modules", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libkmod.so.2"); + + return dlopen_many_sym_or_warn( + &libkmod_dl, + "libkmod.so.2", + log_level, + DLSYM_ARG(kmod_list_next), + DLSYM_ARG(kmod_load_resources), + DLSYM_ARG(kmod_module_get_initstate), + DLSYM_ARG(kmod_module_get_module), + DLSYM_ARG(kmod_module_get_name), + DLSYM_ARG(kmod_module_new_from_lookup), + DLSYM_ARG(kmod_module_probe_insert_module), + DLSYM_ARG(kmod_module_unref), + DLSYM_ARG(kmod_module_unref_list), + DLSYM_ARG(kmod_new), + DLSYM_ARG(kmod_set_log_fn), + DLSYM_ARG(kmod_unref), + DLSYM_ARG(kmod_validate_resources)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libkmod support is not compiled in."); +#endif +} diff --git a/src/shared/module-util.h b/src/shared/module-util.h index f5eaf35c90916..629979722c734 100644 --- a/src/shared/module-util.h +++ b/src/shared/module-util.h @@ -23,8 +23,6 @@ extern DLSYM_PROTOTYPE(kmod_set_log_fn); extern DLSYM_PROTOTYPE(kmod_unref); extern DLSYM_PROTOTYPE(kmod_validate_resources); -int dlopen_libkmod(void); - DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct kmod_ctx*, sym_kmod_unref, kmod_unrefp, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct kmod_module*, sym_kmod_module_unref, kmod_module_unrefp, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct kmod_list*, sym_kmod_module_unref_list, kmod_module_unref_listp, NULL); @@ -41,9 +39,6 @@ int module_setup_context(struct kmod_ctx **ret); struct kmod_ctx; -static inline int dlopen_libkmod(void) { - return -EOPNOTSUPP; -} static inline int module_setup_context(struct kmod_ctx **ret) { return -EOPNOTSUPP; @@ -54,3 +49,5 @@ static inline int module_load_and_warn(struct kmod_ctx *ctx, const char *module, } #endif + +int dlopen_libkmod(int log_level); diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index f3eaa7ba97ad9..12d3da82e220e 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -451,7 +451,7 @@ int bind_remount_one_with_mountinfo( rewind(proc_self_mountinfo); - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0) return r; @@ -900,7 +900,7 @@ int mount_option_mangle( * The validity of options stored in '*ret_remaining_options' is not checked. * If 'options' is NULL, this just copies 'mount_flags' to *ret_mount_flags. */ - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0) return r; @@ -1748,14 +1748,7 @@ static void sub_mount_clear(SubMount *s) { s->mount_fd = safe_close(s->mount_fd); } -void sub_mount_array_free(SubMount *s, size_t n) { - assert(s || n == 0); - - for (size_t i = 0; i < n; i++) - sub_mount_clear(s + i); - - free(s); -} +DEFINE_ARRAY_FREE_FUNC(sub_mount_array_free, SubMount, sub_mount_clear); #if HAVE_LIBMOUNT static int sub_mount_compare(const SubMount *a, const SubMount *b) { @@ -1830,13 +1823,11 @@ int get_sub_mounts(const char *prefix, SubMount **ret_mounts, size_t *ret_n_moun continue; } - mount_fd = open(path, O_CLOEXEC|O_PATH); - if (mount_fd < 0) { - if (errno == ENOENT) /* The path may be hidden by another over-mount or already unmounted. */ - continue; - - return log_debug_errno(errno, "Failed to open subtree of mounted filesystem '%s': %m", path); - } + mount_fd = RET_NERRNO(open_tree(AT_FDCWD, path, OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_RECURSIVE)); + if (mount_fd == -ENOENT) /* The path may be hidden by another over-mount or already unmounted. */ + continue; + if (mount_fd < 0) + return log_debug_errno(mount_fd, "Failed to open subtree of mounted filesystem '%s': %m", path); p = strdup(path); if (!p) @@ -1905,9 +1896,7 @@ int bind_mount_submounts( continue; } - r = mount_follow_verbose(LOG_DEBUG, FORMAT_PROC_FD_PATH(m->mount_fd), t, NULL, MS_BIND|MS_REC, NULL); - if (r < 0 && ret == 0) - ret = r; + RET_GATHER(ret, RET_NERRNO(move_mount(m->mount_fd, "", AT_FDCWD, t, MOVE_MOUNT_F_EMPTY_PATH))); } return ret; @@ -1992,10 +1981,19 @@ int fsmount_credentials_fs(int *ret_fsfd) { if (fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) return -errno; - int mfd = fsmount(fs_fd, FSMOUNT_CLOEXEC, - ms_flags_to_mount_attr(credentials_fs_mount_flags(/* ro= */ false))); + unsigned mount_attrs = ms_flags_to_mount_attr(credentials_fs_mount_flags(/* ro = */ false)); + + int mfd = RET_NERRNO(fsmount(fs_fd, FSMOUNT_CLOEXEC, mount_attrs)); + if (mfd == -EINVAL) { + /* MS_NOSYMFOLLOW was added in kernel 5.10, but the new mount API counterpart was missing + * until 5.14 (c.f. https://github.com/torvalds/linux/commit/dd8b477f9a3d8edb136207acb3652e1a34a661b7). + * + * TODO: drop this once our baseline is raised to 5.14 */ + assert(FLAGS_SET(mount_attrs, MOUNT_ATTR_NOSYMFOLLOW)); + mfd = RET_NERRNO(fsmount(fs_fd, FSMOUNT_CLOEXEC, mount_attrs & ~MOUNT_ATTR_NOSYMFOLLOW)); + } if (mfd < 0) - return -errno; + return mfd; if (ret_fsfd) *ret_fsfd = TAKE_FD(fs_fd); @@ -2062,7 +2060,7 @@ int make_fsmount( r = extract_first_word(&p, &word, ",", EXTRACT_KEEP_QUOTE); if (r < 0) - return log_full_errno(error_log_level, r, "Failed to parse mount option string \"%s\": %m", o); + return log_full_errno(error_log_level, r, "Failed to parse mount option string \"%s\": %m", strempty(o)); if (r == 0) break; diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h index 768aacf09c401..b178bf7bac28e 100644 --- a/src/shared/mount-util.h +++ b/src/shared/mount-util.h @@ -8,7 +8,7 @@ typedef struct SubMount { int mount_fd; } SubMount; -void sub_mount_array_free(SubMount *s, size_t n); +void sub_mount_array_free(SubMount *array, size_t n); int get_sub_mounts(const char *prefix, SubMount **ret_mounts, size_t *ret_n_mounts); int bind_mount_submounts( diff --git a/src/shared/mstack.c b/src/shared/mstack.c index e5dcd91e525a9..cc3cb9a75f7cb 100644 --- a/src/shared/mstack.c +++ b/src/shared/mstack.c @@ -576,11 +576,10 @@ int mstack_open_images( _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL; - r = loop_device_make( + r = loop_device_make_by_path_at( m->what_fd, - FLAGS_SET(flags, MSTACK_RDONLY) ? O_RDONLY : O_RDWR, - /* offset= */ 0, - /* size= */ UINT64_MAX, + /* path= */ NULL, + FLAGS_SET(flags, MSTACK_RDONLY) ? O_RDONLY : -1, /* sector_size= */ UINT32_MAX, LO_FLAGS_PARTSCAN, LOCK_SH, @@ -1033,7 +1032,7 @@ int mstack_bind_mounts( if (mstack->usr_mount_fd >= 0) { _cleanup_close_ int subdir_fd = -EBADF; - r = chaseat(root_fd, "usr", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &subdir_fd); + r = chaseat(root_fd, root_fd, "usr", CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &subdir_fd); if (r < 0) return log_debug_errno(r, "Failed to open mount point inode '%s': %m", where); @@ -1052,7 +1051,7 @@ int mstack_bind_mounts( assert(m->mount_fd >= 0); _cleanup_close_ int subdir_fd = -EBADF; - r = chaseat(root_fd, m->where, CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &subdir_fd); + r = chaseat(root_fd, root_fd, m->where, CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &subdir_fd); if (r < 0) return log_debug_errno(r, "Failed to open mount point inode '%s': %m", m->where); diff --git a/src/shared/netif-util.c b/src/shared/netif-util.c index 74daf1facc83e..0281fd03134d5 100644 --- a/src/shared/netif-util.c +++ b/src/shared/netif-util.c @@ -40,6 +40,8 @@ int net_get_type_string(sd_device *device, uint16_t iftype, char **ret) { const char *t; char *p; + assert(ret); + if (device && sd_device_get_devtype(device, &t) >= 0 && !isempty(t)) diff --git a/src/shared/nsresource.c b/src/shared/nsresource.c index 6f70bcaf1f3cb..7a1c33446b811 100644 --- a/src/shared/nsresource.c +++ b/src/shared/nsresource.c @@ -270,7 +270,7 @@ int nsresource_add_cgroup(sd_varlink *vl, int userns_fd, int cgroup_fd) { cgroup_fd_idx = sd_varlink_push_dup_fd(vl, cgroup_fd); if (cgroup_fd_idx < 0) - return log_debug_errno(userns_fd_idx, "Failed to push cgroup fd into varlink connection: %m"); + return log_debug_errno(cgroup_fd_idx, "Failed to push cgroup fd into varlink connection: %m"); sd_json_variant *reply = NULL; r = sd_varlink_callbo( diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h deleted file mode 100644 index 218641e06fe61..0000000000000 --- a/src/shared/openssl-util.h +++ /dev/null @@ -1,196 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "ask-password-api.h" -#include "shared-forward.h" -#include "iovec-util.h" -#include "sha256.h" - -typedef enum CertificateSourceType { - OPENSSL_CERTIFICATE_SOURCE_FILE, - OPENSSL_CERTIFICATE_SOURCE_PROVIDER, - _OPENSSL_CERTIFICATE_SOURCE_MAX, - _OPENSSL_CERTIFICATE_SOURCE_INVALID = -EINVAL, -} CertificateSourceType; - -typedef enum KeySourceType { - OPENSSL_KEY_SOURCE_FILE, - OPENSSL_KEY_SOURCE_ENGINE, - OPENSSL_KEY_SOURCE_PROVIDER, - _OPENSSL_KEY_SOURCE_MAX, - _OPENSSL_KEY_SOURCE_INVALID = -EINVAL, -} KeySourceType; - -typedef struct OpenSSLAskPasswordUI OpenSSLAskPasswordUI; - -int parse_openssl_certificate_source_argument(const char *argument, char **certificate_source, CertificateSourceType *certificate_source_type); - -int parse_openssl_key_source_argument(const char *argument, char **private_key_source, KeySourceType *private_key_source_type); - -#define X509_FINGERPRINT_SIZE SHA256_DIGEST_SIZE - -#if HAVE_OPENSSL -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# ifndef OPENSSL_NO_UI_CONSOLE -# include /* IWYU pragma: export */ -# endif -# include /* IWYU pragma: export */ - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(void*, OPENSSL_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_OCTET_STRING*, ASN1_OCTET_STRING_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_TIME*, ASN1_TIME_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free_all, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIGNUM*, BN_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BN_CTX*, BN_CTX_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_GROUP*, EC_GROUP_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_POINT*, EC_POINT_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ECDSA_SIG*, ECDSA_SIG_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER*, EVP_CIPHER_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER_CTX*, EVP_CIPHER_CTX_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_KDF*, EVP_KDF_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_KDF_CTX*, EVP_KDF_CTX_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MAC*, EVP_MAC_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MAC_CTX*, EVP_MAC_CTX_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD*, EVP_MD_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY*, EVP_PKEY_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY_CTX*, EVP_PKEY_CTX_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM*, OSSL_PARAM_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM_BLD*, OSSL_PARAM_BLD_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_STORE_CTX*, OSSL_STORE_close, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_STORE_INFO*, OSSL_STORE_INFO_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7*, PKCS7_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7_SIGNER_INFO*, PKCS7_SIGNER_INFO_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509_NAME*, X509_NAME_free, NULL); - -static inline STACK_OF(X509_ALGOR) *x509_algor_free_many(STACK_OF(X509_ALGOR) *attrs) { - if (!attrs) - return NULL; - - sk_X509_ALGOR_pop_free(attrs, X509_ALGOR_free); - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(STACK_OF(X509_ALGOR)*, x509_algor_free_many, NULL); - -static inline STACK_OF(X509_ATTRIBUTE) *x509_attribute_free_many(STACK_OF(X509_ATTRIBUTE) *attrs) { - if (!attrs) - return NULL; - - sk_X509_ATTRIBUTE_pop_free(attrs, X509_ATTRIBUTE_free); - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(STACK_OF(X509_ATTRIBUTE)*, x509_attribute_free_many, NULL); - -static inline void sk_X509_free_allp(STACK_OF(X509) **sk) { - if (!sk || !*sk) - return; - - sk_X509_pop_free(*sk, X509_free); -} - -int openssl_pubkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret); -int openssl_pubkey_to_pem(EVP_PKEY *pkey, char **ret); - -int openssl_digest_size(const char *digest_alg, size_t *ret_digest_size); - -int openssl_digest_many(const char *digest_alg, const struct iovec data[], size_t n_data, void **ret_digest, size_t *ret_digest_size); - -static inline int openssl_digest(const char *digest_alg, const void *buf, size_t len, void **ret_digest, size_t *ret_digest_size) { - return openssl_digest_many(digest_alg, &IOVEC_MAKE((void*) buf, len), 1, ret_digest, ret_digest_size); -} - -int openssl_hmac_many(const char *digest_alg, const void *key, size_t key_size, const struct iovec data[], size_t n_data, void **ret_digest, size_t *ret_digest_size); - -static inline int openssl_hmac(const char *digest_alg, const void *key, size_t key_size, const void *buf, size_t len, void **ret_digest, size_t *ret_digest_size) { - return openssl_hmac_many(digest_alg, key, key_size, &IOVEC_MAKE((void*) buf, len), 1, ret_digest, ret_digest_size); -} - -int openssl_cipher_many(const char *alg, size_t bits, const char *mode, const void *key, size_t key_size, const void *iv, size_t iv_size, const struct iovec data[], size_t n_data, void **ret, size_t *ret_size); - -static inline int openssl_cipher(const char *alg, size_t bits, const char *mode, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *buf, size_t len, void **ret, size_t *ret_size) { - return openssl_cipher_many(alg, bits, mode, key, key_size, iv, iv_size, &IOVEC_MAKE((void*) buf, len), 1, ret, ret_size); -} - -int kdf_ss_derive(const char *digest, const void *key, size_t key_size, const void *salt, size_t salt_size, const void *info, size_t info_size, size_t derive_size, void **ret); - -int kdf_kb_hmac_derive(const char *mode, const char *digest, const void *key, size_t key_size, const void *salt, size_t salt_size, const void *info, size_t info_size, const void *seed, size_t seed_size, size_t derive_size, void **ret); - -int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); - -int rsa_oaep_encrypt_bytes(const EVP_PKEY *pkey, const char *digest_alg, const char *label, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); - -int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size); - -int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size, EVP_PKEY **ret); - -int rsa_pkey_to_n_e(const EVP_PKEY *pkey, void **ret_n, size_t *ret_n_size, void **ret_e, size_t *ret_e_size); - -int ecc_pkey_from_curve_x_y(int curve_id, const void *x, size_t x_size, const void *y, size_t y_size, EVP_PKEY **ret); - -int ecc_pkey_to_curve_x_y(const EVP_PKEY *pkey, int *ret_curve_id, void **ret_x, size_t *ret_x_size, void **ret_y, size_t *ret_y_size); - -int ecc_pkey_new(int curve_id, EVP_PKEY **ret); - -int ecc_ecdh(const EVP_PKEY *private_pkey, const EVP_PKEY *peer_pkey, void **ret_shared_secret, size_t *ret_shared_secret_size); - -int pkey_generate_volume_keys(EVP_PKEY *pkey, void **ret_decrypted_key, size_t *ret_decrypted_key_size, void **ret_saved_key, size_t *ret_saved_key_size); - -int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size); - -int digest_and_sign(const EVP_MD *md, EVP_PKEY *privkey, const void *data, size_t size, void **ret, size_t *ret_size); - -int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, const char *hash_algorithm, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si); - -int string_hashsum(const char *s, size_t len, const char *md_algorithm, char **ret); -static inline int string_hashsum_sha224(const char *s, size_t len, char **ret) { - return string_hashsum(s, len, "SHA224", ret); -} -static inline int string_hashsum_sha256(const char *s, size_t len, char **ret) { - return string_hashsum(s, len, "SHA256", ret); -} - -int x509_fingerprint(X509 *cert, uint8_t buffer[static X509_FINGERPRINT_SIZE]); - -int openssl_load_x509_certificate( - CertificateSourceType certificate_source_type, - const char *certificate_source, - const char *certificate, - X509 **ret); - -int openssl_load_private_key( - KeySourceType private_key_source_type, - const char *private_key_source, - const char *private_key, - const AskPasswordRequest *request, - EVP_PKEY **ret_private_key, - OpenSSLAskPasswordUI **ret_user_interface); - -int openssl_extract_public_key(EVP_PKEY *private_key, EVP_PKEY **ret); - -struct OpenSSLAskPasswordUI { - AskPasswordRequest request; -#ifndef OPENSSL_NO_UI_CONSOLE - UI_METHOD *method; -#endif -}; - -OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OpenSSLAskPasswordUI*, openssl_ask_password_ui_free, NULL); -#endif diff --git a/src/shared/options.c b/src/shared/options.c new file mode 100644 index 0000000000000..a4c6514508c43 --- /dev/null +++ b/src/shared/options.c @@ -0,0 +1,420 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "format-table.h" +#include "log.h" +#include "options.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" + +static bool option_takes_arg(const Option *opt) { + return ASSERT_PTR(opt)->metavar; +} + +static bool option_arg_optional(const Option *opt) { + return option_takes_arg(opt) && FLAGS_SET(opt->flags, OPTION_OPTIONAL_ARG); +} + +static bool option_arg_required(const Option *opt) { + return option_takes_arg(opt) && !FLAGS_SET(opt->flags, OPTION_OPTIONAL_ARG); +} + +static bool option_is_metadata(const Option *opt) { + /* A metadata entry that is not a real option, like the group marker */ + return ASSERT_PTR(opt)->flags & (OPTION_GROUP_MARKER | + OPTION_POSITIONAL_ENTRY | + OPTION_HELP_ENTRY | + OPTION_HELP_ENTRY_VERBATIM); +} + +static void shift_arg(char* argv[], int target, int source) { + assert(argv); + assert(target <= source); + + /* Move argv[source] before argv[target], shifting arguments inbetween */ + char *saved = argv[source]; + memmove(argv + target + 1, argv + target, (source - target) * sizeof(char*)); + argv[target] = saved; +} + +static int partial_match_error( + const Option options[], + const Option options_end[], + const char *optname, + unsigned n_partial_matches) { + int r; + + assert(startswith(ASSERT_PTR(optname), "--")); + assert(n_partial_matches >= 2); + + /* Find options that match the prefix */ + _cleanup_strv_free_ char **s = NULL; + for (const Option* option = options; option < options_end; option++) + if (!option_is_metadata(option) && + option->long_code && + startswith(option->long_code, optname + 2)) { + + r = strv_extendf(&s, "--%s", option->long_code); + if (r < 0) + return log_error_errno(r, "Failed to format message: %m"); + } + + assert(strv_length(s) == n_partial_matches); + + _cleanup_free_ char *p = strv_join_full(s, ", ", /* prefix= */ NULL, /* escape_separator= */ false); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: option '%s' is ambiguous; possibilities: %s", + program_invocation_short_name, optname, strnull(p)); +} + +int option_parse( + const Option options[], + const Option options_end[], + OptionParser *state, + const Option **ret_option, + const char **ret_arg) { + + /* Check and initialize */ + if (state->optind == 0) { + assert(state->mode >= 0 && state->mode < _OPTION_PARSER_MODE_MAX); + + if (state->argc < 1 || strv_isempty(state->argv)) + return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty"); + + state->optind = state->positional_offset = 1; + } + + /* Look for the next option */ + + const Option *option = NULL; /* initialization to appease gcc 13 */ + const char *optname = NULL, *optval = NULL; + _cleanup_free_ char *_optname = NULL; /* allocated option name */ + bool separate_optval = false; + bool handling_positional_arg = false; + + if (state->short_option_offset == 0) { + /* Handle non-option parameters */ + for (;;) { + if (state->optind == state->argc) + return 0; + + if (streq(state->argv[state->optind], "--")) { + /* No more options. Move "--" before positional args so that + * the list of positional args is clean. */ + shift_arg(state->argv, state->positional_offset++, state->optind++); + state->parsing_stopped = true; + } + + if (state->parsing_stopped) + return 0; + + if (state->argv[state->optind][0] == '-' && + state->argv[state->optind][1] != '\0') + /* Looks like we found an option parameter */ + break; + + if (state->mode == OPTION_PARSER_STOP_AT_FIRST_NONOPTION) { + state->parsing_stopped = true; + return 0; + } + + if (state->mode == OPTION_PARSER_RETURN_POSITIONAL_ARGS) { + handling_positional_arg = true; + optval = state->argv[state->optind]; + break; + } + + state->optind++; + } + + /* Find matching option entry. + * First, figure out if we have a long option or a short option. */ + assert(handling_positional_arg || state->argv[state->optind][0] == '-'); + + if (handling_positional_arg) + /* We are supposed to return the positional arg to be handled. */ + for (option = options;; option++) { + /* If OPTION_PARSER_RETURN_POSITIONAL_ARGS is specified, + * OPTION_POSITIONAL must be used. */ + assert(option < options_end); + + if (FLAGS_SET(option->flags, OPTION_POSITIONAL_ENTRY)) + break; + } + + else if (state->argv[state->optind][1] == '-') { + /* We have a long option. */ + char *eq = strchr(state->argv[state->optind], '='); + if (eq) { + optname = _optname = strndup(state->argv[state->optind], eq - state->argv[state->optind]); + if (!_optname) + return log_oom(); + + /* joined argument */ + optval = eq + 1; + } else + /* argument (if any) is separate */ + optname = state->argv[state->optind]; + + const Option *last_partial = NULL; + unsigned n_partial_matches = 0; /* The commandline option matches a defined prefix. */ + + for (option = options;; option++) { + if (option >= options_end) { + if (n_partial_matches == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: unrecognized option '%s'", + program_invocation_short_name, optname); + if (n_partial_matches > 1) + return partial_match_error(options, options_end, optname, n_partial_matches); + + /* just one partial — good */ + option = last_partial; + break; + } + + if (option_is_metadata(option) || !option->long_code) + continue; + + /* Check if the parameter forms a prefix of the option name */ + const char *rest = startswith(option->long_code, optname + 2); + if (!rest) + continue; + if (isempty(rest)) + /* exact match */ + break; + /* partial match */ + last_partial = option; + n_partial_matches++; + } + } else + /* We have a short option */ + state->short_option_offset = 1; + } + + if (state->short_option_offset > 0) { + char optchar = state->argv[state->optind][state->short_option_offset]; + + if (asprintf(&_optname, "-%c", optchar) < 0) + return log_oom(); + optname = _optname; + + for (option = options;; option++) { + if (option >= options_end) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: unrecognized option '%s'", + program_invocation_short_name, optname); + + if (option_is_metadata(option) || optchar != option->short_code) + continue; + + const char *rest = state->argv[state->optind] + state->short_option_offset + 1; + + if (option_takes_arg(option) && !isempty(rest)) { + /* The rest of this parameter is the value. */ + optval = rest; + state->short_option_offset = 0; + } else if (isempty(rest)) + state->short_option_offset = 0; + else + state->short_option_offset++; + + break; + } + } + + assert(option); + + if (!handling_positional_arg && optval && !option_takes_arg(option)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: option '%s' doesn't allow an argument", + program_invocation_short_name, optname); + if (!handling_positional_arg && !optval && option_arg_required(option)) { + if (!state->argv[state->optind + 1]) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: option '%s' requires an argument", + program_invocation_short_name, optname); + optval = state->argv[state->optind + 1]; + separate_optval = true; + } + + if (state->short_option_offset == 0) { + /* We're done with this parameter. Adjust the array and position. */ + if (handling_positional_arg) { + /* Sanity check */ + assert(state->positional_offset == state->optind); + assert(!separate_optval); + } + + shift_arg(state->argv, state->positional_offset++, state->optind++); + if (separate_optval) + shift_arg(state->argv, state->positional_offset++, state->optind++); + } + + if (FLAGS_SET(option->flags, OPTION_STOPS_PARSING)) + state->parsing_stopped = true; + + if (ret_option) + /* Return the matched Option structure to allow the caller to "know" what was matched */ + *ret_option = option; + + if (ret_arg) + *ret_arg = optval; + else + /* It's fine to omit ret_arg, but only if no options return a value. */ + assert(!optval); + + return option->id; +} + +char* option_parser_next_arg(const OptionParser *state) { + /* Peek at the next argument, whatever it is (option or position arg). + * May return NULL. */ + + assert(state->optind > 0); + assert(state->positional_offset <= state->argc); + + return state->optind < state->argc ? state->argv[state->optind] : NULL; +} + +char* option_parser_consume_next_arg(OptionParser *state) { + /* "Take" the next argument, whatever it is (option or position arg). + * The argument remains in the array, but the optind pointer is moved + * so we won't try to interpret it as an option. + * May return NULL. */ + + char *t = option_parser_next_arg(state); + if (t) + shift_arg(state->argv, state->positional_offset++, state->optind++); + return t; +} + +char** option_parser_get_args(const OptionParser *state) { + /* Returns positional args as a strv. + * If "--" was found, it has been moved before state->positional_offset. + * The array is only valid, i.e. clean without any options, after parsing + * has naturally finished. The array that is returned is a slice of the + * original argv array, so it must not be freed or modified. */ + + assert(state->optind > 0); + assert(state->optind == state->argc || state->parsing_stopped); + assert(state->positional_offset <= state->argc); + + return state->argv + state->positional_offset; +} + +size_t option_parser_get_n_args(const OptionParser *state) { + assert(state->optind > 0); + assert(state->optind == state->argc || state->parsing_stopped); + assert(state->positional_offset <= state->argc); + + return state->argc - state->positional_offset; +} + +char* option_get_synopsis(const char *prefix, const Option *opt, const char *joiner, bool show_metavar) { + assert(opt); + assert(!FLAGS_SET(opt->flags, OPTION_GROUP_MARKER)); /* A group marker should not be displayed */ + + if (!prefix) + prefix = ""; + + if (opt->flags & (OPTION_HELP_ENTRY_VERBATIM | OPTION_POSITIONAL_ENTRY)) + return strjoin(prefix, ASSERT_PTR(opt->long_code)); + + /* The option formatted appropriately for --help strings, error messages, and similar: + * ---=[] + * "=" is shown only when a long form is defined: -l --long=ARG, --long=ARG, -s ARG. + * The joiner arg is used between the short and long forms. + * As a special case, if the option has no long form and show_metavar is true, + * a space is used ('-a ARG' or '-a [ARG]'). + */ + assert(opt->short_code != 0 || opt->long_code); + + char sc[3] = ""; + if (opt->short_code != 0) + xsprintf(sc, "-%c", opt->short_code); + + if (show_metavar && opt->metavar && !opt->long_code) + joiner = " "; /* Return '-x ARG', no matter what joiner was specified. */ + else if (opt->short_code == 0 || !opt->long_code) + joiner = ""; + else if (!joiner) + joiner = " "; + + bool need_eq = option_takes_arg(opt) && opt->long_code; + if (!show_metavar) + return strjoin(prefix, + sc, + joiner, + opt->long_code ? "--" : "", + strempty(opt->long_code), + need_eq ? "=" : ""); + + bool need_quote = opt->metavar && strchr(opt->metavar, ' '); + return strjoin(prefix, + sc, + joiner, + opt->long_code ? "--" : "", + strempty(opt->long_code), + option_arg_optional(opt) ? "[" : "", + need_eq ? "=" : "", + need_quote ? "'" : "", + strempty(opt->metavar), + need_quote ? "'" : "", + option_arg_optional(opt) ? "]" : ""); +} + +int _option_parser_get_help_table( + const Option options[], + const Option options_end[], + const char *group, + Table **ret) { + int r; + + assert(ret); + + _cleanup_(table_unrefp) Table *table = table_new("names", "help"); + if (!table) + return log_oom(); + + bool in_group = group == NULL; /* Are we currently in the section on the array that forms + * group ? The first part is the default group, so + * if the group was not specified, we are in. */ + + for (const Option *opt = options; opt < options_end; opt++) { + bool group_marker = FLAGS_SET(opt->flags, OPTION_GROUP_MARKER); + if (!in_group) { + in_group = group_marker && streq(group, opt->long_code); + continue; + } + if (group_marker) + break; /* End of group */ + + if (!opt->help) + /* No help string — we do not show the option */ + continue; + + /* We indent the option string by two spaces. We could set the minimum cell width and + * right-align for a similar result, but that'd be more work. This is only used for + * display. */ + _cleanup_free_ char *s = option_get_synopsis(" ", opt, " ", /* show_metavar= */ true); + if (!s) + return log_oom(); + + r = table_add_many(table, TABLE_STRING, s); + if (r < 0) + return table_log_add_error(r); + + _cleanup_strv_free_ char **t = strv_split(opt->help, /* separators= */ NULL); + if (!t) + return log_oom(); + + r = table_add_many(table, TABLE_STRV_WRAPPED, t); + if (r < 0) + return table_log_add_error(r); + } + + table_set_header(table, false); + *ret = TAKE_PTR(table); + return 0; +} diff --git a/src/shared/options.h b/src/shared/options.h new file mode 100644 index 0000000000000..59b20bc047cd3 --- /dev/null +++ b/src/shared/options.h @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "memory-util.h" +#include "shared-forward.h" + +typedef enum OptionFlags { + OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */ + OPTION_POSITIONAL_ENTRY = 1U << 1, /* The "option" to handle positional arguments */ + OPTION_STOPS_PARSING = 1U << 2, /* This option acts like "--" */ + OPTION_GROUP_MARKER = 1U << 3, /* Fake option entry to separate groups */ + OPTION_HELP_ENTRY = 1U << 4, /* Fake option entry to insert an additional help line */ + OPTION_HELP_ENTRY_VERBATIM = 1U << 5, /* Same, but use the long_code in the first column as written */ +} OptionFlags; + +typedef struct Option { + int id; + OptionFlags flags; + char short_code; + const char *long_code; + const char *metavar; + uintptr_t data; + const char *help; +} Option; + +#define _OPTION(counter, fl, sc, lc, mv, d, h) \ + _section_("SYSTEMD_OPTIONS") \ + _alignptr_ \ + _used_ \ + _retain_ \ + _no_reorder_ \ + _variable_no_sanitize_address_ \ + static const Option CONCATENATE(option, counter) = { \ + .id = 0x100 + counter, \ + .flags = fl, \ + .short_code = sc, \ + .long_code = lc, \ + .metavar = mv, \ + .data = d, \ + .help = h, \ + }; \ + case (0x100 + counter) + +/* Magic entry in the table (which will not be returned) that designates the start of the group . + * The define is structured as 'case' so that it can be followed by ':' and indented appropriately. + */ +#define OPTION_GROUP(gr) \ + _OPTION(__COUNTER__, OPTION_GROUP_MARKER, /* sc= */ 0, /* lc= */ gr, /* mv= */ NULL, /* d= */ 0u, /* h= */ NULL) + +#define OPTION_FULL_DATA(fl, sc, lc, mv, d, h) _OPTION(__COUNTER__, fl, sc, lc, mv, d, h) +#define OPTION_FULL(fl, sc, lc, mv, h) OPTION_FULL_DATA(fl, sc, lc, mv, /* d= */ 0u, h) +#define OPTION(sc, lc, mv, h) OPTION_FULL(/* fl= */ 0, sc, lc, mv, h) +#define OPTION_LONG(lc, mv, h) OPTION(/* sc= */ 0, lc, mv, h) +#define OPTION_LONG_FLAGS(fl, lc, mv, h) OPTION_FULL(fl, /* sc= */ 0, lc, mv, h) +#define OPTION_LONG_DATA(lc, mv, d, h) OPTION_FULL_DATA(/* fl= */ 0, /* sc= */ 0, lc, mv, d, h) +#define OPTION_SHORT(sc, mv, h) OPTION(sc, /* lc= */ NULL, mv, h) +#define OPTION_SHORT_FLAGS(fl, sc, mv, h) OPTION_FULL(fl, sc, /* lc= */ NULL, mv, h) +#define OPTION_SHORT_DATA(sc, mv, d, h) OPTION_FULL_DATA(/* fl= */ 0, sc, /* lc= */ NULL, mv, d, h) +#define OPTION_POSITIONAL OPTION_FULL(OPTION_POSITIONAL_ENTRY, /* sc= */ 0, "(positional)", /* mv= */ NULL, /* h= */ NULL) +#define OPTION_HELP_VERBATIM(lc, h) OPTION_FULL(OPTION_HELP_ENTRY_VERBATIM, /* sc= */ 0, lc, /* mv= */ NULL, h) + +#define OPTION_COMMON_HELP \ + OPTION('h', "help", NULL, "Show this help") +#define OPTION_COMMON_VERSION \ + OPTION_LONG("version", NULL, "Show package version") +#define OPTION_COMMON_NO_PAGER \ + OPTION_LONG("no-pager", NULL, "Do not start a pager") +#define OPTION_COMMON_NO_LEGEND \ + OPTION_LONG("no-legend", NULL, "Do not show headers and footers") +#define OPTION_COMMON_LOG_LEVEL \ + OPTION_LONG("log-level", "LEVEL", \ + "Set log level (debug, info, notice, warning, err, crit, alert, emerg)") +#define OPTION_COMMON_LOG_TARGET \ + OPTION_LONG("log-target", "TARGET", \ + "Set log target (console, journal, journal-or-kmsg, kmsg, null)") +#define OPTION_COMMON_LOG_COLOR \ + OPTION_LONG("log-color", "BOOL", "Highlight important messages") +#define OPTION_COMMON_LOG_LOCATION \ + OPTION_LONG("log-location", "BOOL", "Include code location in messages") +#define OPTION_COMMON_LOG_TIME \ + OPTION_LONG("log-time", "BOOL", "Prefix messages with current time") +#define OPTION_COMMON_CAT_CONFIG \ + OPTION_LONG("cat-config", NULL, "Show configuration files") +#define OPTION_COMMON_TLDR \ + OPTION_LONG("tldr", NULL, "Show non-comment parts of configuration") +#define OPTION_COMMON_NO_ASK_PASSWORD \ + OPTION_LONG("no-ask-password", NULL, "Do not prompt for password") +#define OPTION_COMMON_HOST \ + OPTION('H', "host", "[USER@]HOST", "Operate on remote host") +#define OPTION_COMMON_MACHINE \ + OPTION('M', "machine", "CONTAINER", "Operate on local container") +#define OPTION_COMMON_JSON \ + OPTION_LONG("json", "FORMAT", "Generate JSON output (pretty, short, or off)") +#define OPTION_COMMON_LOWERCASE_J \ + OPTION_SHORT('j', NULL, \ + "Equivalent to --json=pretty (on TTY) or --json=short (otherwise)") +#define OPTION_COMMON_PRIVATE_KEY(purpose) \ + OPTION_LONG("private-key", "PATH|URI", purpose) +#define OPTION_COMMON_PRIVATE_KEY_SOURCE \ + OPTION_LONG("private-key-source", "SOURCE", \ + "Specify how to use the private key " \ + "(file, provider:PROVIDER, engine:ENGINE)") +#define OPTION_COMMON_CERTIFICATE(purpose) \ + OPTION_LONG("certificate", "PATH|URI", purpose \ + ", or a provider-specific designation if --certificate-source= is used") +#define OPTION_COMMON_CERTIFICATE_SOURCE \ + OPTION_LONG("certificate-source", "SOURCE", \ + "Specify how to interpret the certificate from --certificate=. " \ + "Allows the certificate to be loaded from an OpenSSL provider " \ + "(file, provider:PROVIDER)") + +/* A form used in udev code for compatibility. -V is accepted but not documented. */ +#define OPTION_COMMON_VERSION_WITH_HIDDEN_V \ + OPTION_COMMON_VERSION: {} \ + OPTION_SHORT('V', NULL, /* help= */ NULL) + + +/* This is magically mapped to the beginning and end of the section */ +extern const Option __start_SYSTEMD_OPTIONS[]; +extern const Option __stop_SYSTEMD_OPTIONS[]; + +typedef enum OptionParserMode { + /* The default mode. This is the implicit default and doesn't have to be specified. */ + OPTION_PARSER_NORMAL = 0, + + /* Same as "+…" for getopt_long — only parse options before the first positional argument. */ + OPTION_PARSER_STOP_AT_FIRST_NONOPTION, + + /* Same as "-…" for getopt_long — return positional arguments as "options" to be handled by the + * option handler specified with OPTION_POSITIONAL. */ + OPTION_PARSER_RETURN_POSITIONAL_ARGS, + + _OPTION_PARSER_MODE_MAX, +} OptionParserMode; + +typedef struct OptionParser { + /* Those three should stay first so that it's possible to initialize the struct as { argc, argv } + * or { argc, argv, mode }. */ + int argc; /* The original argc. */ + char **argv; /* The argv array, possibly reordered. */ + OptionParserMode mode; + + bool parsing_stopped; /* We processed "--" or an option that terminates option parsing. */ + int optind; /* Position of the parameter being handled. + * 0 → option parsing hasn't been started yet. */ + int short_option_offset; /* Set when we're parsing an argument with one or more short options. + * 0 → we're not parsing short options. */ + int positional_offset; /* Offset to where positional parameters are. After processing has been + * finished, all options and their args are to the left of this offset. */ +} OptionParser; + +int option_parse( + const Option options[], + const Option options_end[], + OptionParser *state, + const Option **ret_option, + const char **ret_arg); + +/* Iterate over options. */ +#define FOREACH_OPTION_FULL(parser, opt, ret_o, ret_a, on_error) \ + for (int opt; (opt = option_parse(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, parser, ret_o, ret_a)) != 0; ) \ + if (opt < 0) { \ + on_error; \ + break; \ + } else + +#define FOREACH_OPTION(parser, opt, ret_a, on_error) \ + FOREACH_OPTION_FULL(parser, opt, /* ret_o= */ NULL, ret_a, on_error) + +char* option_parser_next_arg(const OptionParser *state); +char* option_parser_consume_next_arg(OptionParser *state); + +char** option_parser_get_args(const OptionParser *state); +size_t option_parser_get_n_args(const OptionParser *state); +char* option_get_synopsis(const char *prefix, const Option *opt, const char *joiner, bool show_metavar); + +int _option_parser_get_help_table( + const Option options[], + const Option options_end[], + const char *group, + Table **ret); +#define option_parser_get_help_table_group(group, ret) \ + _option_parser_get_help_table(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, group, ret) +#define option_parser_get_help_table(ret) \ + option_parser_get_help_table_group(/* group= */ NULL, ret) diff --git a/src/shared/pam-util.c b/src/shared/pam-util.c index d6158ec8caec4..9728c3e00da07 100644 --- a/src/shared/pam-util.c +++ b/src/shared/pam-util.c @@ -1,17 +1,21 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "log.h" +#include "pam-util.h" + +#if HAVE_PAM + #include #include #include "sd-bus.h" +#include "sd-dlopen.h" #include "alloc-util.h" #include "bus-internal.h" #include "errno-util.h" #include "fd-util.h" #include "format-util.h" -#include "log.h" -#include "pam-util.h" #include "process-util.h" #include "stdio-util.h" #include "string-util.h" @@ -34,33 +38,6 @@ DLSYM_PROTOTYPE(pam_strerror) = NULL; DLSYM_PROTOTYPE(pam_syslog) = NULL; DLSYM_PROTOTYPE(pam_vsyslog) = NULL; -int dlopen_libpam(void) { - ELF_NOTE_DLOPEN("pam", - "Support for LinuxPAM", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libpam.so.0"); - - return dlopen_many_sym_or_warn( - &libpam_dl, - "libpam.so.0", - LOG_DEBUG, - DLSYM_ARG(pam_acct_mgmt), - DLSYM_ARG(pam_close_session), - DLSYM_ARG(pam_end), - DLSYM_ARG(pam_get_data), - DLSYM_ARG(pam_get_item), - DLSYM_ARG(pam_getenvlist), - DLSYM_ARG(pam_open_session), - DLSYM_ARG(pam_putenv), - DLSYM_ARG(pam_set_data), - DLSYM_ARG(pam_set_item), - DLSYM_ARG(pam_setcred), - DLSYM_ARG(pam_start), - DLSYM_ARG(pam_strerror), - DLSYM_ARG(pam_syslog), - DLSYM_ARG(pam_vsyslog)); -} - void pam_log_setup(void) { /* Make sure we don't leak the syslog fd we open by opening/closing the fd each time. */ log_set_open_when_needed(true); @@ -383,3 +360,38 @@ int pam_prompt_graceful(pam_handle_t *pamh, int style, char **ret_response, cons return PAM_SUCCESS; } + +#endif + +int dlopen_libpam(int log_level) { +#if HAVE_PAM + SD_ELF_NOTE_DLOPEN( + "pam", + "Support for LinuxPAM", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libpam.so.0"); + + return dlopen_many_sym_or_warn( + &libpam_dl, + "libpam.so.0", + log_level, + DLSYM_ARG(pam_acct_mgmt), + DLSYM_ARG(pam_close_session), + DLSYM_ARG(pam_end), + DLSYM_ARG(pam_get_data), + DLSYM_ARG(pam_get_item), + DLSYM_ARG(pam_getenvlist), + DLSYM_ARG(pam_open_session), + DLSYM_ARG(pam_putenv), + DLSYM_ARG(pam_set_data), + DLSYM_ARG(pam_set_item), + DLSYM_ARG(pam_setcred), + DLSYM_ARG(pam_start), + DLSYM_ARG(pam_strerror), + DLSYM_ARG(pam_syslog), + DLSYM_ARG(pam_vsyslog)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libpam support is not compiled in."); +#endif +} diff --git a/src/shared/pam-util.h b/src/shared/pam-util.h index 2464e144fb4e7..96d522d8f9bfb 100644 --- a/src/shared/pam-util.h +++ b/src/shared/pam-util.h @@ -27,8 +27,6 @@ extern DLSYM_PROTOTYPE(pam_strerror); extern DLSYM_PROTOTYPE(pam_syslog); extern DLSYM_PROTOTYPE(pam_vsyslog); -int dlopen_libpam(void); - void pam_log_setup(void); int errno_to_pam_error(int error) _const_; @@ -92,10 +90,6 @@ int pam_get_data_many_internal(pam_handle_t *pamh, ...) _sentinel_; int pam_prompt_graceful(pam_handle_t *pamh, int style, char **ret_response, const char *fmt, ...) _printf_(4,5); -#else - -static inline int dlopen_libpam(void) { - return -EOPNOTSUPP; -} - #endif + +int dlopen_libpam(int log_level); diff --git a/src/shared/parse-argument.c b/src/shared/parse-argument.c index 39e5328e7037e..0d4bfa4c91e69 100644 --- a/src/shared/parse-argument.c +++ b/src/shared/parse-argument.c @@ -79,6 +79,8 @@ int parse_path_argument(const char *path, bool suppress_root, char **arg) { char *p; int r; + assert(arg); + /* * This function is intended to be used in command line parsers, to handle paths that are passed * in. It makes the path absolute, and reduces it to NULL if omitted or root (the latter optionally). @@ -128,11 +130,7 @@ int parse_signal_argument(const char *s, int *ret) { return table_log_add_error(r); } - r = table_print(table, NULL); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } r = signal_from_string(s); diff --git a/src/shared/parse-helpers.c b/src/shared/parse-helpers.c index 8a61f2e66997b..da4b9fd25a397 100644 --- a/src/shared/parse-helpers.c +++ b/src/shared/parse-helpers.c @@ -11,6 +11,7 @@ #include "parse-helpers.h" #include "parse-util.h" #include "path-util.h" +#include "set.h" #include "string-util.h" #include "utf8.h" @@ -86,6 +87,63 @@ int path_simplify_and_warn( return 0; } +int parse_address_families(const char *rvalue, Set **families, bool *is_allowlist) { + bool invert = false; + int r; + + assert(rvalue); + assert(families); + assert(is_allowlist); + + if (isempty(rvalue)) { + *families = set_free(*families); + *is_allowlist = false; + return 0; + } + + if (streq(rvalue, "none")) { + *families = set_free(*families); + *is_allowlist = true; + return 0; + } + + if (rvalue[0] == '~') { + invert = true; + rvalue++; + } + + if (!*families) { + *families = set_new(NULL); + if (!*families) + return -ENOMEM; + + *is_allowlist = !invert; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE); + if (r == 0) + return 0; + if (r < 0) + return r; + + int af = af_from_name(word); + if (af < 0) + return af; + + /* If we previously wanted to forbid an address family and now we want to allow it, then + * just remove it from the list. */ + if (invert != *is_allowlist) { + r = set_put(*families, INT_TO_PTR(af)); + if (r < 0) + return r; + } else + set_remove(*families, INT_TO_PTR(af)); + } +} + static int parse_af_token( const char *token, int *family, diff --git a/src/shared/parse-helpers.h b/src/shared/parse-helpers.h index 402147cbf38a5..a906dfdaefdb5 100644 --- a/src/shared/parse-helpers.h +++ b/src/shared/parse-helpers.h @@ -20,6 +20,8 @@ int path_simplify_and_warn( unsigned line, const char *lvalue); +int parse_address_families(const char *rvalue, Set **families, bool *is_allowlist); + int parse_socket_bind_item( const char *str, int *address_family, diff --git a/src/shared/password-quality-util-passwdqc.c b/src/shared/password-quality-util-passwdqc.c index 31b84c6a0f926..6ce858c744622 100644 --- a/src/shared/password-quality-util-passwdqc.c +++ b/src/shared/password-quality-util-passwdqc.c @@ -2,25 +2,28 @@ #include "password-quality-util-passwdqc.h" +#include "errno-util.h" /* IWYU pragma: keep */ +#include "log.h" /* IWYU pragma: keep */ + #if HAVE_PASSWDQC #include +#include "sd-dlopen.h" + #include "alloc-util.h" #include "dlfcn-util.h" -#include "errno-util.h" -#include "log.h" #include "memory-util.h" #include "strv.h" static void *passwdqc_dl = NULL; -DLSYM_PROTOTYPE(passwdqc_params_reset) = NULL; -DLSYM_PROTOTYPE(passwdqc_params_load) = NULL; -DLSYM_PROTOTYPE(passwdqc_params_parse) = NULL; -DLSYM_PROTOTYPE(passwdqc_params_free) = NULL; -DLSYM_PROTOTYPE(passwdqc_check) = NULL; -DLSYM_PROTOTYPE(passwdqc_random) = NULL; +static DLSYM_PROTOTYPE(passwdqc_params_reset) = NULL; +static DLSYM_PROTOTYPE(passwdqc_params_load) = NULL; +static DLSYM_PROTOTYPE(passwdqc_params_parse) = NULL; +static DLSYM_PROTOTYPE(passwdqc_params_free) = NULL; +static DLSYM_PROTOTYPE(passwdqc_check) = NULL; +static DLSYM_PROTOTYPE(passwdqc_random) = NULL; DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(passwdqc_params_t*, sym_passwdqc_params_free, passwdqc_params_freep, NULL); @@ -32,7 +35,7 @@ static int pwqc_allocate_context(passwdqc_params_t **ret) { assert(ret); - r = dlopen_passwdqc(); + r = dlopen_passwdqc(LOG_DEBUG); if (r < 0) return r; @@ -135,15 +138,16 @@ int check_password_quality( #endif -int dlopen_passwdqc(void) { +int dlopen_passwdqc(int log_level) { #if HAVE_PASSWDQC - ELF_NOTE_DLOPEN("passwdqc", + SD_ELF_NOTE_DLOPEN( + "passwdqc", "Support for password quality checks", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libpasswdqc.so.1"); return dlopen_many_sym_or_warn( - &passwdqc_dl, "libpasswdqc.so.1", LOG_DEBUG, + &passwdqc_dl, "libpasswdqc.so.1", log_level, DLSYM_ARG(passwdqc_params_reset), DLSYM_ARG(passwdqc_params_load), DLSYM_ARG(passwdqc_params_parse), @@ -151,6 +155,7 @@ int dlopen_passwdqc(void) { DLSYM_ARG(passwdqc_check), DLSYM_ARG(passwdqc_random)); #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libpasswdqc support is not compiled in."); #endif } diff --git a/src/shared/password-quality-util-passwdqc.h b/src/shared/password-quality-util-passwdqc.h index e813829b63bec..2ae00aa620b86 100644 --- a/src/shared/password-quality-util-passwdqc.h +++ b/src/shared/password-quality-util-passwdqc.h @@ -8,4 +8,4 @@ int suggest_passwords(void); int check_password_quality(const char *password, const char *old, const char *username, char **ret_error); #endif -int dlopen_passwdqc(void); +int dlopen_passwdqc(int log_level); diff --git a/src/shared/password-quality-util-pwquality.c b/src/shared/password-quality-util-pwquality.c index 33cd16227199a..dd0cdcfa136a5 100644 --- a/src/shared/password-quality-util-pwquality.c +++ b/src/shared/password-quality-util-pwquality.c @@ -2,30 +2,33 @@ #include "password-quality-util-pwquality.h" +#include "errno-util.h" +#include "log.h" + #if HAVE_PWQUALITY #include #include #include +#include "sd-dlopen.h" + #include "alloc-util.h" #include "dlfcn-util.h" -#include "errno-util.h" -#include "log.h" #include "password-quality-util.h" #include "string-util.h" #include "strv.h" static void *pwquality_dl = NULL; -DLSYM_PROTOTYPE(pwquality_check) = NULL; -DLSYM_PROTOTYPE(pwquality_default_settings) = NULL; -DLSYM_PROTOTYPE(pwquality_free_settings) = NULL; -DLSYM_PROTOTYPE(pwquality_generate) = NULL; -DLSYM_PROTOTYPE(pwquality_get_str_value) = NULL; -DLSYM_PROTOTYPE(pwquality_read_config) = NULL; -DLSYM_PROTOTYPE(pwquality_set_int_value) = NULL; -DLSYM_PROTOTYPE(pwquality_strerror) = NULL; +static DLSYM_PROTOTYPE(pwquality_check) = NULL; +static DLSYM_PROTOTYPE(pwquality_default_settings) = NULL; +static DLSYM_PROTOTYPE(pwquality_free_settings) = NULL; +static DLSYM_PROTOTYPE(pwquality_generate) = NULL; +static DLSYM_PROTOTYPE(pwquality_get_str_value) = NULL; +static DLSYM_PROTOTYPE(pwquality_read_config) = NULL; +static DLSYM_PROTOTYPE(pwquality_set_int_value) = NULL; +static DLSYM_PROTOTYPE(pwquality_strerror) = NULL; DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(pwquality_settings_t*, sym_pwquality_free_settings, pwquality_free_settingsp, NULL); @@ -70,7 +73,7 @@ static int pwq_allocate_context(pwquality_settings_t **ret) { assert(ret); - r = dlopen_pwquality(); + r = dlopen_pwquality(LOG_DEBUG); if (r < 0) return r; @@ -151,15 +154,16 @@ int check_password_quality(const char *password, const char *old, const char *us #endif -int dlopen_pwquality(void) { +int dlopen_pwquality(int log_level) { #if HAVE_PWQUALITY - ELF_NOTE_DLOPEN("pwquality", + SD_ELF_NOTE_DLOPEN( + "pwquality", "Support for password quality checks", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libpwquality.so.1"); return dlopen_many_sym_or_warn( - &pwquality_dl, "libpwquality.so.1", LOG_DEBUG, + &pwquality_dl, "libpwquality.so.1", log_level, DLSYM_ARG(pwquality_check), DLSYM_ARG(pwquality_default_settings), DLSYM_ARG(pwquality_free_settings), @@ -169,6 +173,7 @@ int dlopen_pwquality(void) { DLSYM_ARG(pwquality_set_int_value), DLSYM_ARG(pwquality_strerror)); #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libpwquality support is not compiled in."); #endif } diff --git a/src/shared/password-quality-util-pwquality.h b/src/shared/password-quality-util-pwquality.h index 829025a10d3d9..468734f590b17 100644 --- a/src/shared/password-quality-util-pwquality.h +++ b/src/shared/password-quality-util-pwquality.h @@ -8,4 +8,4 @@ int suggest_passwords(void); int check_password_quality(const char *password, const char *old, const char *username, char **ret_error); #endif -int dlopen_pwquality(void); +int dlopen_pwquality(int log_level); diff --git a/src/shared/pcre2-util.c b/src/shared/pcre2-util.c index 10a767442a4e4..3ac76c1f9c9b8 100644 --- a/src/shared/pcre2-util.c +++ b/src/shared/pcre2-util.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dlopen.h" + #include "dlfcn-util.h" #include "hash-funcs.h" #include "log.h" @@ -26,11 +28,12 @@ DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( const struct hash_ops pcre2_code_hash_ops_free = {}; #endif -int dlopen_pcre2(void) { +int dlopen_pcre2(int log_level) { #if HAVE_PCRE2 - ELF_NOTE_DLOPEN("pcre2", + SD_ELF_NOTE_DLOPEN( + "pcre2", "Support for regular expressions", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libpcre2-8.so.0"); /* So here's something weird: PCRE2 actually renames the symbols exported by the library via C @@ -42,7 +45,7 @@ int dlopen_pcre2(void) { * manually anymore. C is weird. 🤯 */ return dlopen_many_sym_or_warn( - &pcre2_dl, "libpcre2-8.so.0", LOG_ERR, + &pcre2_dl, "libpcre2-8.so.0", log_level, DLSYM_ARG(pcre2_match_data_create), DLSYM_ARG(pcre2_match_data_free), DLSYM_ARG(pcre2_code_free), @@ -51,7 +54,8 @@ int dlopen_pcre2(void) { DLSYM_ARG(pcre2_match), DLSYM_ARG(pcre2_get_ovector_pointer)); #else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "PCRE2 support is not compiled in."); + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "PCRE2 support is not compiled in."); #endif } @@ -64,7 +68,7 @@ int pattern_compile_and_log(const char *pattern, PatternCompileCase case_, pcre2 assert(pattern); - r = dlopen_pcre2(); + r = dlopen_pcre2(LOG_ERR); if (r < 0) return r; diff --git a/src/shared/pcre2-util.h b/src/shared/pcre2-util.h index 8c85c6199430a..ba8827fb0749b 100644 --- a/src/shared/pcre2-util.h +++ b/src/shared/pcre2-util.h @@ -44,4 +44,4 @@ typedef enum PatternCompileCase { int pattern_compile_and_log(const char *pattern, PatternCompileCase case_, pcre2_code **ret); int pattern_matches_and_log(pcre2_code *compiled_pattern, const char *message, size_t size, size_t *ret_ovec); -int dlopen_pcre2(void); +int dlopen_pcre2(int log_level); diff --git a/src/shared/pcrextend-util.c b/src/shared/pcrextend-util.c index 431db8cc157d8..79efdc4574e2f 100644 --- a/src/shared/pcrextend-util.c +++ b/src/shared/pcrextend-util.c @@ -18,8 +18,10 @@ #include "mountpoint-util.h" #include "pcrextend-util.h" #include "pkcs7-util.h" +#include "sha256.h" #include "string-util.h" #include "strv.h" +#include "tpm2-pcr.h" static int device_get_file_system_word( sd_device *d, @@ -35,7 +37,7 @@ static int device_get_file_system_word( assert(ret); #if HAVE_BLKID - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) return r; @@ -195,6 +197,7 @@ int pcrextend_verity_word( assert(name); assert(iovec_is_set(root_hash)); + assert(ret); _cleanup_free_ char *name_escaped = xescape(name, ":"); /* Avoid ambiguity around ":" */ if (!name_escaped) @@ -285,9 +288,76 @@ int pcrextend_verity_now( return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %s", error_id); } - log_debug("Measurement of '%s' into 'images' NvPCR completed.", word); + log_debug("Measurement of '%s' into 'verity' NvPCR completed.", word); return 1; #else return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring Verity root hashes and signatures."); #endif } + +#define IMDS_USERDATA_TRUNCATED_MAX 256U + +int pcrextend_imds_userdata_word(const struct iovec *data, char **ret) { + assert(iovec_is_set(data)); + assert(ret); + + /* We include both a hash of the complete user data, and a truncated version of the data in the word + * we measure. The former protects the actual data, the latter is useful for debugging. */ + + _cleanup_free_ char *hash = hexmem(SHA256_DIRECT(data->iov_base, data->iov_len), SHA256_DIGEST_SIZE); + if (!hash) + return log_oom(); + + _cleanup_free_ char *data_encoded = NULL; + if (base64mem_full(data->iov_base, MIN(data->iov_len, IMDS_USERDATA_TRUNCATED_MAX), /* line_break= */ SIZE_MAX, &data_encoded) < 0) + return log_oom(); + + _cleanup_free_ char *word = strjoin("imds-userdata:", hash, ":", data_encoded); + if (!word) + return log_oom(); + + *ret = TAKE_PTR(word); + return 0; +} + +int pcrextend_imds_userdata_now(const struct iovec *data) { + +#if HAVE_TPM2 + int r; + + _cleanup_free_ char *word = NULL; + r = pcrextend_imds_userdata_word(data, &word); + if (r < 0) + return r; + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.PCRExtend"); + if (r < 0) + return r; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.PCRExtend.Extend", + /* ret_reply= */ NULL, + &error_id, + SD_JSON_BUILD_PAIR_INTEGER("pcr", TPM2_PCR_KERNEL_CONFIG), + SD_JSON_BUILD_PAIR_STRING("text", word), + SD_JSON_BUILD_PAIR_STRING("eventType", "imds_userdata")); + if (r < 0) + return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %m"); + if (error_id) { + r = sd_varlink_error_to_errno(error_id, reply); + if (r != -EBADR) + return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %m"); + + return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %s", error_id); + } + + log_debug("Measurement of '%s' into PCR 12 completed.", word); + return 1; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring IMDS userdata."); +#endif +} diff --git a/src/shared/pcrextend-util.h b/src/shared/pcrextend-util.h index 00bc5b9b48dc7..eadc2d5cffc98 100644 --- a/src/shared/pcrextend-util.h +++ b/src/shared/pcrextend-util.h @@ -1,9 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include + int pcrextend_file_system_word(const char *path, char **ret, char **ret_normalized_path); int pcrextend_machine_id_word(char **ret); int pcrextend_product_id_word(char **ret); int pcrextend_verity_word(const char *name, const struct iovec *root_hash, const struct iovec *root_hash_sig, char **ret); +int pcrextend_imds_userdata_word(const struct iovec *data, char **ret); -int pcrextend_verity_now(const char *name, const struct iovec *root_hash,const struct iovec *root_hash_sig); +int pcrextend_verity_now(const char *name, const struct iovec *root_hash, const struct iovec *root_hash_sig); +int pcrextend_imds_userdata_now(const struct iovec *data); diff --git a/src/shared/pe-binary.c b/src/shared/pe-binary.c index f342801bea220..b4b180d701337 100644 --- a/src/shared/pe-binary.c +++ b/src/shared/pe-binary.c @@ -4,6 +4,7 @@ #include #include "alloc-util.h" +#include "crypto-util.h" #include "hexdecoct.h" #include "log.h" #include "pe-binary.h" @@ -325,7 +326,7 @@ static int hash_file(int fd, EVP_MD_CTX *md_ctx, uint64_t offset, uint64_t size) if ((size_t) n != m) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while hashing."); - if (EVP_DigestUpdate(md_ctx, buffer, m) != 1) + if (sym_EVP_DigestUpdate(md_ctx, buffer, m) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data."); offset += m; @@ -358,6 +359,10 @@ int pe_hash(int fd, assert(ret_hash_size); assert(ret_hash); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + if (fstat(fd, &st) < 0) return log_debug_errno(errno, "Failed to stat file: %m"); r = stat_verify_regular(&st); @@ -376,11 +381,11 @@ int pe_hash(int fd, if (!certificate_table) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks certificate table."); - mdctx = EVP_MD_CTX_new(); + mdctx = sym_EVP_MD_CTX_new(); if (!mdctx) return log_oom_debug(); - if (EVP_DigestInit_ex(mdctx, md, NULL) != 1) + if (sym_EVP_DigestInit_ex(mdctx, md, NULL) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest."); /* Everything from beginning of file to CheckSum field in PE header */ @@ -421,18 +426,18 @@ int pe_hash(int fd, if ((uint64_t) st.st_size > p) { if ((uint64_t) st.st_size - p < le32toh(certificate_table->Size)) - return log_debug_errno(errno, "No space for certificate table, refusing."); + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "No space for certificate table, refusing."); - r = hash_file(fd, mdctx, p, st.st_size - p - le32toh(certificate_table->Size)); + r = hash_file(fd, mdctx, p, (uint64_t) st.st_size - p - le32toh(certificate_table->Size)); if (r < 0) return r; /* If the file size is not a multiple of 8 bytes, pad the hash with zero bytes. */ - if (st.st_size % 8 != 0 && EVP_DigestUpdate(mdctx, (const uint8_t[8]) {}, 8 - (st.st_size % 8)) != 1) + if (st.st_size % 8 != 0 && sym_EVP_DigestUpdate(mdctx, (const uint8_t[8]) {}, 8 - (st.st_size % 8)) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data."); } - int hsz = EVP_MD_CTX_size(mdctx); + int hsz = sym_EVP_MD_CTX_get_size(mdctx); if (hsz < 0) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size."); @@ -441,7 +446,7 @@ int pe_hash(int fd, if (!hash) return log_oom_debug(); - if (EVP_DigestFinal_ex(mdctx, hash, &hash_size) != 1) + if (sym_EVP_DigestFinal_ex(mdctx, hash, &hash_size) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function."); assert(hash_size == (unsigned) hsz); @@ -525,6 +530,10 @@ int uki_hash(int fd, assert(ret_hashes); assert(ret_hash_size); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + r = pe_load_headers(fd, &dos_header, &pe_header); if (r < 0) return r; @@ -533,7 +542,7 @@ int uki_hash(int fd, if (r < 0) return r; - int hsz = EVP_MD_size(md); + int hsz = sym_EVP_MD_get_size(md); if (hsz < 0) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size."); @@ -553,11 +562,11 @@ int uki_hash(int fd, if (hashes[i]) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section"); - mdctx = EVP_MD_CTX_new(); + mdctx = sym_EVP_MD_CTX_new(); if (!mdctx) return log_oom_debug(); - if (EVP_DigestInit_ex(mdctx, md, NULL) != 1) + if (sym_EVP_DigestInit_ex(mdctx, md, NULL) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest."); r = hash_file(fd, mdctx, le32toh(section->PointerToRawData), MIN(le32toh(section->VirtualSize), le32toh(section->SizeOfRawData))); @@ -571,7 +580,7 @@ int uki_hash(int fd, while (remaining > 0) { size_t sz = MIN(sizeof(zeroes), remaining); - if (EVP_DigestUpdate(mdctx, zeroes, sz) != 1) + if (sym_EVP_DigestUpdate(mdctx, zeroes, sz) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data."); remaining -= sz; @@ -583,7 +592,7 @@ int uki_hash(int fd, return log_oom_debug(); unsigned hash_size = (unsigned) hsz; - if (EVP_DigestFinal_ex(mdctx, hashes[i], &hash_size) != 1) + if (sym_EVP_DigestFinal_ex(mdctx, hashes[i], &hash_size) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function."); assert(hash_size == (unsigned) hsz); @@ -592,7 +601,7 @@ int uki_hash(int fd, _cleanup_free_ char *hs = NULL; hs = hexmem(hashes[i], hsz); - log_debug("Section %s with %s is %s.", n, EVP_MD_name(md), strna(hs)); + log_debug("Section %s with %s is %s.", n, sym_EVP_MD_get0_name(md), strna(hs)); } } diff --git a/src/shared/pe-binary.h b/src/shared/pe-binary.h index 0f748a87d7d25..4b5dc243fe77f 100644 --- a/src/shared/pe-binary.h +++ b/src/shared/pe-binary.h @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "openssl-util.h" #include "sparse-endian.h" #include "uki.h" diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index 3062bcc554196..a4c5bb83f7d9e 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -1,14 +1,20 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#if HAVE_OPENSSL +# include +#endif + +#include "sd-dlopen.h" + #include "alloc-util.h" #include "ask-password-api.h" +#include "crypto-util.h" #include "dlfcn-util.h" #include "env-util.h" #include "escape.h" #include "format-table.h" #include "log.h" #include "memory-util.h" -#include "openssl-util.h" #include "pkcs11-util.h" #include "random-util.h" #include "string-util.h" @@ -63,7 +69,7 @@ int uri_from_string(const char *p, P11KitUri **ret) { assert(p); assert(ret); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -83,7 +89,7 @@ P11KitUri *uri_from_module_info(const CK_INFO *info) { assert(info); - if (dlopen_p11kit() < 0) + if (dlopen_p11kit(LOG_DEBUG) < 0) return NULL; uri = sym_p11_kit_uri_new(); @@ -99,7 +105,7 @@ P11KitUri *uri_from_slot_info(const CK_SLOT_INFO *slot_info) { assert(slot_info); - if (dlopen_p11kit() < 0) + if (dlopen_p11kit(LOG_DEBUG) < 0) return NULL; uri = sym_p11_kit_uri_new(); @@ -115,7 +121,7 @@ P11KitUri *uri_from_token_info(const CK_TOKEN_INFO *token_info) { assert(token_info); - if (dlopen_p11kit() < 0) + if (dlopen_p11kit(LOG_DEBUG) < 0) return NULL; uri = sym_p11_kit_uri_new(); @@ -219,7 +225,7 @@ int pkcs11_token_login_by_pin( assert(m); assert(token_info); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -281,7 +287,7 @@ int pkcs11_token_login( assert(m); assert(token_info); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -401,6 +407,8 @@ static int read_public_key_info( CK_OBJECT_HANDLE object, EVP_PKEY **ret_pkey) { + assert(ret_pkey); + CK_ATTRIBUTE attribute = { CKA_PUBLIC_KEY_INFO, NULL_PTR, 0 }; _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; CK_RV rv; @@ -425,7 +433,7 @@ static int read_public_key_info( "Failed to read CKA_PUBLIC_KEY_INFO: %s", sym_p11_kit_strerror(rv)); const unsigned char *value = attribute.pValue; - pkey = d2i_PUBKEY(NULL, &value, attribute.ulValueLen); + pkey = sym_d2i_PUBKEY(NULL, &value, attribute.ulValueLen); if (!pkey) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse CKA_PUBLIC_KEY_INFO"); @@ -443,6 +451,12 @@ int pkcs11_token_read_public_key( CK_RV rv; int r; + assert(ret_pkey); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + r = read_public_key_info(m, session, object, &pkey); if (r >= 0) { *ret_pkey = TAKE_PTR(pkey); @@ -537,63 +551,67 @@ int pkcs11_token_read_public_key( _cleanup_(ASN1_OCTET_STRING_freep) ASN1_OCTET_STRING *os = NULL; const unsigned char *ec_params_value = ec_attributes[0].pValue; - group = d2i_ECPKParameters(NULL, &ec_params_value, ec_attributes[0].ulValueLen); + group = sym_d2i_ECPKParameters(NULL, &ec_params_value, ec_attributes[0].ulValueLen); if (!group) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_PARAMS."); const unsigned char *ec_point_value = ec_attributes[1].pValue; - os = d2i_ASN1_OCTET_STRING(NULL, &ec_point_value, ec_attributes[1].ulValueLen); + os = sym_d2i_ASN1_OCTET_STRING(NULL, &ec_point_value, ec_attributes[1].ulValueLen); if (!os) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_POINT."); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); if (!ctx) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create an EVP_PKEY_CTX for EC."); - if (EVP_PKEY_fromdata_init(ctx) != 1) + if (sym_EVP_PKEY_fromdata_init(ctx) != 1) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to init an EVP_PKEY_CTX for EC."); OSSL_PARAM ec_params[8] = { - OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, os->data, os->length) + /* We need to drop the const from the data param, because ec_params is + * modified below. But we'll not modify ec_params[0]. */ + OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, + (unsigned char *) sym_ASN1_STRING_get0_data(os), + sym_ASN1_STRING_length(os)), }; _cleanup_free_ void *order = NULL, *p = NULL, *a = NULL, *b = NULL, *generator = NULL; size_t order_size, p_size, a_size, b_size, generator_size; - int nid = EC_GROUP_get_curve_name(group); + int nid = sym_EC_GROUP_get_curve_name(group); if (nid != NID_undef) { - const char* name = OSSL_EC_curve_nid2name(nid); - ec_params[1] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, (char*)name, strlen(name)); - ec_params[2] = OSSL_PARAM_construct_end(); + const char* name = sym_OSSL_EC_curve_nid2name(nid); + ec_params[1] = sym_OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, (char*)name, strlen(name)); + ec_params[2] = sym_OSSL_PARAM_construct_end(); } else { - const char *field_type = EC_GROUP_get_field_type(group) == NID_X9_62_prime_field ? + const char *field_type = sym_EC_GROUP_get_field_type(group) == NID_X9_62_prime_field ? "prime-field" : "characteristic-two-field"; - const BIGNUM *bn_order = EC_GROUP_get0_order(group); + const BIGNUM *bn_order = sym_EC_GROUP_get0_order(group); - _cleanup_(BN_CTX_freep) BN_CTX *bnctx = BN_CTX_new(); + _cleanup_(BN_CTX_freep) BN_CTX *bnctx = sym_BN_CTX_new(); if (!bnctx) return log_oom_debug(); - _cleanup_(BN_freep) BIGNUM *bn_p = BN_new(); + _cleanup_(BN_freep) BIGNUM *bn_p = sym_BN_new(); if (!bn_p) return log_oom_debug(); - _cleanup_(BN_freep) BIGNUM *bn_a = BN_new(); + _cleanup_(BN_freep) BIGNUM *bn_a = sym_BN_new(); if (!bn_a) return log_oom_debug(); - _cleanup_(BN_freep) BIGNUM *bn_b = BN_new(); + _cleanup_(BN_freep) BIGNUM *bn_b = sym_BN_new(); if (!bn_b) return log_oom_debug(); - if (EC_GROUP_get_curve(group, bn_p, bn_a, bn_b, bnctx) != 1) + if (sym_EC_GROUP_get_curve(group, bn_p, bn_a, bn_b, bnctx) != 1) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract EC parameters from EC_GROUP."); - order_size = BN_num_bytes(bn_order); - p_size = BN_num_bytes(bn_p); - a_size = BN_num_bytes(bn_a); - b_size = BN_num_bytes(bn_b); + order_size = sym_BN_num_bytes(bn_order); + p_size = sym_BN_num_bytes(bn_p); + a_size = sym_BN_num_bytes(bn_a); + b_size = sym_BN_num_bytes(bn_b); order = malloc(order_size); if (!order) @@ -611,14 +629,14 @@ int pkcs11_token_read_public_key( if (!b) return log_oom_debug(); - if (BN_bn2nativepad(bn_order, order, order_size) <= 0 || - BN_bn2nativepad(bn_p, p, p_size) <= 0 || - BN_bn2nativepad(bn_a, a, a_size) <= 0 || - BN_bn2nativepad(bn_b, b, b_size) <= 0 ) + if (sym_BN_bn2nativepad(bn_order, order, order_size) <= 0 || + sym_BN_bn2nativepad(bn_p, p, p_size) <= 0 || + sym_BN_bn2nativepad(bn_a, a, a_size) <= 0 || + sym_BN_bn2nativepad(bn_b, b, b_size) <= 0 ) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to store EC parameters in native byte order."); - const EC_POINT *point_gen = EC_GROUP_get0_generator(group); - generator_size = EC_POINT_point2oct(group, point_gen, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bnctx); + const EC_POINT *point_gen = sym_EC_GROUP_get0_generator(group); + generator_size = sym_EC_POINT_point2oct(group, point_gen, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bnctx); if (generator_size == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine size of a EC generator."); @@ -626,20 +644,20 @@ int pkcs11_token_read_public_key( if (!generator) return log_oom_debug(); - generator_size = EC_POINT_point2oct(group, point_gen, POINT_CONVERSION_UNCOMPRESSED, generator, generator_size, bnctx); + generator_size = sym_EC_POINT_point2oct(group, point_gen, POINT_CONVERSION_UNCOMPRESSED, generator, generator_size, bnctx); if (generator_size == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert a EC generator to octet string."); - ec_params[1] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_EC_FIELD_TYPE, (char*)field_type, strlen(field_type)); - ec_params[2] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_EC_GENERATOR, generator, generator_size); - ec_params[3] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_ORDER, order, order_size); - ec_params[4] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_P, p, p_size); - ec_params[5] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_A, a, a_size); - ec_params[6] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_B, b, b_size); - ec_params[7] = OSSL_PARAM_construct_end(); + ec_params[1] = sym_OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_EC_FIELD_TYPE, (char*)field_type, strlen(field_type)); + ec_params[2] = sym_OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_EC_GENERATOR, generator, generator_size); + ec_params[3] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_ORDER, order, order_size); + ec_params[4] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_P, p, p_size); + ec_params[5] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_A, a, a_size); + ec_params[6] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_B, b, b_size); + ec_params[7] = sym_OSSL_PARAM_construct_end(); } - if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, ec_params) != 1) + if (sym_EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, ec_params) != 1) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create EVP_PKEY from EC parameters."); break; } @@ -657,16 +675,15 @@ int pkcs11_token_read_x509_certificate( CK_OBJECT_HANDLE object, X509 **ret_cert) { - _cleanup_free_ char *t = NULL; CK_ATTRIBUTE attribute = { .type = CKA_VALUE }; CK_RV rv; - _cleanup_(X509_freep) X509 *x509 = NULL; - X509_NAME *name = NULL; int r; - r = dlopen_p11kit(); + assert(ret_cert); + + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -686,16 +703,20 @@ int pkcs11_token_read_x509_certificate( return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to read X.509 certificate data off token: %s", sym_p11_kit_strerror(rv)); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + const unsigned char *p = attribute.pValue; - x509 = d2i_X509(NULL, &p, attribute.ulValueLen); + _cleanup_(X509_freep) X509 *x509 = sym_d2i_X509(NULL, &p, attribute.ulValueLen); if (!x509) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse X.509 certificate."); - name = X509_get_subject_name(x509); + const X509_NAME *name = sym_X509_get_subject_name(x509); if (!name) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to acquire X.509 subject name."); - t = X509_NAME_oneline(name, NULL, 0); + _cleanup_free_ char *t = sym_X509_NAME_oneline(name, NULL, 0); if (!t) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to format X.509 subject name as string."); @@ -872,6 +893,8 @@ int pkcs11_token_find_related_object( CK_OBJECT_HANDLE objects[2]; CK_RV rv; + assert(ret_object); + rv = m->C_GetAttributeValue(session, prototype, attributes, ELEMENTSOF(attributes)); if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID)) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve length of attributes: %s", sym_p11_kit_strerror(rv)); @@ -947,6 +970,9 @@ static int ecc_convert_to_compressed( CK_RV rv; int r; + assert(ret_compressed_point); + assert(ret_compressed_point_size); + rv = m->C_GetAttributeValue(session, object, &ec_params_attr, 1); if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID)) return log_error_errno(SYNTHETIC_ERRNO(EIO), @@ -995,23 +1021,27 @@ static int ecc_convert_to_compressed( _cleanup_free_ void *compressed_point = NULL; size_t compressed_point_size; + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + const unsigned char *ec_params_value = ec_params_attr.pValue; - group = d2i_ECPKParameters(NULL, &ec_params_value, ec_params_attr.ulValueLen); + group = sym_d2i_ECPKParameters(NULL, &ec_params_value, ec_params_attr.ulValueLen); if (!group) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_PARAMS"); - point = EC_POINT_new(group); + point = sym_EC_POINT_new(group); if (!point) return log_oom(); - bnctx = BN_CTX_new(); + bnctx = sym_BN_CTX_new(); if (!bnctx) return log_oom(); - if (EC_POINT_oct2point(group, point, uncompressed_point, uncompressed_point_size, bnctx) != 1) + if (sym_EC_POINT_oct2point(group, point, uncompressed_point, uncompressed_point_size, bnctx) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode an uncompressed EC point"); - compressed_point_size = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, NULL, 0, bnctx); + compressed_point_size = sym_EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, NULL, 0, bnctx); if (compressed_point_size == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine size of a compressed EC point"); @@ -1019,7 +1049,7 @@ static int ecc_convert_to_compressed( if (!compressed_point) return log_oom(); - compressed_point_size = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, compressed_point, compressed_point_size, bnctx); + compressed_point_size = sym_EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, compressed_point, compressed_point_size, bnctx); if (compressed_point_size == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert a EC point to compressed format"); @@ -1070,6 +1100,9 @@ static int pkcs11_token_decrypt_data_ecc( int r; #endif + assert(ret_decrypted_data); + assert(ret_decrypted_data_size); + rv = m->C_GetSessionInfo(session, &session_info); if (rv != CKR_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), @@ -1150,6 +1183,9 @@ static int pkcs11_token_decrypt_data_rsa( CK_ULONG dbuffer_size = 0; CK_RV rv; + assert(ret_decrypted_data); + assert(ret_decrypted_data_size); + rv = m->C_DecryptInit(session, (CK_MECHANISM*) &mechanism, object); if (rv != CKR_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), @@ -1228,7 +1264,7 @@ int pkcs11_token_acquire_rng( assert(m); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -1314,7 +1350,7 @@ static int slot_process( assert(m); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -1397,7 +1433,7 @@ static int module_process( assert(m); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -1461,7 +1497,7 @@ int pkcs11_find_token( _cleanup_(p11_kit_uri_freep) P11KitUri *search_uri = NULL; int r; - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -1501,7 +1537,8 @@ struct pkcs11_acquire_public_key_callback_data { static void pkcs11_acquire_public_key_callback_data_release(struct pkcs11_acquire_public_key_callback_data *data) { erase_and_free(data->pin_used); - EVP_PKEY_free(data->pkey); + if (data->pkey) + sym_EVP_PKEY_free(data->pkey); } static int pkcs11_acquire_public_key_callback( @@ -1653,7 +1690,11 @@ static int pkcs11_acquire_public_key_callback( if (r < 0) return log_error_errno(r, "Failed to read a found X.509 certificate."); - pkey = X509_get_pubkey(cert); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + pkey = sym_X509_get_pubkey(cert); if (!pkey) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate."); } @@ -1720,7 +1761,7 @@ static int list_callback( assert(slot_info); assert(token_info); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -1764,16 +1805,17 @@ static int list_callback( } #endif -int dlopen_p11kit(void) { +int dlopen_p11kit(int log_level) { #if HAVE_P11KIT - ELF_NOTE_DLOPEN("p11-kit", + SD_ELF_NOTE_DLOPEN( + "p11-kit", "Support for PKCS11 hardware tokens", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libp11-kit.so.0"); return dlopen_many_sym_or_warn( &p11kit_dl, - "libp11-kit.so.0", LOG_DEBUG, + "libp11-kit.so.0", log_level, DLSYM_ARG(p11_kit_module_get_name), DLSYM_ARG(p11_kit_modules_finalize_and_release), DLSYM_ARG(p11_kit_modules_load_and_initialize), @@ -1791,7 +1833,8 @@ int dlopen_p11kit(void) { DLSYM_ARG(p11_kit_uri_new), DLSYM_ARG(p11_kit_uri_parse)); #else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "p11kit support is not compiled in."); + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libp11-kit support is not compiled in."); #endif } @@ -1813,11 +1856,7 @@ int pkcs11_list_tokens(void) { return 0; } - r = table_print(t, stdout); - if (r < 0) - return log_error_errno(r, "Failed to show device table: %m"); - - return 0; + return table_print_or_warn(t); #else return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "PKCS#11 tokens not supported on this build."); @@ -1841,7 +1880,7 @@ static int auto_callback( assert(slot_info); assert(token_info); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; diff --git a/src/shared/pkcs11-util.h b/src/shared/pkcs11-util.h index 51279eabc47ad..7864598180f62 100644 --- a/src/shared/pkcs11-util.h +++ b/src/shared/pkcs11-util.h @@ -1,10 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#if HAVE_OPENSSL -# include -#endif - #if HAVE_P11KIT # include /* IWYU pragma: export */ # include /* IWYU pragma: export */ @@ -106,7 +102,7 @@ int pkcs11_crypt_device_callback( #endif -int dlopen_p11kit(void); +int dlopen_p11kit(int log_level); typedef struct { const char *friendly_name; diff --git a/src/shared/pkcs7-util.c b/src/shared/pkcs7-util.c index 0a0e102adf7d9..4d22d90421a99 100644 --- a/src/shared/pkcs7-util.c +++ b/src/shared/pkcs7-util.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" -#include "openssl-util.h" +#include "crypto-util.h" #include "pkcs7-util.h" #include "log.h" @@ -28,6 +28,10 @@ int pkcs7_extract_signers( Signer **ret_signers, size_t *ret_n_signers) { +#if HAVE_OPENSSL + int r; +#endif + assert(ret_signers); assert(ret_n_signers); @@ -35,16 +39,20 @@ int pkcs7_extract_signers( return -EBADMSG; #if HAVE_OPENSSL + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + const unsigned char *d = sig->iov_base; _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL; - p7 = d2i_PKCS7(/* a= */ NULL, &d, (long) sig->iov_len); + p7 = sym_d2i_PKCS7(/* a= */ NULL, &d, (long) sig->iov_len); if (!p7) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse PKCS7 DER signature data."); - STACK_OF(PKCS7_SIGNER_INFO) *sinfos = PKCS7_get_signer_info(p7); + STACK_OF(PKCS7_SIGNER_INFO) *sinfos = sym_PKCS7_get_signer_info(p7); if (!sinfos) return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "No signature information in PKCS7 signature?"); - int n = sk_PKCS7_SIGNER_INFO_num(sinfos); + int n = sym_sk_PKCS7_SIGNER_INFO_num(sinfos); if (n == 0) return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "No signatures in PKCS7 signature, refusing."); if (n > SIGNERS_MAX) /* safety net, in case people send us weirdly complex signatures */ @@ -59,17 +67,17 @@ int pkcs7_extract_signers( CLEANUP_ARRAY(signers, n_signers, signer_free_many); for (int i = 0; i < n; i++) { - PKCS7_SIGNER_INFO *si = sk_PKCS7_SIGNER_INFO_value(PKCS7_get_signer_info(p7), i); + PKCS7_SIGNER_INFO *si = sym_sk_PKCS7_SIGNER_INFO_value(sym_PKCS7_get_signer_info(p7), i); if (!si) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get signer information."); _cleanup_(signer_done) Signer signer = {}; _cleanup_free_ unsigned char *p = NULL; - int len = i2d_X509_NAME(si->issuer_and_serial->issuer, &p); + int len = sym_i2d_X509_NAME(si->issuer_and_serial->issuer, &p); signer.issuer = IOVEC_MAKE(TAKE_PTR(p), len); - len = i2d_ASN1_INTEGER(si->issuer_and_serial->serial, &p); + len = sym_i2d_ASN1_INTEGER(si->issuer_and_serial->serial, &p); signer.serial = IOVEC_MAKE(TAKE_PTR(p), len); signers[n_signers++] = TAKE_STRUCT(signer); diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c index 7e04acbcfc111..7a4bc10eebf2a 100644 --- a/src/shared/pretty-print.c +++ b/src/shared/pretty-print.c @@ -100,6 +100,7 @@ int terminal_urlify(const char *url, const char *text, char **ret) { char *n; assert(url); + assert(ret); /* Takes a URL and a pretty string and formats it as clickable link for the terminal. See * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda for details. */ @@ -126,6 +127,8 @@ int file_url_from_path(const char *path, char **ret) { char *url = NULL; int r; + assert(ret); + if (uname(&u) < 0) return -errno; @@ -388,6 +391,12 @@ static int guess_type(const char **name, char ***ret_prefixes, bool *ret_is_coll _cleanup_free_ char *n = NULL; bool run = false, coll = false; const char *ext = ".conf"; + + assert(name); + assert(ret_prefixes); + assert(ret_is_collection); + assert(ret_extension); + /* This is static so that the array doesn't get deallocated when we exit the function */ static const char* const std_prefixes[] = { CONF_PATHS(""), NULL }; static const char* const run_prefixes[] = { "/run/", NULL }; @@ -485,7 +494,7 @@ int conf_files_cat(const char *root, const char *name, CatFlags flags) { /* Then locate the drop-ins, if any */ ConfFile **dropins = NULL; size_t n_dropins = 0; - CLEANUP_ARRAY(dropins, n_dropins, conf_file_free_many); + CLEANUP_ARRAY(dropins, n_dropins, conf_file_free_array); r = conf_files_list_strv_full(extension, root, CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED | CONF_FILES_WARN, (const char* const*) dirs, &dropins, &n_dropins); if (r < 0) return log_error_errno(r, "Failed to query file list: %m"); diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c index 2e8d77dee1c43..88cfd596d0508 100644 --- a/src/shared/ptyfwd.c +++ b/src/shared/ptyfwd.c @@ -669,19 +669,22 @@ static int do_shovel(PTYForward *f) { f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); } else { - /* Check if ^] has been pressed three times within one second. If we get this we quite - * immediately. */ - RequestOperation q = look_for_escape(f, f->in_buffer + f->in_buffer_full, k); - f->in_buffer_full += (size_t) k; - if (q < 0) - return q; - if (q == REQUEST_EXIT) - return -ECANCELED; - if (q >= REQUEST_HOTKEY_A && q <= REQUEST_HOTKEY_Z && f->hotkey_handler) { - r = f->hotkey_handler(f, q - REQUEST_HOTKEY_BASE, f->hotkey_userdata); - if (r < 0) - return r; - } + if (!FLAGS_SET(f->flags, PTY_FORWARD_TRANSPARENT)) { + /* Check if ^] has been pressed three times within one second. If we get this we quit + * immediately. */ + RequestOperation q = look_for_escape(f, f->in_buffer + f->in_buffer_full, k); + f->in_buffer_full += (size_t) k; + if (q < 0) + return q; + if (q == REQUEST_EXIT) + return -ECANCELED; + if (q >= REQUEST_HOTKEY_A && q <= REQUEST_HOTKEY_Z && f->hotkey_handler) { + r = f->hotkey_handler(f, q - REQUEST_HOTKEY_BASE, f->hotkey_userdata); + if (r < 0) + return r; + } + } else + f->in_buffer_full += (size_t) k; } did_something = true; diff --git a/src/shared/ptyfwd.h b/src/shared/ptyfwd.h index 1c1246f37f163..f92676dabe3e8 100644 --- a/src/shared/ptyfwd.h +++ b/src/shared/ptyfwd.h @@ -17,6 +17,9 @@ typedef enum PTYForwardFlags { /* Don't tint the background, or set window title */ PTY_FORWARD_DUMB_TERMINAL = 1 << 3, + + /* Don't interpret escape sequences (^] exit, hotkeys), just forward everything as-is */ + PTY_FORWARD_TRANSPARENT = 1 << 4, } PTYForwardFlags; typedef int (*PTYForwardHangupHandler)(PTYForward *f, int rcode, void *userdata); diff --git a/src/shared/qmp-client.c b/src/shared/qmp-client.c new file mode 100644 index 0000000000000..41b0c6dd57034 --- /dev/null +++ b/src/shared/qmp-client.c @@ -0,0 +1,932 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-event.h" +#include "sd-json.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "hash-funcs.h" +#include "json-stream.h" +#include "json-util.h" +#include "qmp-client.h" +#include "set.h" +#include "siphash24.h" +#include "string-util.h" + +typedef enum QmpClientState { + QMP_CLIENT_RUNNING, /* connection alive; qmp_capabilities may still be in flight */ + QMP_CLIENT_DISCONNECTED, /* connection closed */ + _QMP_CLIENT_STATE_MAX, + _QMP_CLIENT_STATE_INVALID = -EINVAL, +} QmpClientState; + +struct QmpSlot { + unsigned n_ref; + QmpClient *client; /* NULL once disconnected (reply dispatched, cancelled, or client died) */ + uint64_t id; + bool floating; + qmp_command_callback_t callback; + void *userdata; +}; + +struct QmpClient { + unsigned n_ref; + + JsonStream stream; + + sd_event_source *quit_event_source; + sd_event_source *defer_event_source; + + uint64_t next_id; + Set *slots; /* QmpSlot* entries indexed by id, for async dispatch */ + + qmp_event_callback_t event_callback; + void *event_userdata; + qmp_disconnect_callback_t disconnect_callback; + void *disconnect_userdata; + + uint64_t next_fdset_id; /* monotonic fdset-id allocator for add-fd */ + + QmpClientState state; + sd_json_variant *current; /* most recently parsed message, pending dispatch */ + + void *userdata; +}; + +static void qmp_slot_hash_func(const QmpSlot *p, struct siphash *state) { + siphash24_compress_typesafe(p->id, state); +} + +static int qmp_slot_compare_func(const QmpSlot *a, const QmpSlot *b) { + return CMP(a->id, b->id); +} + +DEFINE_PRIVATE_HASH_OPS(qmp_slot_hash_ops, + QmpSlot, qmp_slot_hash_func, qmp_slot_compare_func); + +/* Break the slot's connection to the client: remove from the lookup set, drop whichever reference + * is implied by the slot's floating-ness. For floating slots, the set is the sole owner, so with + * unref=true we also drop the slot's n_ref (usually dropping it to zero and freeing). For + * non-floating slots, we release the back-reference the slot holds on the client. + * + * Safe to call multiple times: once slot->client is NULL, subsequent calls are no-ops. */ +static void qmp_slot_disconnect(QmpSlot *slot, bool unref) { + assert(slot); + + if (!slot->client) + return; + + QmpClient *client = slot->client; + + set_remove(client->slots, slot); + slot->client = NULL; + + if (!slot->floating) + qmp_client_unref(client); + else if (unref) + /* May re-enter via qmp_slot_free→qmp_slot_disconnect(,false) if this drops the + * last ref, but the early return above makes that recursion a no-op. */ + qmp_slot_unref(slot); +} + +static QmpSlot* qmp_slot_free(QmpSlot *slot) { + if (!slot) + return NULL; + + /* Idempotent: if the slot was already disconnected (reply dispatched, explicit cancel, + * or client-side teardown), this is a no-op. Otherwise it removes us from the set and + * drops our client reference (for non-floating slots). */ + qmp_slot_disconnect(slot, /* unref= */ false); + + return mfree(slot); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(QmpSlot, qmp_slot, qmp_slot_free); + +QmpClient* qmp_slot_get_client(QmpSlot *slot) { + assert(slot); + return slot->client; +} + +static int qmp_slot_new( + QmpClient *client, + bool floating, + uint64_t id, + qmp_command_callback_t callback, + void *userdata, + QmpSlot **ret) { + + int r; + + assert(client); + assert(ret); + + _cleanup_(qmp_slot_unrefp) QmpSlot *slot = new(QmpSlot, 1); + if (!slot) + return -ENOMEM; + + *slot = (QmpSlot) { + .n_ref = 1, + .client = NULL, /* wired up below, after set_put succeeds */ + .id = id, + .floating = floating, + .callback = callback, + .userdata = userdata, + }; + + r = set_ensure_put(&client->slots, &qmp_slot_hash_ops, slot); + if (r < 0) + return r; + assert(r > 0); + + slot->client = client; + if (!floating) + qmp_client_ref(client); + + *ret = TAKE_PTR(slot); + return 0; +} + +static void qmp_client_clear(QmpClient *c); + +static QmpClient* qmp_client_free(QmpClient *c) { + if (!c) + return NULL; + + qmp_client_clear(c); + + return mfree(c); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(QmpClient, qmp_client, qmp_client_free); + +static void qmp_client_clear_current(QmpClient *c) { + assert(c); + + c->current = sd_json_variant_unref(c->current); +} + +static void qmp_client_dispatch_event(QmpClient *c, sd_json_variant *v) { + int r; + + assert(c); + assert(v); + + if (!c->event_callback) + return; + + struct { + const char *event; + sd_json_variant *data; + } p = {}; + + static const sd_json_dispatch_field table[] = { + { "event", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, event), SD_JSON_MANDATORY }, + { "data", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, voffsetof(p, data), 0 }, + {}, + }; + + r = sd_json_dispatch(v, table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG|SD_JSON_DEBUG, &p); + if (r < 0) + return; + + r = c->event_callback(c, p.event, p.data, c->event_userdata); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Event callback returned error, ignoring: %m"); +} + +/* QEMU's error "class" is effectively always "GenericError"; only "desc" carries useful info. */ +static const char* qmp_extract_error_description(sd_json_variant *v) { + sd_json_variant *error = sd_json_variant_by_key(v, "error"); + if (!error) + return NULL; + sd_json_variant *desc = sd_json_variant_by_key(error, "desc"); + if (desc) + return sd_json_variant_string(desc); + return "unspecified error"; +} + +/* Returns 1 with id set; 0 if absent (e.g. pre-parse error responses); -EBADMSG on wrong type. */ +static int qmp_extract_response_id(sd_json_variant *v, uint64_t *ret) { + sd_json_variant *id_variant; + + assert(v); + assert(ret); + + id_variant = sd_json_variant_by_key(v, "id"); + if (!id_variant) { + *ret = 0; + return 0; + } + if (!sd_json_variant_is_unsigned(id_variant)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "QMP response 'id' field is not an unsigned integer."); + + *ret = sd_json_variant_unsigned(id_variant); + return 1; +} + +/* Returns 0 on success (ret_result = "return" value), -EIO on QMP error (reterr_desc set). */ +static int qmp_parse_response(sd_json_variant *v, sd_json_variant **ret_result, const char **reterr_desc) { + const char *desc; + + desc = qmp_extract_error_description(v); + if (desc) { + if (reterr_desc) + *reterr_desc = desc; + return -EIO; + } + + if (ret_result) + *ret_result = sd_json_variant_by_key(v, "return"); + return 0; +} + +static int qmp_client_build_command( + QmpClient *c, + const char *command, + sd_json_variant *arguments, + sd_json_variant **ret, + uint64_t *ret_id) { + + uint64_t id; + int r; + + assert(c); + assert(command); + assert(ret); + assert(ret_id); + + id = c->next_id++; + + r = sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("execute", command), + SD_JSON_BUILD_PAIR_CONDITION(!!arguments, "arguments", SD_JSON_BUILD_VARIANT(arguments)), + SD_JSON_BUILD_PAIR_UNSIGNED("id", id)); + if (r < 0) + return r; + + *ret_id = id; + return 0; +} + +/* Route c->current to event callback or matching async slot. Returns 1 on dispatch. */ +static int qmp_client_dispatch(QmpClient *c) { + sd_json_variant *result = NULL; + const char *desc = NULL; + uint64_t id; + int error, r; + + assert(c); + + if (!c->current) + return 0; + + /* Events have an "event" key */ + if (sd_json_variant_by_key(c->current, "event")) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = TAKE_PTR(c->current); + qmp_client_dispatch_event(c, v); + return 1; + } + + /* QEMU sends a one-shot greeting with a "QMP" key unsolicited on connect. We don't + * wait for it before sending qmp_capabilities (QEMU accepts commands the moment the + * socket is open), we detect it by the "QMP" key and drop it. */ + if (sd_json_variant_by_key(c->current, "QMP")) { + qmp_client_clear_current(c); + return 1; + } + + /* Command responses carry an "id" matching a request we sent */ + r = qmp_extract_response_id(c->current, &id); + if (r < 0) { + qmp_client_clear_current(c); + return json_stream_log_errno(&c->stream, r, "Discarding QMP response with malformed id: %m"); + } + if (r == 0) { + qmp_client_clear_current(c); + json_stream_log(&c->stream, "Discarding unrecognized QMP message"); + return 1; + } + + QmpSlot *slot = set_get(c->slots, &(QmpSlot) { .id = id }); + if (!slot) { + qmp_client_clear_current(c); + json_stream_log(&c->stream, "Discarding QMP response with unknown id %" PRIu64, id); + return 1; + } + + /* Synchronous slot (no callback): leave c->current pinned so qmp_client_call() can + * pick up the reply and hand out borrowed pointers into it. The sync caller owns a + * ref on the slot and detects completion by observing slot->client turning NULL. */ + if (!slot->callback) { + qmp_slot_disconnect(slot, /* unref= */ true); + return 1; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = TAKE_PTR(c->current); + error = qmp_parse_response(v, &result, &desc); + + /* Pin the slot across the callback regardless of floating-ness. For a floating slot, + * disconnect(unref=true) drops the set's implicit ref which would otherwise free it + * out from under the callback. */ + qmp_slot_ref(slot); + + r = slot->callback(c, result, desc, error, slot->userdata); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Command callback returned error, ignoring: %m"); + + qmp_slot_disconnect(slot, /* unref= */ true); + qmp_slot_unref(slot); + + return 1; +} + +/* Fail all pending commands with the given error. Called on disconnect. */ +static void qmp_client_fail_pending(QmpClient *c, int error) { + QmpSlot *slot; + int r; + + assert(c); + + while ((slot = set_first(c->slots))) { + /* Keep alive across the callback and past disconnect (which may unref it for + * floating slots). */ + qmp_slot_ref(slot); + + if (slot->callback) { + r = slot->callback(c, /* result= */ NULL, /* error_desc= */ NULL, error, slot->userdata); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Command callback returned error, ignoring: %m"); + } + + qmp_slot_disconnect(slot, /* unref= */ true); + qmp_slot_unref(slot); + } +} + +/* Synthetic SHUTDOWN on unexpected disconnect so subscribers learn the VM is gone. */ +static void qmp_client_emit_synthetic_shutdown(QmpClient *c) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *data = NULL; + int r; + + assert(c); + + if (!c->event_callback) + return; + + r = sd_json_buildo( + &data, + SD_JSON_BUILD_PAIR_BOOLEAN("guest", false), + SD_JSON_BUILD_PAIR_STRING("reason", "disconnected")); + if (r < 0) { + json_stream_log_errno(&c->stream, r, "Failed to build synthetic SHUTDOWN event data, skipping: %m"); + return; + } + + r = c->event_callback(c, "SHUTDOWN", data, c->event_userdata); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Event callback returned error, ignoring: %m"); +} + +static bool qmp_client_handle_disconnect(QmpClient *c) { + assert(c); + + if (c->state == QMP_CLIENT_DISCONNECTED) + return false; + + c->state = QMP_CLIENT_DISCONNECTED; + + /* Disable defer event source so we don't busy-loop on the EOF condition. */ + if (c->defer_event_source) + (void) sd_event_source_set_enabled(c->defer_event_source, SD_EVENT_OFF); + + qmp_client_fail_pending(c, -ECONNRESET); + qmp_client_emit_synthetic_shutdown(c); + if (c->disconnect_callback) + c->disconnect_callback(c, c->disconnect_userdata); + + return true; +} + +static bool qmp_client_test_disconnect(QmpClient *c) { + assert(c); + + /* Already disconnected? */ + if (c->state == QMP_CLIENT_DISCONNECTED) + return false; + + if (!json_stream_should_disconnect(&c->stream)) + return false; + + return qmp_client_handle_disconnect(c); +} + +/* Single step: write → dispatch → parse → read → disconnect. Matches sd_varlink_process(). */ +int qmp_client_process(QmpClient *c) { + int r; + + assert(c); + + if (c->state < 0 || c->state == QMP_CLIENT_DISCONNECTED) + return -ENOTCONN; + + /* Pin against a callback dropping the last ref mid-dispatch. Matches sd_varlink_process(). */ + qmp_client_ref(c); + + /* 1. Write — drain output buffer */ + r = json_stream_write(&c->stream); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Failed to write to QMP socket: %m"); + if (r != 0) + goto finish; + + /* 2. Dispatch — dispatch incoming messages to slots */ + r = qmp_client_dispatch(c); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Failed to dispatch QMP message: %m"); + if (r != 0) + goto finish; + + /* 3. Parse — extract one complete message into c->current */ + if (!c->current) { + r = json_stream_parse(&c->stream, &c->current); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Failed to parse QMP message: %m"); + if (r != 0) + goto finish; + } + + /* 4. Read — fill input buffer from fd */ + if (!c->current) { + r = json_stream_read(&c->stream); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Failed to read from QMP socket: %m"); + if (r != 0) + goto finish; + } + + /* 5. Test disconnect */ + if (qmp_client_test_disconnect(c)) { + r = 1; + goto finish; + } + +finish: + /* Re-arm defer source on progress so we get called again next iteration. */ + if (r >= 0 && c->defer_event_source) { + int q; + + q = sd_event_source_set_enabled(c->defer_event_source, r > 0 ? SD_EVENT_ON : SD_EVENT_OFF); + if (q < 0) + r = json_stream_log_errno(&c->stream, q, "Failed to enable deferred event source: %m"); + } + + /* -ENOBUFS is the buffered stream's 16 MiB cap, not a transport error — propagate without disconnecting. */ + if (r < 0 && r != -ENOBUFS && c->state != QMP_CLIENT_DISCONNECTED) + qmp_client_handle_disconnect(c); + + qmp_client_unref(c); + return r; +} + +int qmp_client_wait(QmpClient *c, uint64_t timeout_usec) { + assert(c); + + if (c->state == QMP_CLIENT_DISCONNECTED) + return -ENOTCONN; + + return json_stream_wait(&c->stream, timeout_usec); +} + +bool qmp_client_is_idle(QmpClient *c) { + assert(c); + return set_isempty(c->slots); +} + +bool qmp_client_is_disconnected(QmpClient *c) { + assert(c); + return c->state == QMP_CLIENT_DISCONNECTED; +} + +void* qmp_client_set_userdata(QmpClient *c, void *userdata) { + void *old; + + assert(c); + + old = c->userdata; + c->userdata = userdata; + return old; +} + +void* qmp_client_get_userdata(QmpClient *c) { + assert(c); + return c->userdata; +} + +/* Map our state to the transport phase used for POLLIN / salvage / timeout decisions. */ +static JsonStreamPhase qmp_client_phase(void *userdata) { + QmpClient *c = ASSERT_PTR(userdata); + + /* A parsed-but-undispatched message is mid-processing, not waiting on the wire. */ + if (c->current) + return JSON_STREAM_PHASE_OTHER; + + if (c->state != QMP_CLIENT_RUNNING) + return JSON_STREAM_PHASE_OTHER; + + /* Pending slots (user commands or the initial qmp_capabilities) → awaiting reply. + * Otherwise we're idling for unsolicited events. */ + return set_isempty(c->slots) + ? JSON_STREAM_PHASE_READING + : JSON_STREAM_PHASE_AWAITING_REPLY; +} + +static int qmp_client_dispatch_cb(void *userdata) { + QmpClient *c = ASSERT_PTR(userdata); + return qmp_client_process(c); +} + +static int qmp_client_defer_callback(sd_event_source *source, void *userdata) { + QmpClient *c = ASSERT_PTR(userdata); + + assert(source); + + (void) qmp_client_process(c); + + return 1; +} + +static void qmp_client_detach_event(QmpClient *c) { + if (!c) + return; + + c->defer_event_source = sd_event_source_disable_unref(c->defer_event_source); + c->quit_event_source = sd_event_source_disable_unref(c->quit_event_source); + json_stream_detach_event(&c->stream); +} + +static void qmp_client_clear(QmpClient *c) { + assert(c); + + qmp_client_handle_disconnect(c); + qmp_client_detach_event(c); + qmp_client_clear_current(c); + json_stream_done(&c->stream); + c->slots = set_free(c->slots); +} + +/* Blocks until output buffer is empty. Matches sd_varlink_flush(). */ +static int qmp_client_flush(QmpClient *c) { + if (!c) + return 0; + + if (c->state == QMP_CLIENT_DISCONNECTED) + return -ENOTCONN; + + return json_stream_flush(&c->stream); +} + +/* Notify callbacks, fire disconnect, detach sources, close fd. Matches sd_varlink_close(). */ +static int qmp_client_close(QmpClient *c) { + if (!c) + return 0; + + /* Take a temporary ref to prevent destruction mid-callback, + * matching sd_varlink_close()'s pattern. */ + qmp_client_ref(c); + qmp_client_clear(c); + qmp_client_unref(c); + + return 1; +} + +static int qmp_client_quit_callback(sd_event_source *source, void *userdata) { + QmpClient *c = ASSERT_PTR(userdata); + + assert(source); + + qmp_client_flush(c); + qmp_client_close(c); + + return 1; +} + +static int qmp_client_send( + QmpClient *c, + const char *command, + QmpClientArgs *args, + qmp_command_callback_t callback, + void *userdata, + QmpSlot **ret_slot); + +/* Reply callback for the eagerly-enqueued qmp_capabilities command. Success → we stay in + * RUNNING. Failure → negotiation is unrecoverable, force-disconnect so the next user op gets + * -ENOTCONN rather than hanging. */ +static int qmp_client_capabilities_reply( + QmpClient *c, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + /* qmp_client_handle_disconnect() below fails all pending slots, which re-enters this + * callback with -ECONNRESET on our own still-registered slot. Short-circuit that. */ + if (c->state == QMP_CLIENT_DISCONNECTED) + return 0; + + if (error >= 0) + return 0; + + json_stream_log_errno(&c->stream, error, "qmp_capabilities failed: %s", strna(error_desc)); + qmp_client_handle_disconnect(c); + return 0; +} + +int qmp_client_connect_fd(QmpClient **ret, int fd) { + _cleanup_(qmp_client_unrefp) QmpClient *c = NULL; + int r; + + assert(ret); + assert(fd >= 0); + + c = new(QmpClient, 1); + if (!c) + return -ENOMEM; + + *c = (QmpClient) { + .n_ref = 1, + .state = QMP_CLIENT_RUNNING, + .next_id = 1, + }; + + const JsonStreamParams params = { + .delimiter = "\r\n", + .phase = qmp_client_phase, + .dispatch = qmp_client_dispatch_cb, + .userdata = c, + }; + + r = json_stream_init(&c->stream, ¶ms); + if (r < 0) + return r; + + r = json_stream_connect_fd_pair(&c->stream, fd, fd); + if (r < 0) + return r; + + /* Eagerly queue qmp_capabilities. QEMU accepts commands as soon as the socket opens + * — its greeting is informational and doesn't gate writes on our side. FIFO ordering + * of the output queue guarantees cap precedes any user command a later invoke() + * enqueues, which is all QEMU actually requires. */ + r = qmp_client_send(c, "qmp_capabilities", /* args= */ NULL, + qmp_client_capabilities_reply, /* userdata= */ NULL, + /* ret_slot= */ NULL); + if (r < 0) + return r; + + *ret = TAKE_PTR(c); + return 0; +} + +int qmp_client_attach_event(QmpClient *c, sd_event *event, int64_t priority) { + int r; + + assert(c); + assert(event); + assert(!json_stream_get_event(&c->stream)); + + r = json_stream_attach_event(&c->stream, event, priority); + if (r < 0) + return r; + + sd_event *ev = json_stream_get_event(&c->stream); + + r = sd_event_add_exit(ev, &c->quit_event_source, qmp_client_quit_callback, c); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(c->quit_event_source, priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(c->quit_event_source, "qmp-client-quit"); + + r = sd_event_add_defer(ev, &c->defer_event_source, qmp_client_defer_callback, c); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(c->defer_event_source, priority); + if (r < 0) + goto fail; + + r = sd_event_source_set_enabled(c->defer_event_source, SD_EVENT_OFF); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(c->defer_event_source, "qmp-client-defer"); + + return 0; + +fail: + qmp_client_detach_event(c); + return r; +} + +/* Cleanup hook: closes any fds in *args not yet transferred to the stream. */ +static QmpClientArgs* qmp_client_args_close_fds(QmpClientArgs *p) { + assert(p); + close_many_unset(p->fds_consume, p->n_fds); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(QmpClientArgs*, qmp_client_args_close_fds); + +/* Shared send path for qmp_client_invoke() and qmp_client_call(). A NULL callback registers + * a "synchronous" slot: dispatch_reply leaves c->current pinned on match instead of invoking + * a callback, so qmp_client_call() can hand out borrowed pointers into the reply. If ret_slot + * is NULL the slot is allocated as floating (owned by c->slots); otherwise a reference is + * handed back to the caller. */ +static int qmp_client_send( + QmpClient *c, + const char *command, + QmpClientArgs *args, + qmp_command_callback_t callback, + void *userdata, + QmpSlot **ret_slot) { + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd = NULL; + _cleanup_(qmp_slot_unrefp) QmpSlot *slot = NULL; + /* Closes any fds in args on every early-return path; TAKE_PTR()'d on the success path + * below once json_stream_enqueue_full() has taken ownership of them. */ + _cleanup_(qmp_client_args_close_fdsp) QmpClientArgs *fds_owner = args; + uint64_t id; + int r; + + assert(c); + assert(command); + + if (c->state == QMP_CLIENT_DISCONNECTED) + return -ENOTCONN; + + r = qmp_client_build_command(c, command, args ? args->arguments : NULL, &cmd, &id); + if (r < 0) + return r; + + r = qmp_slot_new(c, /* floating= */ !ret_slot, id, callback, userdata, &slot); + if (r < 0) + return r; + + r = json_stream_enqueue_full(&c->stream, cmd, + args ? args->fds_consume : NULL, + args ? args->n_fds : 0); + if (r < 0) + return r; /* slot cleanup disconnects it */ + + /* Arm defer so process() drains the output on the next iteration. */ + if (c->defer_event_source) + (void) sd_event_source_set_enabled(c->defer_event_source, SD_EVENT_ON); + + TAKE_PTR(fds_owner); + + if (ret_slot) + *ret_slot = TAKE_PTR(slot); + else + TAKE_PTR(slot); /* floating: c->slots keeps it alive until dispatch */ + + return 0; +} + +int qmp_client_invoke( + QmpClient *c, + QmpSlot **ret_slot, + const char *command, + QmpClientArgs *args, + qmp_command_callback_t callback, + void *userdata) { + + assert(callback); + return qmp_client_send(c, command, args, callback, userdata, ret_slot); +} + +int qmp_client_call( + QmpClient *c, + const char *command, + QmpClientArgs *args, + sd_json_variant **ret_result, + const char **ret_error_desc) { + + _cleanup_(qmp_slot_unrefp) QmpSlot *slot = NULL; + int r; + + assert_return(c, -EINVAL); + assert_return(command, -EINVAL); + + /* Drop any reply pinned by a previous qmp_client_call() before we pin a new one. */ + qmp_client_clear_current(c); + + /* NULL callback marks this as a synchronous slot: dispatch_reply matches on id like + * any other slot (so stray unknown-id replies still get logged and dropped), but + * pins c->current for us instead of invoking a callback. The slot is non-floating so + * we can observe dispatch by watching slot->client go NULL. */ + r = qmp_client_send(c, command, args, /* callback= */ NULL, /* userdata= */ NULL, &slot); + if (r < 0) + return r; + + /* Pump the loop until our sync slot fires (disconnected by dispatch, c->current pinned). */ + for (;;) { + if (c->state == QMP_CLIENT_DISCONNECTED) + return -ECONNRESET; + + if (!slot->client) { + assert(c->current); + break; + } + + r = qmp_client_process(c); + if (r < 0) + return r; + if (r > 0) + continue; + + r = qmp_client_wait(c, USEC_INFINITY); + if (r < 0) + return r; + } + + sd_json_variant *result = NULL; + const char *desc = NULL; + int error = qmp_parse_response(c->current, &result, &desc); + + /* If caller doesn't ask for the error string, surface the error as the return code. */ + if (!ret_error_desc && error < 0) + return error; + + if (ret_result) + *ret_result = result; + if (ret_error_desc) + *ret_error_desc = desc; + + return 1; +} + +void qmp_client_bind_event(QmpClient *c, qmp_event_callback_t callback, void *userdata) { + assert(c); + c->event_callback = callback; + c->event_userdata = userdata; +} + +void qmp_client_bind_disconnect(QmpClient *c, qmp_disconnect_callback_t callback, void *userdata) { + assert(c); + c->disconnect_callback = callback; + c->disconnect_userdata = userdata; +} + +int qmp_client_set_description(QmpClient *c, const char *description) { + assert(c); + return json_stream_set_description(&c->stream, description); +} + +sd_event* qmp_client_get_event(QmpClient *c) { + assert(c); + return json_stream_get_event(&c->stream); +} + +uint64_t qmp_client_next_fdset_id(QmpClient *c) { + assert(c); + return c->next_fdset_id++; +} + +bool qmp_schema_has_member(sd_json_variant *schema, const char *member_name) { + sd_json_variant *entry; + + assert(member_name); + + if (!sd_json_variant_is_array(schema)) + return false; + + JSON_VARIANT_ARRAY_FOREACH(entry, schema) { + if (!sd_json_variant_is_object(entry)) + continue; + + sd_json_variant *meta = sd_json_variant_by_key(entry, "meta-type"); + if (!meta || !streq_ptr(sd_json_variant_string(meta), "object")) + continue; + + sd_json_variant *members = sd_json_variant_by_key(entry, "members"); + if (!sd_json_variant_is_array(members)) + continue; + + sd_json_variant *m; + JSON_VARIANT_ARRAY_FOREACH(m, members) { + if (!sd_json_variant_is_object(m)) + continue; + sd_json_variant *mn = sd_json_variant_by_key(m, "name"); + if (mn && streq_ptr(sd_json_variant_string(mn), member_name)) + return true; + } + } + + return false; +} diff --git a/src/shared/qmp-client.h b/src/shared/qmp-client.h new file mode 100644 index 0000000000000..7dcd53355d06c --- /dev/null +++ b/src/shared/qmp-client.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +typedef int (*qmp_event_callback_t)( + QmpClient *client, + const char *event, + sd_json_variant *data, + void *userdata); + +typedef void (*qmp_disconnect_callback_t)( + QmpClient *client, + void *userdata); + +/* Success: (result, NULL, 0). QMP error: (NULL, desc, -EIO). Transport: (NULL, NULL, -errno). */ +typedef int (*qmp_command_callback_t)( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata); + +/* Bundles arguments + fds for one command. Construct fresh per invoke via the macros below. */ +typedef struct QmpClientArgs { + sd_json_variant *arguments; + int *fds_consume; + size_t n_fds; +} QmpClientArgs; + +#define QMP_CLIENT_ARGS(args_) \ + (&(QmpClientArgs){ .arguments = (args_) }) +#define QMP_CLIENT_ARGS_FD(args_, fd_) \ + (&(QmpClientArgs){ .arguments = (args_), .fds_consume = (int[]){ (fd_) }, .n_fds = 1 }) + +/* Takes ownership of fd; handshake runs lazily on first invoke or from the event loop. */ +int qmp_client_connect_fd(QmpClient **ret, int fd); + +int qmp_client_attach_event(QmpClient *c, sd_event *event, int64_t priority); + +/* Single non-blocking pump step: write → dispatch → parse → read → disconnect. Returns >0 if + * progress was made, 0 if idle, <0 on error (-ENOTCONN on disconnect). Same contract as + * sd_varlink_process(). */ +int qmp_client_process(QmpClient *c); + +/* Block on the transport fd until readable, or until timeout (USEC_INFINITY for no timeout). + * Same contract as sd_varlink_wait(). */ +int qmp_client_wait(QmpClient *c, uint64_t timeout_usec); + +/* True iff there are no outstanding command replies (slots set is empty). Useful as the pump-loop + * exit condition for callers driving the client synchronously via process() + wait(). */ +bool qmp_client_is_idle(QmpClient *c); + +/* True iff the connection is dead. Stable terminal state — once set, it stays set. */ +bool qmp_client_is_disconnected(QmpClient *c); + +void* qmp_client_set_userdata(QmpClient *c, void *userdata); +void* qmp_client_get_userdata(QmpClient *c); + +/* Async send. Returns 0 on send (callback will fire later), negative errno on failure. If + * ret_slot is non-NULL, returns a reference to a QmpSlot which can be used to cancel the call + * (by unreffing it before the reply arrives). */ +int qmp_client_invoke( + QmpClient *client, + QmpSlot **ret_slot, + const char *command, + QmpClientArgs *args, + qmp_command_callback_t callback, + void *userdata); + +/* Synchronous send + receive. Pumps the event loop until the reply arrives. *ret_result and + * *ret_error_desc are borrowed pointers into the last reply, valid until the next + * qmp_client_call(). Same contract as sd_varlink_call(). */ +int qmp_client_call( + QmpClient *client, + const char *command, + QmpClientArgs *args, + sd_json_variant **ret_result, + const char **ret_error_desc); + +void qmp_client_bind_event(QmpClient *c, qmp_event_callback_t callback, void *userdata); +void qmp_client_bind_disconnect(QmpClient *c, qmp_disconnect_callback_t callback, void *userdata); +int qmp_client_set_description(QmpClient *c, const char *description); +sd_event* qmp_client_get_event(QmpClient *c); +uint64_t qmp_client_next_fdset_id(QmpClient *client); + +DECLARE_TRIVIAL_REF_UNREF_FUNC(QmpClient, qmp_client); +DEFINE_TRIVIAL_CLEANUP_FUNC(QmpClient *, qmp_client_unref); + +DECLARE_TRIVIAL_REF_UNREF_FUNC(QmpSlot, qmp_slot); +DEFINE_TRIVIAL_CLEANUP_FUNC(QmpSlot *, qmp_slot_unref); + +QmpClient* qmp_slot_get_client(QmpSlot *slot); + +/* Returns true iff any object entry in schema (result of query-qmp-schema) has a member with this + * name. QEMU's introspection replaces type names with opaque numeric ids, so lookup-by-type-name is + * impossible — but member names are real. Use only when the member name is unique in the schema. */ +bool qmp_schema_has_member(sd_json_variant *schema, const char *member_name); diff --git a/src/shared/qrcode-util.c b/src/shared/qrcode-util.c index 320b2353ff92f..d1c639e74c1f0 100644 --- a/src/shared/qrcode-util.c +++ b/src/shared/qrcode-util.c @@ -7,6 +7,8 @@ #endif #include +#include "sd-dlopen.h" + #include "ansi-color.h" #include "dlfcn-util.h" #include "locale-util.h" @@ -26,18 +28,19 @@ static DLSYM_PROTOTYPE(QRcode_encodeString) = NULL; static DLSYM_PROTOTYPE(QRcode_free) = NULL; #endif -int dlopen_qrencode(void) { +int dlopen_qrencode(int log_level) { #if HAVE_QRENCODE int r; - ELF_NOTE_DLOPEN("qrencode", + SD_ELF_NOTE_DLOPEN( + "qrencode", "Support for generating QR codes", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libqrencode.so.4", "libqrencode.so.3"); FOREACH_STRING(s, "libqrencode.so.4", "libqrencode.so.3") { r = dlopen_many_sym_or_warn( - &qrcode_dl, s, LOG_DEBUG, + &qrcode_dl, s, log_level, DLSYM_ARG(QRcode_encodeString), DLSYM_ARG(QRcode_free)); if (r >= 0) @@ -46,7 +49,8 @@ int dlopen_qrencode(void) { return r; #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libqrencode support is not compiled in."); #endif } @@ -207,7 +211,7 @@ int print_qrcode_full( if (check_tty && !colors_enabled()) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Colors are disabled, cannot print qrcode"); - r = dlopen_qrencode(); + r = dlopen_qrencode(LOG_DEBUG); if (r < 0) return r; diff --git a/src/shared/qrcode-util.h b/src/shared/qrcode-util.h index 9a624e29e13b7..667ff8e3ff7cc 100644 --- a/src/shared/qrcode-util.h +++ b/src/shared/qrcode-util.h @@ -13,7 +13,7 @@ int print_qrcode_full( unsigned tty_height, bool check_tty); -int dlopen_qrencode(void); +int dlopen_qrencode(int log_level); static inline int print_qrcode(FILE *out, const char *header, const char *string) { return print_qrcode_full(out, header, string, UINT_MAX, UINT_MAX, UINT_MAX, UINT_MAX, true); diff --git a/src/shared/reboot-util.c b/src/shared/reboot-util.c index 948e15631d8ab..5e460b1dc517d 100644 --- a/src/shared/reboot-util.c +++ b/src/shared/reboot-util.c @@ -1,30 +1,53 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include #include #include #if HAVE_XENCTRL +#include +#include + #define __XEN_INTERFACE_VERSION__ 0x00040900 #include #include #include + +#include "errno-util.h" #endif #include "alloc-util.h" -#include "errno-util.h" +#include "compress.h" +#include "copy.h" #include "fd-util.h" #include "fileio.h" +#include "io-util.h" #include "log.h" +#include "memfd-util.h" +#include "pe-binary.h" #include "proc-cmdline.h" #include "reboot-util.h" +#include "sparse-endian.h" +#include "stat-util.h" #include "string-util.h" #include "umask-util.h" #include "utf8.h" #include "virt.h" +/* ZBOOT header layout — see linux/drivers/firmware/efi/libstub/zboot-header.S */ +struct zboot_header { + le16_t mz_magic; /* 0x00: "MZ" DOS signature */ + le16_t _pad0; + uint8_t zimg_magic[4]; /* 0x04: "zimg" identifier */ + le32_t payload_offset; /* 0x08: offset to compressed payload */ + le32_t payload_size; /* 0x0C: size of compressed payload */ + uint8_t _pad1[8]; + char comp_type[6]; /* 0x18: NUL-terminated compression type (e.g. "gzip", "zstd") */ + uint8_t _pad2[2]; +} _packed_; +assert_cc(sizeof(struct zboot_header) == 0x20); +assert_cc(offsetof(struct zboot_header, comp_type) == 0x18); + int raw_reboot(int cmd, const void *arg) { return syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd, arg); } @@ -137,13 +160,15 @@ bool shall_restore_state(void) { return (cached = b); } -static int xen_kexec_loaded(void) { #if HAVE_XENCTRL +static int xen_kexec_command(uint64_t cmd) { _cleanup_close_ int privcmd_fd = -EBADF, buf_fd = -EBADF; - xen_kexec_status_t *buffer; + void *buffer; size_t size; int r; + assert(IN_SET(cmd, KEXEC_CMD_kexec, KEXEC_CMD_kexec_status)); + if (access("/proc/xen", F_OK) < 0) { if (errno == ENOENT) return -EOPNOTSUPP; @@ -151,7 +176,8 @@ static int xen_kexec_loaded(void) { } size = page_size(); - if (sizeof(xen_kexec_status_t) > size) + if ((cmd == KEXEC_CMD_kexec_status && sizeof(xen_kexec_status_t) > size) || + (cmd == KEXEC_CMD_kexec && sizeof(xen_kexec_exec_t) > size)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "page_size is too small for hypercall"); privcmd_fd = open("/dev/xen/privcmd", O_RDWR|O_CLOEXEC); @@ -166,25 +192,44 @@ static int xen_kexec_loaded(void) { if (buffer == MAP_FAILED) return log_debug_errno(errno, "Cannot allocate buffer for hypercall: %m"); - *buffer = (xen_kexec_status_t) { - .type = KEXEC_TYPE_DEFAULT, - }; + if (cmd == KEXEC_CMD_kexec_status) + *(xen_kexec_status_t *)buffer = (xen_kexec_status_t) { + .type = KEXEC_TYPE_DEFAULT, + }; + else + *(xen_kexec_exec_t *)buffer = (xen_kexec_exec_t) { + .type = KEXEC_TYPE_DEFAULT, + }; privcmd_hypercall_t call = { .op = __HYPERVISOR_kexec_op, .arg = { - KEXEC_CMD_kexec_status, + cmd, PTR_TO_UINT64(buffer), }, }; r = RET_NERRNO(ioctl(privcmd_fd, IOCTL_PRIVCMD_HYPERCALL, &call)); if (r < 0) - log_debug_errno(r, "kexec_status failed: %m"); + log_debug_errno(r, "kexec%s failed: %m", cmd == KEXEC_CMD_kexec_status ? "_status" : ""); munmap(buffer, size); return r; +} +#endif + +static int xen_kexec(void) { +#if HAVE_XENCTRL + return xen_kexec_command(KEXEC_CMD_kexec); +#else + return -EOPNOTSUPP; +#endif +} + +static int xen_kexec_loaded(void) { +#if HAVE_XENCTRL + return xen_kexec_command(KEXEC_CMD_kexec_status); #else return -EOPNOTSUPP; #endif @@ -208,6 +253,242 @@ bool kexec_loaded(void) { return s[0] == '1'; } +int kexec(void) { + int r; + + r = xen_kexec(); + if (r < 0 && r != -EOPNOTSUPP) + return log_error_errno(r, "Failed to call xen kexec: %m"); + + r = reboot(LINUX_REBOOT_CMD_KEXEC); + if (r < 0) + return log_error_errno(errno, "Failed to kexec: %m"); + + return 0; +} + +static int decompress_to_memfd(Compression compression, int fd) { + int r; + + _cleanup_close_ int memfd = memfd_new("kexec-kernel"); + if (memfd < 0) + return log_error_errno(memfd, "Failed to create memfd: %m"); + + r = decompress_stream(compression, fd, memfd, UINT64_MAX); + if (r < 0) + return log_error_errno(r, "Failed to decompress kernel: %m"); + + if (lseek(memfd, 0, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to seek memfd: %m"); + + return TAKE_FD(memfd); +} + +static int decompress_zboot_to_memfd(int fd, uint32_t payload_offset, uint32_t payload_size, const char *comp_type) { + int r; + + Compression c = compression_from_string(comp_type); + if (c < 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Unsupported ZBOOT compression type '%s'.", comp_type); + + struct stat st; + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to stat ZBOOT image: %m"); + + r = stat_verify_regular(&st); + if (r < 0) + return log_error_errno(r, "Kernel image is not a regular file: %m"); + + if (payload_offset < 0x20 || + payload_size == 0 || + payload_offset > (uint64_t) st.st_size || + payload_size > (uint64_t) st.st_size - payload_offset) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "ZBOOT payload offset/size invalid."); + + if (payload_size > 256 * 1024 * 1024) /* generous for any compressed kernel */ + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "ZBOOT payload unreasonably large."); + + _cleanup_free_ void *payload = malloc(payload_size); + if (!payload) + return log_oom(); + + ssize_t n = pread(fd, payload, payload_size, payload_offset); + if (n < 0) + return log_error_errno(errno, "Failed to read ZBOOT payload: %m"); + if ((uint32_t) n < payload_size) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read of ZBOOT payload."); + + _cleanup_free_ void *decompressed = NULL; + size_t decompressed_size; + r = decompress_blob(c, payload, payload_size, &decompressed, &decompressed_size, /* dst_max= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to decompress ZBOOT payload: %m"); + + payload = mfree(payload); + + _cleanup_close_ int memfd = memfd_new("kexec-kernel"); + if (memfd < 0) + return log_error_errno(memfd, "Failed to create memfd: %m"); + + r = loop_write(memfd, decompressed, decompressed_size); + if (r < 0) + return log_error_errno(r, "Failed to write decompressed kernel to memfd: %m"); + + if (lseek(memfd, 0, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to seek memfd: %m"); + + return TAKE_FD(memfd); +} + +static int pe_section_to_memfd(int fd, const IMAGE_SECTION_HEADER *section, const char *name) { + int r; + + assert(fd >= 0); + assert(section); + + uint32_t offset = le32toh(section->PointerToRawData), + size = MIN(le32toh(section->VirtualSize), le32toh(section->SizeOfRawData)); + + _cleanup_close_ int memfd = memfd_new(name); + if (memfd < 0) + return log_error_errno(memfd, "Failed to create memfd for PE section '%s': %m", name); + + if (lseek(fd, offset, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to seek to PE section '%s': %m", name); + + r = copy_bytes(fd, memfd, size, /* copy_flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to copy PE section '%s': %m", name); + + if (lseek(memfd, 0, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to seek memfd: %m"); + + return TAKE_FD(memfd); +} + +static int extract_uki(const char *path, int fd, int *ret_kernel_fd, int *ret_initrd_fd) { + int r; + + assert(fd >= 0); + assert(ret_kernel_fd); + + _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL; + _cleanup_free_ PeHeader *pe_header = NULL; + r = pe_load_headers(fd, &dos_header, &pe_header); + if (r < 0) + return log_debug_errno(r, "Not a valid PE file '%s': %m", path); + + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + r = pe_load_sections(fd, dos_header, pe_header, §ions); + if (r < 0) + return log_debug_errno(r, "Failed to load PE sections from '%s': %m", path); + + if (!pe_is_uki(pe_header, sections)) + return 0; /* Not a UKI */ + + /* FIXME: we currently only extract .linux and .initrd, but sd-stub does a lot more: + * profiles, .cmdline, .dtb/.dtbauto, .ucode, .pcrsig/.pcrpkey, sidecar addons, + * credentials, sysexts/confexts, and TPM PCR measurements. */ + + const IMAGE_SECTION_HEADER *linux_section = pe_header_find_section(pe_header, sections, ".linux"); + if (!linux_section) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "UKI '%s' has no .linux section.", path); + + log_debug("Detected UKI image '%s', extracting .linux section.", path); + + _cleanup_close_ int kernel_memfd = pe_section_to_memfd(fd, linux_section, "kexec-uki-kernel"); + if (kernel_memfd < 0) + return kernel_memfd; + + _cleanup_close_ int initrd_memfd = -EBADF; + if (ret_initrd_fd) { + const IMAGE_SECTION_HEADER *initrd_section = pe_header_find_section(pe_header, sections, ".initrd"); + if (initrd_section) { + log_debug("Extracting .initrd section from UKI '%s'.", path); + + initrd_memfd = pe_section_to_memfd(fd, initrd_section, "kexec-uki-initrd"); + if (initrd_memfd < 0) + return initrd_memfd; + } + } + + *ret_kernel_fd = TAKE_FD(kernel_memfd); + if (ret_initrd_fd) + *ret_initrd_fd = TAKE_FD(initrd_memfd); + + return 1; +} + +int kexec_maybe_decompress_kernel(const char *path, int fd, int *ret_kernel_fd, int *ret_initrd_fd) { + uint8_t magic[8]; + ssize_t n; + int r; + + assert(fd >= 0); + assert(ret_kernel_fd); + + n = pread(fd, magic, sizeof(magic), 0); + if (n < 0) + return log_error_errno(errno, "Failed to read kernel magic from '%s': %m", path); + if ((size_t) n < sizeof(magic)) + /* Too small to detect, pass through as-is */ + return 0; + + if (magic[0] == 'M' && magic[1] == 'Z') { + + if (magic[4] == 'z' && magic[5] == 'i' && magic[6] == 'm' && magic[7] == 'g') { + struct zboot_header h; + + n = pread(fd, &h, sizeof(h), 0); + if (n < 0) + return log_error_errno(errno, "Failed to read ZBOOT header from '%s': %m", path); + if ((size_t) n < sizeof(h)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Short read of ZBOOT header from '%s'.", path); + + char comp_type[sizeof(h.comp_type) + 1]; + memcpy(comp_type, h.comp_type, sizeof(h.comp_type)); + comp_type[sizeof(h.comp_type)] = '\0'; + + uint32_t payload_offset = le32toh(h.payload_offset), + payload_size = le32toh(h.payload_size); + + log_debug("Detected ZBOOT image '%s' (compression=%s, offset=%"PRIu32", size=%"PRIu32")", + path, comp_type, payload_offset, payload_size); + + r = decompress_zboot_to_memfd(fd, payload_offset, payload_size, comp_type); + if (r < 0) + return r; + + *ret_kernel_fd = r; + return 1; + } + + /* MZ but not ZBOOT — check if it's a UKI */ + return extract_uki(path, fd, ret_kernel_fd, ret_initrd_fd); + } + + Compression c = compression_detect_from_magic(magic); + if (c < 0) + /* Not a recognized compressed format, pass through as-is */ + return 0; + + log_debug("Detected %s-compressed kernel '%s', decompressing.", compression_to_string(c), path); + + /* Seek back to start before decompression */ + if (lseek(fd, 0, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to seek kernel fd: %m"); + + r = decompress_to_memfd(c, fd); + if (r < 0) + return r; + + *ret_kernel_fd = r; + return 1; +} + int create_shutdown_run_nologin_or_warn(void) { int r; diff --git a/src/shared/reboot-util.h b/src/shared/reboot-util.h index eaa6614df05ea..658d065ce918b 100644 --- a/src/shared/reboot-util.h +++ b/src/shared/reboot-util.h @@ -26,5 +26,8 @@ int reboot_with_parameter(RebootFlags flags); bool shall_restore_state(void); bool kexec_loaded(void); +int kexec(void); + +int kexec_maybe_decompress_kernel(const char *path, int fd, int *ret_kernel_fd, int *ret_initrd_fd); int create_shutdown_run_nologin_or_warn(void); diff --git a/src/shared/reread-partition-table.c b/src/shared/reread-partition-table.c index 8f99b67134de7..9d908ee948360 100644 --- a/src/shared/reread-partition-table.c +++ b/src/shared/reread-partition-table.c @@ -287,7 +287,7 @@ static int reread_partition_table_full(sd_device *dev, int fd, RereadPartitionTa } #if HAVE_BLKID - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { log_device_debug(dev, "We don't have libblkid, falling back to BLKRRPART on '%s'.", p); return fallback_ioctl(dev, fd, flags); diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c index f375d25428c4b..ede18318abfe6 100644 --- a/src/shared/rm-rf.c +++ b/src/shared/rm-rf.c @@ -37,8 +37,9 @@ static int patch_dirfd_mode( if (fstat(dfd, &st) < 0) return -errno; - if (!S_ISDIR(st.st_mode)) - return -ENOTDIR; + r = stat_verify_directory(&st); + if (r < 0) + return r; if (FLAGS_SET(st.st_mode, 0700)) { /* Already set? */ if (refuse_already_set) @@ -268,6 +269,8 @@ typedef struct TodoEntry { } TodoEntry; static void free_todo_entries(TodoEntry **todos) { + assert(todos); + for (TodoEntry *x = *todos; x && x->dir; x++) { closedir(x->dir); free(x->dirname); diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c index 55e3c14e443fd..167a0e40de8c3 100644 --- a/src/shared/seccomp-util.c +++ b/src/shared/seccomp-util.c @@ -12,10 +12,12 @@ #include #include -#ifdef ARCH_MIPS +#ifdef __mips__ #include #endif +#include "sd-dlopen.h" + #include "af-list.h" #include "alloc-util.h" #include "env-util.h" @@ -48,31 +50,6 @@ DLSYM_PROTOTYPE(seccomp_rule_add_exact) = NULL; DLSYM_PROTOTYPE(seccomp_syscall_resolve_name) = NULL; DLSYM_PROTOTYPE(seccomp_syscall_resolve_num_arch) = NULL; -int dlopen_libseccomp(void) { - ELF_NOTE_DLOPEN("seccomp", - "Support for Seccomp Sandboxes", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libseccomp.so.2"); - - return dlopen_many_sym_or_warn( - &libseccomp_dl, - "libseccomp.so.2", - LOG_DEBUG, - DLSYM_ARG(seccomp_api_get), - DLSYM_ARG(seccomp_arch_add), - DLSYM_ARG(seccomp_arch_exist), - DLSYM_ARG(seccomp_arch_native), - DLSYM_ARG(seccomp_arch_remove), - DLSYM_ARG(seccomp_attr_set), - DLSYM_ARG(seccomp_init), - DLSYM_ARG(seccomp_load), - DLSYM_ARG(seccomp_release), - DLSYM_ARG(seccomp_rule_add_array), - DLSYM_ARG(seccomp_rule_add_exact), - DLSYM_ARG(seccomp_syscall_resolve_name), - DLSYM_ARG(seccomp_syscall_resolve_num_arch)); -} - /* This array will be modified at runtime as seccomp_restrict_archs is called. */ uint32_t seccomp_local_archs[] = { @@ -275,10 +252,12 @@ int seccomp_init_for_arch(scmp_filter_ctx *ret, uint32_t arch, uint32_t default_ _cleanup_(seccomp_releasep) scmp_filter_ctx seccomp = NULL; int r; + assert(ret); + /* Much like seccomp_init(), but initializes the filter for one specific architecture only, without affecting * any others. Also, turns off the NNP fiddling. */ - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -338,7 +317,7 @@ bool is_seccomp_available(void) { static int cached_enabled = -1; if (cached_enabled < 0) { - if (dlopen_libseccomp() < 0) + if (dlopen_libseccomp(LOG_DEBUG) < 0) return (cached_enabled = false); int b = secure_getenv_bool("SYSTEMD_SECCOMP"); @@ -1088,7 +1067,7 @@ int seccomp_add_syscall_filter_item( } else { int id, r; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1168,7 +1147,7 @@ int seccomp_load_syscall_filter_set(uint32_t default_action, const SyscallFilter /* The one-stop solution: allocate a seccomp object, add the specified filter to it, and apply it. Once for * each local arch. */ - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1234,7 +1213,7 @@ int seccomp_load_syscall_filter_set_raw(uint32_t default_action, Hashmap* filter if (hashmap_isempty(filter) && default_action == SCMP_ACT_ALLOW) return 0; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1357,7 +1336,7 @@ int seccomp_parse_syscall_filter( } else { int id; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) { if (!FLAGS_SET(flags, SECCOMP_PARSE_PERMISSIVE)) return r; @@ -1415,7 +1394,7 @@ int seccomp_restrict_namespaces(unsigned long retain) { if (FLAGS_SET(retain, NAMESPACE_FLAGS_ALL)) return 0; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1543,7 +1522,7 @@ int seccomp_protect_sysctl(void) { uint32_t arch; int r; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1595,7 +1574,7 @@ int seccomp_protect_syslog(void) { uint32_t arch; int r; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1632,7 +1611,7 @@ int seccomp_restrict_address_families(Set *address_families, bool allow_list) { uint32_t arch; int r; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1818,7 +1797,7 @@ int seccomp_restrict_realtime_full(int error_code) { assert(error_code > 0); - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1923,7 +1902,7 @@ int seccomp_memory_deny_write_execute(void) { unsigned loaded = 0; int r; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -2043,7 +2022,7 @@ int seccomp_restrict_archs(Set *archs) { int r; bool blocked_new = false; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -2136,7 +2115,7 @@ int seccomp_filter_set_add_by_name(Hashmap *filter, bool add, const char *name) assert(filter); assert(name); - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -2185,7 +2164,7 @@ int seccomp_lock_personality(unsigned long personality) { if (personality >= PERSONALITY_INVALID) return -EINVAL; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -2223,7 +2202,7 @@ int seccomp_protect_hostname(void) { uint32_t arch; int r; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -2439,7 +2418,7 @@ int seccomp_restrict_suid_sgid(void) { uint32_t arch; int r, k; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -2566,7 +2545,7 @@ int seccomp_suppress_sync(void) { * * Additionally, O_SYNC/O_DSYNC are masked. */ - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -2623,6 +2602,37 @@ int seccomp_suppress_sync(void) { #endif +int dlopen_libseccomp(int log_level) { +#if HAVE_SECCOMP + SD_ELF_NOTE_DLOPEN( + "seccomp", + "Support for Seccomp Sandboxes", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libseccomp.so.2"); + + return dlopen_many_sym_or_warn( + &libseccomp_dl, + "libseccomp.so.2", + log_level, + DLSYM_ARG(seccomp_api_get), + DLSYM_ARG(seccomp_arch_add), + DLSYM_ARG(seccomp_arch_exist), + DLSYM_ARG(seccomp_arch_native), + DLSYM_ARG(seccomp_arch_remove), + DLSYM_ARG(seccomp_attr_set), + DLSYM_ARG(seccomp_init), + DLSYM_ARG(seccomp_load), + DLSYM_ARG(seccomp_release), + DLSYM_ARG(seccomp_rule_add_array), + DLSYM_ARG(seccomp_rule_add_exact), + DLSYM_ARG(seccomp_syscall_resolve_name), + DLSYM_ARG(seccomp_syscall_resolve_num_arch)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libseccomp support is not compiled in."); +#endif +} + bool seccomp_errno_or_action_is_valid(int n) { return n == SECCOMP_ERROR_NUMBER_KILL || errno_is_valid(n); } diff --git a/src/shared/seccomp-util.h b/src/shared/seccomp-util.h index 51c2ba650501b..7037ef52d17d7 100644 --- a/src/shared/seccomp-util.h +++ b/src/shared/seccomp-util.h @@ -23,8 +23,6 @@ extern DLSYM_PROTOTYPE(seccomp_rule_add_exact); extern DLSYM_PROTOTYPE(seccomp_syscall_resolve_name); extern DLSYM_PROTOTYPE(seccomp_syscall_resolve_num_arch); -int dlopen_libseccomp(void); - DECLARE_STRING_TABLE_LOOKUP_TO_STRING(seccomp_arch, uint32_t); int seccomp_arch_from_string(const char *n, uint32_t *ret); @@ -163,12 +161,11 @@ static inline bool is_seccomp_available(void) { return false; } -static inline int dlopen_libseccomp(void) { - return -EOPNOTSUPP; -} #endif +int dlopen_libseccomp(int log_level); + /* This is a special value to be used where syscall filters otherwise expect errno numbers, will be replaced with real seccomp action. */ enum { diff --git a/src/shared/selinux-util.c b/src/shared/selinux-util.c index 4fca62b66d32c..19e0d2b488d4d 100644 --- a/src/shared/selinux-util.c +++ b/src/shared/selinux-util.c @@ -1,30 +1,35 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include #include #include -#include #include +#include "log.h" + #if HAVE_SELINUX +#include +#include +#include + #include #include #include #include -#endif + +#include "sd-dlopen.h" #include "alloc-util.h" -#include "errno-util.h" #include "fd-util.h" #include "label.h" -#include "label-util.h" -#include "log.h" #include "path-util.h" -#include "selinux-util.h" #include "string-util.h" #include "time-util.h" +#endif + +#include "errno-util.h" +#include "label-util.h" +#include "selinux-util.h" #if HAVE_SELINUX DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(context_t, sym_context_free, context_freep, NULL); @@ -86,17 +91,20 @@ DLSYM_PROTOTYPE(setfilecon_raw) = NULL; DLSYM_PROTOTYPE(setfscreatecon_raw) = NULL; DLSYM_PROTOTYPE(setsockcreatecon_raw) = NULL; DLSYM_PROTOTYPE(string_to_security_class) = NULL; +#endif -int dlopen_libselinux(void) { - ELF_NOTE_DLOPEN("selinux", +int dlopen_libselinux(int log_level) { +#if HAVE_SELINUX + SD_ELF_NOTE_DLOPEN( + "selinux", "Support for SELinux", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "libselinux.so.1"); return dlopen_many_sym_or_warn( &libselinux_dl, "libselinux.so.1", - LOG_DEBUG, + log_level, DLSYM_ARG(avc_open), DLSYM_ARG(context_free), DLSYM_ARG(context_new), @@ -131,13 +139,16 @@ int dlopen_libselinux(void) { DLSYM_ARG(setfscreatecon_raw), DLSYM_ARG(setsockcreatecon_raw), DLSYM_ARG(string_to_security_class)); -} +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libselinux support is not compiled in."); #endif +} bool mac_selinux_use(void) { #if HAVE_SELINUX if (_unlikely_(cached_use < 0)) { - if (dlopen_libselinux() < 0) + if (dlopen_libselinux(LOG_DEBUG) < 0) return (cached_use = false); cached_use = sym_is_selinux_enabled() > 0; @@ -157,7 +168,7 @@ bool mac_selinux_enforcing(void) { /* If the SELinux status page has been successfully opened, retrieve the enforcing * status over it to avoid system calls in security_getenforce(). */ - if (dlopen_libselinux() < 0) + if (dlopen_libselinux(LOG_DEBUG) < 0) return false; if (have_status_page) @@ -183,7 +194,7 @@ static int open_label_db(void) { struct mallinfo2 before_mallinfo = {}; int r; - r = dlopen_libselinux(); + r = dlopen_libselinux(LOG_DEBUG); if (r < 0) return r; @@ -297,7 +308,7 @@ void mac_selinux_maybe_reload(void) { if (!initialized) return; - if (dlopen_libselinux() < 0) + if (dlopen_libselinux(LOG_DEBUG) < 0) return; /* Do not use selinux_status_updated(3), cause since libselinux 3.2 selinux_check_access(3), @@ -347,7 +358,7 @@ static int selinux_log_glue(int type, const char *fmt, ...) { void mac_selinux_disable_logging(void) { #if HAVE_SELINUX /* Turn off all of SELinux' own logging, we want to do that ourselves */ - if (dlopen_libselinux() < 0) + if (dlopen_libselinux(LOG_DEBUG) < 0) return; sym_selinux_set_callback(SELINUX_CB_LOG, (const union selinux_callback) { .func_log = selinux_log_glue }); @@ -355,6 +366,19 @@ void mac_selinux_disable_logging(void) { } #if HAVE_SELINUX +static int setfilecon_idempotent(int fd, const char *context) { + _cleanup_freecon_ char *oldcon = NULL; + + assert(fd >= 0); + assert(context); + + /* Read current context via /proc/self/fd/ so this works for O_PATH fds too */ + if (sym_getfilecon_raw(FORMAT_PROC_FD_PATH(fd), &oldcon) >= 0 && streq_ptr(context, oldcon)) + return 0; /* Already correct */ + + return RET_NERRNO(sym_setfilecon_raw(FORMAT_PROC_FD_PATH(fd), context)); +} + static int selinux_fix_fd( int fd, const char *label_path, @@ -384,25 +408,19 @@ static int selinux_fix_fd( return log_selinux_enforcing_errno(errno, "Unable to lookup intended SELinux security context of %s: %m", label_path); } - r = RET_NERRNO(sym_setfilecon_raw(FORMAT_PROC_FD_PATH(fd), fcon)); - if (r < 0) { - /* If the FS doesn't support labels, then exit without warning */ - if (ERRNO_IS_NOT_SUPPORTED(r)) - return 0; - - /* It the FS is read-only and we were told to ignore failures caused by that, suppress error */ - if (r == -EROFS && (flags & LABEL_IGNORE_EROFS)) - return 0; + r = setfilecon_idempotent(fd, fcon); + if (r >= 0) + return 0; - /* If the old label is identical to the new one, suppress any kind of error */ - _cleanup_freecon_ char *oldcon = NULL; - if (sym_getfilecon_raw(FORMAT_PROC_FD_PATH(fd), &oldcon) >= 0 && streq_ptr(fcon, oldcon)) - return 0; + /* If the FS doesn't support labels, then exit without warning */ + if (ERRNO_IS_NOT_SUPPORTED(r)) + return 0; - return log_selinux_enforcing_errno(r, "Unable to fix SELinux security context of %s: %m", label_path); - } + /* If the FS is read-only and we were told to ignore failures caused by that, suppress error */ + if (r == -EROFS && (flags & LABEL_IGNORE_EROFS)) + return 0; - return 0; + return log_selinux_enforcing_errno(r, "Unable to fix SELinux security context of %s: %m", label_path); } #endif @@ -490,8 +508,9 @@ int mac_selinux_apply_fd(int fd, const char *path, const char *label) { assert(label); - if (sym_setfilecon_raw(FORMAT_PROC_FD_PATH(fd), label) < 0) - return log_selinux_enforcing_errno(errno, "Failed to set SELinux security context %s on path %s: %m", label, strna(path)); + r = setfilecon_idempotent(fd, label); + if (r < 0) + return log_selinux_enforcing_errno(r, "Failed to set SELinux security context %s on path %s: %m", label, strna(path)); #endif return 0; } diff --git a/src/shared/selinux-util.h b/src/shared/selinux-util.h index ab499e8c4fff2..f73503bc4b29e 100644 --- a/src/shared/selinux-util.h +++ b/src/shared/selinux-util.h @@ -13,8 +13,6 @@ #include "dlfcn-util.h" -int dlopen_libselinux(void); - extern DLSYM_PROTOTYPE(avc_open); extern DLSYM_PROTOTYPE(context_free); extern DLSYM_PROTOTYPE(context_new); @@ -54,15 +52,14 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(char*, sym_freecon, freeconp, NULL); #else -static inline int dlopen_libselinux(void) { - return -EOPNOTSUPP; -} static inline void freeconp(char **p) { assert(*p == NULL); } #endif +int dlopen_libselinux(int log_level); + #define _cleanup_freecon_ _cleanup_(freeconp) /* This accepts 0 error, like _zerook(). */ diff --git a/src/shared/shared-forward.h b/src/shared/shared-forward.h index e07b25c056aac..1207fe8a25826 100644 --- a/src/shared/shared-forward.h +++ b/src/shared/shared-forward.h @@ -45,6 +45,7 @@ typedef enum UserDBFlags UserDBFlags; typedef enum UserRecordLoadFlags UserRecordLoadFlags; typedef enum UserStorage UserStorage; +typedef struct AskPasswordRequest AskPasswordRequest; typedef struct Bitmap Bitmap; typedef struct BootConfig BootConfig; typedef struct BPFProgram BPFProgram; @@ -78,6 +79,8 @@ typedef struct MountOptions MountOptions; typedef struct MStack MStack; typedef struct OpenFile OpenFile; typedef struct Pkcs11EncryptedKey Pkcs11EncryptedKey; +typedef struct QmpClient QmpClient; +typedef struct QmpSlot QmpSlot; typedef struct Table Table; typedef struct Tpm2Context Tpm2Context; typedef struct Tpm2Handle Tpm2Handle; diff --git a/src/shared/shift-uid.c b/src/shared/shift-uid.c index 2dcd5f2359715..e455306be91f9 100644 --- a/src/shared/shift-uid.c +++ b/src/shared/shift-uid.c @@ -165,7 +165,7 @@ static int patch_acls(int fd, const char *name, const struct stat *st, uid_t shi if (!inode_type_can_acl(st->st_mode)) return 0; - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return 0; if (r < 0) diff --git a/src/shared/smack-util.c b/src/shared/smack-util.c index 6faec02a9f8ee..e1dbf8686edda 100644 --- a/src/shared/smack-util.c +++ b/src/shared/smack-util.c @@ -149,16 +149,8 @@ static int smack_fix_fd( to ignore failures caused by that, suppress error */ return 0; - if (r < 0) { - /* If the old label is identical to the new one, suppress any kind of error */ - _cleanup_free_ char *old_label = NULL; - - if (fgetxattr_malloc(fd, "security.SMACK64", &old_label, /* ret_size= */ NULL) >= 0 && - streq(old_label, label)) - return 0; - + if (r < 0) return log_debug_errno(r, "Unable to fix SMACK label of '%s': %m", label_path); - } return 0; } diff --git a/src/shared/snapshot-util.c b/src/shared/snapshot-util.c index 93fd886d7bab1..6e73bef78f1ba 100644 --- a/src/shared/snapshot-util.c +++ b/src/shared/snapshot-util.c @@ -23,6 +23,8 @@ int create_ephemeral_snapshot( _cleanup_free_ char *np = NULL; int r; + assert(ret_new_path); + /* If the specified path is a mount point we generate the new snapshot immediately * inside it under a random name. However if the specified is not a mount point we * create the new snapshot in the parent directory, just next to it. */ diff --git a/src/shared/socket-forward.c b/src/shared/socket-forward.c new file mode 100644 index 0000000000000..10c0a9be6793b --- /dev/null +++ b/src/shared/socket-forward.c @@ -0,0 +1,406 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "sd-event.h" + +#include "alloc-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "log.h" +#include "socket-forward.h" + +#define SOCKET_FORWARD_BUFFER_SIZE (256 * 1024) + +/* Unidirectional forwarder: splices data from read_fd to write_fd via a kernel pipe buffer. + * Each direction of a full-duplex SocketForward is handled by one of these. */ +typedef struct SimplexForward { + sd_event *event; + + int read_fd, write_fd; + + int buffer[2]; /* a pipe */ + + size_t buffer_full, buffer_size; + + sd_event_source *read_event_source, *write_event_source; + + int (*on_done)(struct SimplexForward *fwd, int error, void *userdata); + void *userdata; +} SimplexForward; + +static SimplexForward* simplex_forward_free(SimplexForward *fwd) { + if (!fwd) + return NULL; + + sd_event_source_unref(fwd->read_event_source); + sd_event_source_unref(fwd->write_event_source); + + safe_close(fwd->read_fd); + safe_close(fwd->write_fd); + + safe_close_pair(fwd->buffer); + + sd_event_unref(fwd->event); + + return mfree(fwd); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(SimplexForward*, simplex_forward_free); + +static int socket_forward_create_pipes(int buffer[static 2], size_t *ret_size) { + int r; + + assert(buffer); + assert(ret_size); + + if (buffer[0] >= 0) + return 0; + + r = pipe2(buffer, O_CLOEXEC|O_NONBLOCK); + if (r < 0) + return log_debug_errno(errno, "Failed to allocate pipe buffer: %m"); + + (void) fcntl(buffer[0], F_SETPIPE_SZ, SOCKET_FORWARD_BUFFER_SIZE); + + r = fcntl(buffer[0], F_GETPIPE_SZ); + if (r < 0) + return log_debug_errno(errno, "Failed to get pipe buffer size: %m"); + + assert(r > 0); + *ret_size = r; + + return 0; +} + +static int simplex_forward_shovel( + int *from, int buffer[2], int *to, + size_t *full, size_t *sz, + sd_event_source **from_source, sd_event_source **to_source) { + + bool shoveled; + + assert(from); + assert(buffer); + assert(buffer[0] >= 0); + assert(buffer[1] >= 0); + assert(to); + assert(full); + assert(sz); + assert(from_source); + assert(to_source); + + do { + ssize_t z; + + shoveled = false; + + if (*full < *sz && *from >= 0 && *to >= 0) { + z = splice(*from, NULL, buffer[1], NULL, *sz - *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK); + if (z > 0) { + *full += z; + shoveled = true; + } else if (z == 0 || ERRNO_IS_DISCONNECT(errno)) { + *from_source = sd_event_source_unref(*from_source); + *from = safe_close(*from); + } else if (!ERRNO_IS_TRANSIENT(errno)) + return log_debug_errno(errno, "Failed to splice: %m"); + } + + if (*full > 0 && *to >= 0) { + z = splice(buffer[0], NULL, *to, NULL, *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK); + if (z > 0) { + *full -= z; + shoveled = true; + } else if (z == 0 || ERRNO_IS_DISCONNECT(errno)) { + *to_source = sd_event_source_unref(*to_source); + *to = safe_close(*to); + } else if (!ERRNO_IS_TRANSIENT(errno)) + return log_debug_errno(errno, "Failed to splice: %m"); + } + } while (shoveled); + + return 0; +} + +static int simplex_forward_enable_event_sources(SimplexForward *fwd); + +static int simplex_forward_traffic(SimplexForward *fwd) { + int r; + + r = simplex_forward_shovel( + &fwd->read_fd, fwd->buffer, &fwd->write_fd, + &fwd->buffer_full, &fwd->buffer_size, + &fwd->read_event_source, &fwd->write_event_source); + if (r < 0) + goto quit; + + /* Read side closed and all buffered data written? */ + if (fwd->read_fd < 0 && fwd->buffer_full <= 0) + goto quit; + + /* Write side closed? */ + if (fwd->write_fd < 0) + goto quit; + + r = simplex_forward_enable_event_sources(fwd); + if (r < 0) + goto quit; + + return 1; + +quit: + fwd->read_event_source = sd_event_source_disable_unref(fwd->read_event_source); + fwd->write_event_source = sd_event_source_disable_unref(fwd->write_event_source); + return fwd->on_done(fwd, r, fwd->userdata); +} + +static int simplex_forward_io_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + SimplexForward *fwd = ASSERT_PTR(userdata); + + return simplex_forward_traffic(fwd); +} + +static int simplex_forward_defer_cb(sd_event_source *s, void *userdata) { + SimplexForward *fwd = ASSERT_PTR(userdata); + + return simplex_forward_traffic(fwd); +} + +static int simplex_forward_enable_event_sources(SimplexForward *fwd) { + bool can_read, can_write; + int r; + + assert(fwd); + + can_read = fwd->buffer_full < fwd->buffer_size; + can_write = fwd->buffer_full > 0; + + /* Event sources may have been unref'd by the shovel on EOF/disconnect */ + if (fwd->read_event_source) { + r = sd_event_source_set_enabled(fwd->read_event_source, can_read ? SD_EVENT_ONESHOT : SD_EVENT_OFF); + if (r < 0) + return log_debug_errno(r, "Failed to update read event source: %m"); + } + + if (fwd->write_event_source) { + r = sd_event_source_set_enabled(fwd->write_event_source, can_write ? SD_EVENT_ONESHOT : SD_EVENT_OFF); + if (r < 0) + return log_debug_errno(r, "Failed to update write event source: %m"); + } + + return 0; +} + +static int simplex_forward_create_event_source( + SimplexForward *fwd, + sd_event_source **ret, + int fd, + uint32_t events) { + + int r; + + r = sd_event_add_io(fwd->event, ret, fd, events, simplex_forward_io_cb, fwd); + if (r == -EPERM) + /* fd is not pollable (e.g. regular file). Fall back to a defer event source + * which fires on each event loop iteration. This works because regular + * file are always ready for I/O so we don't need to poll. */ + r = sd_event_add_defer(fwd->event, ret, simplex_forward_defer_cb, fwd); + + return r; +} + +static int simplex_forward_new( + sd_event *event, + int read_fd, + int write_fd, + int (*on_done)(SimplexForward *fwd, int error, void *userdata), + void *userdata, + SimplexForward **ret) { + + _cleanup_(simplex_forward_freep) SimplexForward *fwd = NULL; + int r; + + assert(event); + assert(read_fd >= 0); + assert(write_fd >= 0); + assert(read_fd != write_fd); + assert(on_done); + assert(ret); + + fwd = new(SimplexForward, 1); + if (!fwd) { + safe_close(read_fd); + safe_close(write_fd); + return log_oom_debug(); + } + + *fwd = (SimplexForward) { + .event = sd_event_ref(event), + .read_fd = read_fd, + .write_fd = write_fd, + .buffer = EBADF_PAIR, + .on_done = on_done, + .userdata = userdata, + }; + + r = socket_forward_create_pipes(fwd->buffer, &fwd->buffer_size); + if (r < 0) + return r; + + r = simplex_forward_create_event_source(fwd, &fwd->read_event_source, fwd->read_fd, EPOLLIN); + if (r < 0) + return r; + + r = simplex_forward_create_event_source(fwd, &fwd->write_event_source, fwd->write_fd, EPOLLOUT); + if (r < 0) + return r; + + r = simplex_forward_enable_event_sources(fwd); + if (r < 0) + return r; + + *ret = TAKE_PTR(fwd); + return 0; +} + +/* Full-duplex forwarder from two SimplexForward instances */ +struct SocketForward { + SimplexForward *server_to_client; + SimplexForward *client_to_server; + + socket_forward_done_t on_done; + void *userdata; + + int first_error; + unsigned directions_done; +}; + +SocketForward* socket_forward_free(SocketForward *sf) { + if (!sf) + return NULL; + + simplex_forward_free(sf->server_to_client); + simplex_forward_free(sf->client_to_server); + + return mfree(sf); +} + +static int socket_forward_direction_done(SimplexForward *fwd, int error, void *userdata) { + SocketForward *sf = ASSERT_PTR(userdata); + + /* Half-close the write side so the remote end sees EOF. For sockets, + * shutdown(SHUT_WR) sends FIN while keeping the fd open for the read side + * (which belongs to the other direction's dup'd fd). For pipes/FIFOs, + * shutdown() fails with ENOTSOCK - close the fd instead, which is the + * only way to signal EOF on a pipe. */ + if (fwd->write_fd >= 0 && shutdown(fwd->write_fd, SHUT_WR) < 0) { + if (errno == ENOTSOCK) + fwd->write_fd = safe_close(fwd->write_fd); + else + log_debug_errno(errno, "Failed to shutdown write side of fd %d: %m, ignoring", + fwd->write_fd); + } + + if (error < 0 && sf->first_error >= 0) + sf->first_error = error; + + sf->directions_done++; + + if (sf->directions_done >= 2) + return sf->on_done(sf, sf->first_error, sf->userdata); + + return 0; +} + +int socket_forward_new_pair( + sd_event *event, + int server_read_fd, + int server_write_fd, + int client_read_fd, + int client_write_fd, + socket_forward_done_t on_done, + void *userdata, + SocketForward **ret) { + + _cleanup_close_ int server_read_fd_close = server_read_fd, + server_write_fd_close = server_write_fd, + client_read_fd_close = client_read_fd, + client_write_fd_close = client_write_fd; + _cleanup_(socket_forward_freep) SocketForward *sf = NULL; + int r; + + assert(event); + assert(server_read_fd >= 0); + assert(server_write_fd >= 0); + assert(client_read_fd >= 0); + assert(client_write_fd >= 0); + assert(server_read_fd != server_write_fd); + assert(client_read_fd != client_write_fd); + assert(server_read_fd != client_read_fd); + assert(server_read_fd != client_write_fd); + assert(server_write_fd != client_read_fd); + assert(server_write_fd != client_write_fd); + assert(on_done); + assert(ret); + + sf = new(SocketForward, 1); + if (!sf) + return log_oom_debug(); + + *sf = (SocketForward) { + .on_done = on_done, + .userdata = userdata, + }; + + r = simplex_forward_new(event, + TAKE_FD(server_read_fd_close), TAKE_FD(client_write_fd_close), + socket_forward_direction_done, sf, + &sf->server_to_client); + if (r < 0) + return r; + + r = simplex_forward_new(event, + TAKE_FD(client_read_fd_close), TAKE_FD(server_write_fd_close), + socket_forward_direction_done, sf, + &sf->client_to_server); + if (r < 0) + return r; + + *ret = TAKE_PTR(sf); + return 0; +} + +int socket_forward_new( + sd_event *event, + int server_fd, + int client_fd, + socket_forward_done_t on_done, + void *userdata, + SocketForward **ret) { + + _cleanup_close_ int server_fd_close = server_fd, client_fd_close = client_fd, + server_write_fd = -EBADF, client_write_fd = -EBADF; + + assert(event); + assert(server_fd >= 0); + assert(client_fd >= 0); + assert(on_done); + assert(ret); + + server_write_fd = fcntl(server_fd, F_DUPFD_CLOEXEC, 3); + if (server_write_fd < 0) + return -errno; + + client_write_fd = fcntl(client_fd, F_DUPFD_CLOEXEC, 3); + if (client_write_fd < 0) + return -errno; + + return socket_forward_new_pair( + event, + TAKE_FD(server_fd_close), TAKE_FD(server_write_fd), + TAKE_FD(client_fd_close), TAKE_FD(client_write_fd), + on_done, userdata, ret); +} diff --git a/src/shared/socket-forward.h b/src/shared/socket-forward.h new file mode 100644 index 0000000000000..a5b81b4a44f43 --- /dev/null +++ b/src/shared/socket-forward.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +/* Bidirectional forwarder using splice(). + * + * Forwards data between two sides ("server" and "client") via kernel pipe buffers, + * avoiding userspace copies. Internally uses two independent half-duplex forwarders, + * one per direction. All four fds must be distinct - use dup()/fcntl(fd, F_DUPFD_CLOEXEC, 3) for bidirectional sockets. + * + * When forwarding completes (both directions reach EOF or error), the completion callback is invoked. + * + * The SocketForward takes ownership of all fds - they are closed when the SocketForward is freed + * (or earlier, during normal forwarding when EOF/disconnect is detected). */ + +typedef struct SocketForward SocketForward; + +typedef int (*socket_forward_done_t)(SocketForward *sf, int error, void *userdata); + +/* Create a forwarder between two bidirectional sockets. */ +int socket_forward_new( + sd_event *event, + int server_fd, + int client_fd, + socket_forward_done_t on_done, + void *userdata, + SocketForward **ret); + +/* Create a forwarder between two fd pairs (e.g. stdin/stdout on one side, socket on the other). + * All four fds must be distinct - use dup()/fcntl(fd, F_DUPFD_CLOEXEC, 3) for bidirectional sockets. */ +int socket_forward_new_pair( + sd_event *event, + int server_read_fd, + int server_write_fd, + int client_read_fd, + int client_write_fd, + socket_forward_done_t on_done, + void *userdata, + SocketForward **ret); + +SocketForward* socket_forward_free(SocketForward *sf); +DEFINE_TRIVIAL_CLEANUP_FUNC(SocketForward*, socket_forward_free); diff --git a/src/shared/socket-netlink.c b/src/shared/socket-netlink.c index 885606b6e0da2..1112a090840ec 100644 --- a/src/shared/socket-netlink.c +++ b/src/shared/socket-netlink.c @@ -18,6 +18,7 @@ #include "socket-label.h" #include "socket-netlink.h" #include "socket-util.h" +#include "stat-util.h" #include "string-util.h" int socket_address_parse(SocketAddress *a, const char *s) { @@ -98,6 +99,8 @@ int socket_address_parse_and_warn(SocketAddress *a, const char *s) { SocketAddress b; int r; + assert(a); + /* Similar to socket_address_parse() but warns for IPv6 sockets when we don't support them. */ r = socket_address_parse(&b, s); @@ -348,7 +351,7 @@ int in_addr_port_ifindex_name_from_string_auto( return r; } -struct in_addr_full *in_addr_full_free(struct in_addr_full *a) { +struct in_addr_full* in_addr_full_free(struct in_addr_full *a) { if (!a) return NULL; @@ -357,14 +360,7 @@ struct in_addr_full *in_addr_full_free(struct in_addr_full *a) { return mfree(a); } -void in_addr_full_array_free(struct in_addr_full *addrs[], size_t n) { - assert(addrs || n == 0); - - FOREACH_ARRAY(a, addrs, n) - in_addr_full_freep(a); - - free(addrs); -} +DEFINE_POINTER_ARRAY_FREE_FUNC(struct in_addr_full*, in_addr_full_free); int in_addr_full_new( int family, @@ -377,6 +373,7 @@ int in_addr_full_new( _cleanup_free_ char *name = NULL; struct in_addr_full *x; + assert(a); assert(ret); if (!isempty(server_name)) { @@ -502,8 +499,9 @@ int af_unix_get_qlen(int fd, uint32_t *ret) { struct stat st; if (fstat(fd, &st) < 0) return -errno; - if (!S_ISSOCK(st.st_mode)) - return -ENOTSOCK; + r = stat_verify_socket(&st); + if (r < 0) + return r; _cleanup_(sd_netlink_unrefp) sd_netlink *nl = NULL; r = sd_sock_diag_socket_open(&nl); diff --git a/src/shared/socket-netlink.h b/src/shared/socket-netlink.h index f371967c6908b..be15398ba545a 100644 --- a/src/shared/socket-netlink.h +++ b/src/shared/socket-netlink.h @@ -36,9 +36,9 @@ struct in_addr_full { char *cached_server_string; /* Should not be handled directly, but through in_addr_full_to_string(). */ }; -struct in_addr_full *in_addr_full_free(struct in_addr_full *a); +struct in_addr_full* in_addr_full_free(struct in_addr_full *a); DEFINE_TRIVIAL_CLEANUP_FUNC(struct in_addr_full*, in_addr_full_free); -void in_addr_full_array_free(struct in_addr_full *addrs[], size_t n); +void in_addr_full_free_array(struct in_addr_full **array, size_t n); int in_addr_full_new(int family, const union in_addr_union *a, uint16_t port, int ifindex, const char *server_name, struct in_addr_full **ret); int in_addr_full_new_from_string(const char *s, struct in_addr_full **ret); const char* in_addr_full_to_string(struct in_addr_full *a); diff --git a/src/shared/specifier.c b/src/shared/specifier.c index 62b036a74ef6c..595160352225b 100644 --- a/src/shared/specifier.c +++ b/src/shared/specifier.c @@ -139,6 +139,8 @@ int specifier_id128(char specifier, const void *data, const char *root, const vo const sd_id128_t *id = ASSERT_PTR(data); char *n; + assert(ret); + n = new(char, SD_ID128_STRING_MAX); if (!n) return -ENOMEM; @@ -151,6 +153,8 @@ int specifier_uuid(char specifier, const void *data, const char *root, const voi const sd_id128_t *id = ASSERT_PTR(data); char *n; + assert(ret); + n = new(char, SD_ID128_UUID_STRING_MAX); if (!n) return -ENOMEM; diff --git a/src/shared/ssl-util.c b/src/shared/ssl-util.c new file mode 100644 index 0000000000000..82ed54d037e14 --- /dev/null +++ b/src/shared/ssl-util.c @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dlopen.h" + +#include "log.h" /* IWYU pragma: keep */ +#include "ssl-util.h" + +#if HAVE_OPENSSL + +static void *libssl_dl = NULL; + +DLSYM_PROTOTYPE(SSL_ctrl) = NULL; +DLSYM_PROTOTYPE(SSL_CTX_ctrl) = NULL; +DLSYM_PROTOTYPE(SSL_CTX_free) = NULL; +DLSYM_PROTOTYPE(SSL_CTX_new) = NULL; +DLSYM_PROTOTYPE(SSL_CTX_set_default_verify_paths) = NULL; +DLSYM_PROTOTYPE(SSL_CTX_set_options) = NULL; +DLSYM_PROTOTYPE(SSL_do_handshake) = NULL; +DLSYM_PROTOTYPE(SSL_free) = NULL; +DLSYM_PROTOTYPE(SSL_get_error) = NULL; +DLSYM_PROTOTYPE(SSL_get_wbio) = NULL; +DLSYM_PROTOTYPE(SSL_get0_param) = NULL; +DLSYM_PROTOTYPE(SSL_get1_session) = NULL; +DLSYM_PROTOTYPE(SSL_new) = NULL; +DLSYM_PROTOTYPE(SSL_read) = NULL; +DLSYM_PROTOTYPE(SSL_SESSION_free) = NULL; +DLSYM_PROTOTYPE(SSL_set_bio) = NULL; +DLSYM_PROTOTYPE(SSL_set_connect_state) = NULL; +DLSYM_PROTOTYPE(SSL_set_session) = NULL; +DLSYM_PROTOTYPE(SSL_set_verify) = NULL; +DLSYM_PROTOTYPE(SSL_shutdown) = NULL; +DLSYM_PROTOTYPE(SSL_write) = NULL; +DLSYM_PROTOTYPE(TLS_client_method) = NULL; + +#endif + +int dlopen_libssl(int log_level) { +#if HAVE_OPENSSL + SD_ELF_NOTE_DLOPEN( + "libssl", + "Support for TLS", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libssl.so.3"); + + return dlopen_many_sym_or_warn( + &libssl_dl, + "libssl.so.3", + log_level, + DLSYM_ARG(SSL_ctrl), + DLSYM_ARG(SSL_CTX_ctrl), + DLSYM_ARG(SSL_CTX_free), + DLSYM_ARG(SSL_CTX_new), + DLSYM_ARG(SSL_CTX_set_default_verify_paths), + DLSYM_ARG(SSL_CTX_set_options), + DLSYM_ARG(SSL_do_handshake), + DLSYM_ARG(SSL_free), + DLSYM_ARG(SSL_get_error), + DLSYM_ARG(SSL_get_wbio), + DLSYM_ARG(SSL_get0_param), + DLSYM_ARG(SSL_get1_session), + DLSYM_ARG(SSL_new), + DLSYM_ARG(SSL_read), + DLSYM_ARG(SSL_SESSION_free), + DLSYM_ARG(SSL_set_bio), + DLSYM_ARG(SSL_set_connect_state), + DLSYM_ARG(SSL_set_session), + DLSYM_ARG(SSL_set_verify), + DLSYM_ARG(SSL_shutdown), + DLSYM_ARG(SSL_write), + DLSYM_ARG(TLS_client_method)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libssl support is not compiled in."); +#endif +} diff --git a/src/shared/ssl-util.h b/src/shared/ssl-util.h new file mode 100644 index 0000000000000..7deb028cfe3de --- /dev/null +++ b/src/shared/ssl-util.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +int dlopen_libssl(int log_level); + +#if HAVE_OPENSSL + +# include /* IWYU pragma: export */ + +# include "dlfcn-util.h" + +extern DLSYM_PROTOTYPE(SSL_ctrl); +extern DLSYM_PROTOTYPE(SSL_CTX_ctrl); +extern DLSYM_PROTOTYPE(SSL_CTX_free); +extern DLSYM_PROTOTYPE(SSL_CTX_new); +extern DLSYM_PROTOTYPE(SSL_CTX_set_default_verify_paths); +extern DLSYM_PROTOTYPE(SSL_CTX_set_options); +extern DLSYM_PROTOTYPE(SSL_do_handshake); +extern DLSYM_PROTOTYPE(SSL_free); +extern DLSYM_PROTOTYPE(SSL_get_error); +extern DLSYM_PROTOTYPE(SSL_get_wbio); +extern DLSYM_PROTOTYPE(SSL_get0_param); +extern DLSYM_PROTOTYPE(SSL_get1_session); +extern DLSYM_PROTOTYPE(SSL_new); +extern DLSYM_PROTOTYPE(SSL_read); +extern DLSYM_PROTOTYPE(SSL_SESSION_free); +extern DLSYM_PROTOTYPE(SSL_set_bio); +extern DLSYM_PROTOTYPE(SSL_set_connect_state); +extern DLSYM_PROTOTYPE(SSL_set_session); +extern DLSYM_PROTOTYPE(SSL_set_verify); +extern DLSYM_PROTOTYPE(SSL_shutdown); +extern DLSYM_PROTOTYPE(SSL_write); +extern DLSYM_PROTOTYPE(TLS_client_method); + +/* Mirrors of OpenSSL macros that go through our dlopen'd sym_* variants, so we don't end up linking against + * libssl just for these. */ +#define sym_SSL_set_tlsext_host_name(s, name) \ + sym_SSL_ctrl((s), SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, (void *) (name)) +#define sym_SSL_CTX_set_min_proto_version(ctx, version) \ + sym_SSL_CTX_ctrl((ctx), SSL_CTRL_SET_MIN_PROTO_VERSION, (version), NULL) + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(SSL*, sym_SSL_free, SSL_freep, NULL); + +#endif diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index 6017200d79c3e..08710a8966400 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -54,7 +54,7 @@ int switch_root(const char *new_root, if (new_root_fd < 0) return log_error_errno(errno, "Failed to open target directory '%s': %m", new_root); - r = fds_are_same_mount(old_root_fd, new_root_fd); /* checks if referenced inodes and mounts match */ + r = fds_inode_and_mount_same(old_root_fd, new_root_fd); /* checks if referenced inodes and mounts match */ if (r < 0) return log_error_errno(r, "Failed to check if old and new root directory/mount are the same: %m"); if (r > 0) { diff --git a/src/shared/swtpm-util.c b/src/shared/swtpm-util.c new file mode 100644 index 0000000000000..156a5422df9c5 --- /dev/null +++ b/src/shared/swtpm-util.c @@ -0,0 +1,238 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-json.h" + +#include "alloc-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "json-util.h" +#include "log.h" +#include "memfd-util.h" +#include "path-util.h" +#include "pidref.h" +#include "process-util.h" +#include "string-util.h" +#include "strv.h" +#include "swtpm-util.h" + +static int swtpm_find_best_profile(const char *swtpm_setup, char **ret) { + int r; + + assert(swtpm_setup); + assert(ret); + + _cleanup_strv_free_ char **args = strv_new( + swtpm_setup, + "--tpm2", + "--print-profiles"); + if (!args) + return log_oom(); + + _cleanup_close_ int mfd = memfd_new("swtpm-profiles"); + if (mfd < 0) + return log_error_errno(mfd, "Failed to allocate memfd: %m"); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *cmdline = quote_command_line(args, SHELL_ESCAPE_EMPTY); + log_debug("About to spawn: %s", strnull(cmdline)); + } + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = pidref_safe_fork_full( + "(swtpm-lprof)", + (int[]) { -EBADF, mfd, STDERR_FILENO }, + /* except_fds= */ NULL, + /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_REARRANGE_STDIO|FORK_LOG, + &pidref); + if (r < 0) + return log_error_errno(r, "Failed to run swtpm_setup: %m"); + if (r == 0) { + /* Child */ + execvp(args[0], args); + log_error_errno(errno, "Failed to execute '%s': %m", args[0]); + _exit(EXIT_FAILURE); + } + + r = pidref_wait_for_terminate_and_check("(swtpm-lprof)", &pidref, WAIT_LOG_ABNORMAL); + if (r < 0) + return r; + + /* NB: we ignore the exit status of --print-profiles, it's broken. Instead we check if we have + * received a valid JSON object via STDOUT. */ + (void) r; + + _cleanup_free_ char *text = NULL; + r = read_full_file_full( + mfd, + /* filename= */ NULL, + /* offset= */ 0, + /* size= */ SIZE_MAX, + /* flags= */ 0, + /* bind_name= */ NULL, + &text, + /* ret_size= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to read memory fd: %m"); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + r = sd_json_parse(text, SD_JSON_PARSE_MUST_BE_OBJECT, &j, /* reterr_line= */ NULL, /* reterr_column= */ NULL); + if (r < 0) { + log_notice("Failed to parse swtpm's --print-profiles output as JSON, assuming the implementation is too old to know the concept of profiles."); + *ret = NULL; + return 0; + } + + sd_json_variant *v = sd_json_variant_by_key(j, "builtin"); + if (!v) { + *ret = NULL; + return 0; + } + + if (!sd_json_variant_is_array(v)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'builtin' field is not an array."); + + const char *best_profile = NULL; + sd_json_variant *i; + JSON_VARIANT_ARRAY_FOREACH(i, v) { + if (!sd_json_variant_is_object(i)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Profile object is not a JSON object."); + + sd_json_variant *n = sd_json_variant_by_key(i, "Name"); + if (!n) { + log_debug("Object in profiles array does not have a 'Name', skipping."); + continue; + } + + if (!sd_json_variant_is_string(n)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Profile's 'Name' field is not a string."); + + const char *s = sd_json_variant_string(n); + + /* Pick the best of the default-v1, default-v2, … profiles */ + if (!startswith(s, "default-v")) + continue; + if (!best_profile || strverscmp_improved(s, best_profile) > 0) + best_profile = s; + } + + _cleanup_free_ char *copy = NULL; + if (best_profile) { + copy = strdup(best_profile); + if (!copy) + return log_oom(); + } + + *ret = TAKE_PTR(copy); + return 0; +} + +int manufacture_swtpm(const char *state_dir, const char *secret) { + int r; + + assert(state_dir); + + _cleanup_free_ char *swtpm_setup = NULL; + r = find_executable("swtpm_setup", &swtpm_setup); + if (r < 0) + return log_error_errno(r, "Failed to find 'swtpm_setup' binary: %m"); + + _cleanup_free_ char *best_profile = NULL; + r = swtpm_find_best_profile(swtpm_setup, &best_profile); + if (r < 0) + return r; + + /* Create custom swtpm config files so that swtpm_localca uses our state directory instead of + * the system-wide /var/lib/swtpm-localca/ which may not be writable. */ + _cleanup_free_ char *localca_conf = path_join(state_dir, "swtpm-localca.conf"); + if (!localca_conf) + return log_oom(); + + r = write_string_filef( + localca_conf, + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE|WRITE_STRING_FILE_MKDIR_0755, + "statedir = %1$s\n" + "signingkey = %1$s/signing-private-key.pem\n" + "issuercert = %1$s/issuer-certificate.pem\n" + "certserial = %1$s/certserial\n", + state_dir); + if (r < 0) + return log_error_errno(r, "Failed to write swtpm-localca.conf: %m"); + + _cleanup_free_ char *localca_options = path_join(state_dir, "swtpm-localca.options"); + if (!localca_options) + return log_oom(); + + r = write_string_file( + localca_options, + "--platform-manufacturer systemd\n" + "--platform-version 2.1\n" + "--platform-model swtpm\n", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE|WRITE_STRING_FILE_MKDIR_0755); + if (r < 0) + return log_error_errno(r, "Failed to write swtpm-localca.options: %m"); + + _cleanup_free_ char *swtpm_localca = NULL; + r = find_executable("swtpm_localca", &swtpm_localca); + if (r < 0) + return log_error_errno(r, "Failed to find 'swtpm_localca' binary: %m"); + + _cleanup_free_ char *setup_conf = path_join(state_dir, "swtpm_setup.conf"); + if (!setup_conf) + return log_oom(); + + r = write_string_filef( + setup_conf, + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE|WRITE_STRING_FILE_MKDIR_0755, + "create_certs_tool = %1$s\n" + "create_certs_tool_config = %2$s\n" + "create_certs_tool_options = %3$s\n", + swtpm_localca, + localca_conf, + localca_options); + if (r < 0) + return log_error_errno(r, "Failed to write swtpm_setup.conf: %m"); + + _cleanup_strv_free_ char **args = strv_new( + swtpm_setup, + "--tpm-state", state_dir, + "--tpm2", + "--pcr-banks", "sha256", + "--ecc", + "--createek", + "--create-ek-cert", + "--create-platform-cert", + "--not-overwrite", + "--config", setup_conf); + if (!args) + return log_oom(); + + if (secret && strv_extendf(&args, "--keyfile=%s", secret) < 0) + return log_oom(); + + if (best_profile && strv_extendf(&args, "--profile-name=%s", best_profile) < 0) + return log_oom(); + + if (!DEBUG_LOGGING && strv_extend_many(&args, "--logfile", "/dev/null") < 0) + return log_oom(); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *cmdline = quote_command_line(args, SHELL_ESCAPE_EMPTY); + log_debug("About to spawn: %s", strnull(cmdline)); + } + + r = pidref_safe_fork("(swtpm-setup)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_LOG|FORK_WAIT, /* ret= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to run swtpm_setup: %m"); + if (r == 0) { + /* Child */ + execvp(args[0], args); + log_error_errno(errno, "Failed to execute '%s': %m", args[0]); + _exit(EXIT_FAILURE); + } + + return 0; +} diff --git a/src/shared/swtpm-util.h b/src/shared/swtpm-util.h new file mode 100644 index 0000000000000..9c1c7377218e5 --- /dev/null +++ b/src/shared/swtpm-util.h @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int manufacture_swtpm(const char *state_dir, const char *secret); diff --git a/src/shared/tar-util.c b/src/shared/tar-util.c index 17c03e910bb74..97c41adba9a45 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -69,14 +69,7 @@ static void xattr_done(XAttr *xa) { iovec_done(&xa->data); } -static void xattr_done_many(XAttr *xa, size_t n) { - assert(xa || n == 0); - - FOREACH_ARRAY(i, xa, n) - xattr_done(i); - - free(xa); -} +static DEFINE_ARRAY_FREE_FUNC(xattr_free_array, XAttr, xattr_done); static void open_inode_done(OpenInode *of) { assert(of); @@ -87,7 +80,7 @@ static void open_inode_done(OpenInode *of) { of->fd = safe_close(of->fd); of->path = mfree(of->path); } - xattr_done_many(of->xattr, of->n_xattr); + xattr_free_array(of->xattr, of->n_xattr); #if HAVE_ACL if (of->acl_access) sym_acl_free(of->acl_access); @@ -96,14 +89,7 @@ static void open_inode_done(OpenInode *of) { #endif } -static void open_inode_done_many(OpenInode *array, size_t n) { - assert(array || n == 0); - - FOREACH_ARRAY(i, array, n) - open_inode_done(i); - - free(array); -} +static DEFINE_ARRAY_FREE_FUNC(open_inode_free_array, OpenInode, open_inode_done); static int open_inode_apply_acl(OpenInode *of) { int r = 0; @@ -472,9 +458,8 @@ static int archive_unpack_special_inode( return log_error_errno(errno, "Failed to fstat() '%s': %m", path); if (((st.st_mode ^ filetype) & S_IFMT) != 0) - return log_error_errno( - SYNTHETIC_ERRNO(ENODEV), - "Special node '%s' we just created is of a wrong type: %m", path); + return log_error_errno(SYNTHETIC_ERRNO(ENODEV), + "Special node '%s' we just created is of a wrong type: %m", path); return TAKE_FD(fd); } @@ -526,11 +511,9 @@ static int archive_entry_read_acl( assert(c > 0); #if HAVE_ACL - r = dlopen_libacl(); - if (r < 0) { - log_debug_errno(r, "Not restoring ACL data on inode as libacl is not available: %m"); + r = dlopen_libacl(LOG_DEBUG); + if (r < 0) return 0; - } _cleanup_(acl_freep) acl_t a = NULL; a = sym_acl_init(c); @@ -552,7 +535,8 @@ static int archive_entry_read_acl( if (r == ARCHIVE_EOF) break; if (r != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unexpected error while iterating through ACLs."); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Unexpected error while iterating through ACLs."); assert(rtype == type); @@ -674,6 +658,8 @@ static int archive_entry_read_stat( int r; assert(entry); + assert(xa); + assert(n_xa); /* Fills in all fields that are present in the archive entry. Doesn't change the fields if the entry * doesn't contain the relevant data */ @@ -781,7 +767,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { ar = sym_archive_read_open_fd(a, input_fd, 64 * 1024); if (ar != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize archive context: %s", sym_archive_error_string(a)); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to initialize archive context: %s", sym_archive_error_string(a)); OpenInode *open_inodes = NULL; @@ -791,7 +778,7 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { return log_oom(); size_t n_open_inodes = 0; - CLEANUP_ARRAY(open_inodes, n_open_inodes, open_inode_done_many); + CLEANUP_ARRAY(open_inodes, n_open_inodes, open_inode_free_array); /* Fill in the root inode. (Note: we leave the .path field as NULL to mark it as root inode.) */ open_inodes[0] = (OpenInode) { @@ -811,14 +798,16 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { if (ar == ARCHIVE_EOF) break; if (!IN_SET(ar, ARCHIVE_OK, ARCHIVE_WARN)) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse archive: %s", sym_archive_error_string(a)); + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse archive: %s", + sym_archive_error_string(a)); const char *p = NULL; r = archive_entry_pathname_safe(entry, &p); if (r < 0) return log_error_errno(r, "Invalid path name in entry, refusing."); if (ar == ARCHIVE_WARN) - log_warning("Non-critical error found while parsing '%s' from the archive, ignoring: %s", p ?: ".", sym_archive_error_string(a)); + log_warning("Non-critical error found while parsing '%s' from the archive, ignoring: %s", + p ?: ".", sym_archive_error_string(a)); if (!p) { /* This is the root inode */ @@ -838,7 +827,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { if (r < 0) return r; if (open_inodes[0].filetype != S_IFDIR) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Archives root inode is not a directory, refusing."); + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Archives root inode is not a directory, refusing."); continue; } @@ -909,7 +899,7 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { acl_t acl_access = NULL, acl_default = NULL; XAttr *xa = NULL; size_t n_xa = 0; - CLEANUP_ARRAY(xa, n_xa, xattr_done_many); + CLEANUP_ARRAY(xa, n_xa, xattr_free_array); if (isempty(rest)) { /* This is the final node in the path, create it */ @@ -930,7 +920,9 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { "Invalid hardlink path name '%s' in entry, refusing.", target); _cleanup_close_ int target_fd = -EBADF; - r = chaseat(tree_fd, target, CHASE_PROHIBIT_SYMLINKS|CHASE_AT_RESOLVE_IN_ROOT|CHASE_NOFOLLOW, /* ret_path= */ NULL, &target_fd); + r = chaseat(tree_fd, tree_fd, target, + CHASE_PROHIBIT_SYMLINKS|CHASE_NOFOLLOW, + /* ret_path= */ NULL, &target_fd); if (r < 0) return log_error_errno( r, @@ -942,9 +934,9 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { /* Refuse hardlinking directories early. */ if (!inode_type_can_hardlink(verify_st.st_mode)) - return log_error_errno( - SYNTHETIC_ERRNO(EBADF), - "Refusing to hardlink inode '%s' of type '%s': %m", target, inode_type_to_string(verify_st.st_mode)); + return log_error_errno(SYNTHETIC_ERRNO(EBADF), + "Refusing to hardlink inode '%s' of type '%s': %m", + target, inode_type_to_string(verify_st.st_mode)); if (linkat(target_fd, "", parent_fd, e, AT_EMPTY_PATH) < 0) { if (errno != ENOENT) @@ -959,16 +951,16 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { _cleanup_close_ int target_parent_fd = -EBADF; _cleanup_free_ char *target_filename = NULL; - r = chaseat(tree_fd, target, CHASE_PROHIBIT_SYMLINKS|CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_EXTRACT_FILENAME|CHASE_NOFOLLOW, &target_filename, &target_parent_fd); + r = chaseat(tree_fd, tree_fd, target, + CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_EXTRACT_FILENAME|CHASE_NOFOLLOW, + &target_filename, &target_parent_fd); if (r < 0) - return log_error_errno( - r, - "Failed to find inode '%s' which shall be hardlinked as '%s': %m", target, j); + return log_error_errno(r, "Failed to find inode '%s' which shall be hardlinked as '%s': %m", + target, j); if (linkat(target_parent_fd, target_filename, parent_fd, e, /* flags= */ 0) < 0) - return log_error_errno( - errno, - "Failed to hardlink inode '%s' as '%s': %m", target, j); + return log_error_errno(errno, "Failed to hardlink inode '%s' as '%s': %m", + target, j); } continue; @@ -1006,7 +998,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { const char *w = startswith(e, ".wh."); if (w) { if (!filename_is_valid(w)) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Invalid whiteout file entry '%s', refusing.", e); + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Invalid whiteout file entry '%s', refusing.", e); r = archive_unpack_whiteout(a, entry, parent_fd, empty_to_root(parent_path), w, j); if (r < 0) @@ -1044,9 +1037,9 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { break; default: - return log_error_errno( - SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Unexpected file type %i of '%s', refusing.", (int) filetype, j); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Unexpected file type %i of '%s', refusing.", + (int) filetype, j); } } else { /* This is some intermediary node in the path that we haven't opened yet. Create it with default attributes */ @@ -1123,6 +1116,36 @@ struct make_archive_data { int have_unique_mount_id; }; +static int filter_item( + int inode_fd, + const struct statx *sx, + const char *path) { + mode_t m; + int r; + + assert(inode_fd >= 0); + assert(sx); + assert(path); + + if (FLAGS_SET(sx->stx_mask, STATX_TYPE)) + m = sx->stx_mode; + else { + struct stat st; + r = RET_NERRNO(fstat(inode_fd, &st)); + if (r < 0) + return log_error_errno(r, "Failed to stat '%s': %m", path); + m = st.st_mode; + } + + /* Filter out sockets, fifos, and weird misc fds such as eventfds() that have no inode type. */ + if (IN_SET(m & S_IFMT, S_IFSOCK, S_IFIFO, 0)) { + log_debug("Skipping '%s' (%s).", path, inode_type_to_string(m) ?: "unknown"); + return false; + } + + return true; +} + static int hardlink_lookup( struct make_archive_data *d, int inode_fd, @@ -1137,6 +1160,7 @@ static int hardlink_lookup( assert(d); assert(inode_fd >= 0); assert(sx); + assert(ret); /* If we know the hardlink count, and it's 1, then don't bother */ if (FLAGS_SET(sx->stx_mask, STATX_NLINK) && sx->stx_nlink == 1) @@ -1380,7 +1404,13 @@ static int archive_item( assert(inode_fd >= 0); assert(sx); - log_debug("Archiving %s\n", path); + r = filter_item(inode_fd, sx, path); + if (r < 0) + return r; + if (r == 0) + return RECURSE_DIR_CONTINUE; + + log_debug("Archiving '%s'...", path); _cleanup_(archive_entry_freep) struct archive_entry *entry = NULL; entry = sym_archive_entry_new(); @@ -1397,7 +1427,8 @@ static int archive_item( sym_archive_entry_set_hardlink(entry, hardlink); if (sym_archive_write_header(d->archive, entry) != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(d->archive)); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to write archive entry header: %s", sym_archive_error_string(d->archive)); return RECURSE_DIR_CONTINUE; } @@ -1447,10 +1478,8 @@ static int archive_item( #if HAVE_ACL if (inode_type_can_acl(sx->stx_mode)) { - r = dlopen_libacl(); - if (r < 0) - log_debug_errno(r, "No trying to read ACL off inode, as libacl support is not available: %m"); - else { + r = dlopen_libacl(LOG_DEBUG); + if (r >= 0) { r = sym_acl_extended_file(FORMAT_PROC_FD_PATH(inode_fd)); if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(errno)) return log_error_errno(errno, "Failed check if '%s' has ACLs: %m", path); @@ -1525,7 +1554,9 @@ static int archive_item( } if (sym_archive_write_header(d->archive, entry) != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(d->archive)); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to write archive entry header: %s", + sym_archive_error_string(d->archive)); if (S_ISREG(sx->stx_mode)) { assert(data_fd >= 0); @@ -1543,7 +1574,9 @@ static int archive_item( la_ssize_t k; k = sym_archive_write_data(d->archive, buffer, l); if (k < 0) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive data: %s", sym_archive_error_string(d->archive)); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to write archive data: %s", + sym_archive_error_string(d->archive)); } } @@ -1574,11 +1607,13 @@ int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) { else r = sym_archive_write_set_format_pax(a); if (r != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output format: %s", sym_archive_error_string(a)); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to set libarchive output format: %s", sym_archive_error_string(a)); r = sym_archive_write_open_fd(a, output_fd); if (r != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output file: %s", sym_archive_error_string(a)); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to set libarchive output file: %s", sym_archive_error_string(a)); _cleanup_(make_archive_data_done) struct make_archive_data data = { .archive = a, @@ -1599,7 +1634,8 @@ int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) { r = sym_archive_write_close(a); if (r != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to finish writing archive: %s", sym_archive_error_string(a)); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Unable to finish writing archive: %s", sym_archive_error_string(a)); return 0; } diff --git a/src/shared/tests.c b/src/shared/tests.c index 55464c3553fbe..8111d481e4d6a 100644 --- a/src/shared/tests.c +++ b/src/shared/tests.c @@ -71,6 +71,8 @@ int get_testdata_dir(const char *suffix, char **ret) { const char *dir; char *p; + assert(ret); + load_testdata_env(); /* if the env var is set, use that */ diff --git a/src/shared/tests.h b/src/shared/tests.h index ae57cab3863c5..9a2b7c0412825 100644 --- a/src/shared/tests.h +++ b/src/shared/tests.h @@ -101,7 +101,6 @@ typedef struct TestFunc { /* See static-destruct.h for an explanation of how this works. */ #define REGISTER_TEST(func, ...) \ - _Pragma("GCC diagnostic ignored \"-Wattributes\"") \ _section_("SYSTEMD_TEST_TABLE") _alignptr_ _used_ _retain_ _variable_no_sanitize_address_ \ static const TestFunc UNIQ_T(static_test_table_entry, UNIQ) = { \ .f = (union f) &(func), \ @@ -110,8 +109,8 @@ typedef struct TestFunc { ##__VA_ARGS__ \ } -extern const TestFunc _weak_ __start_SYSTEMD_TEST_TABLE[]; -extern const TestFunc _weak_ __stop_SYSTEMD_TEST_TABLE[]; +extern const TestFunc __start_SYSTEMD_TEST_TABLE[]; +extern const TestFunc __stop_SYSTEMD_TEST_TABLE[]; #define TEST(name, ...) \ static void test_##name(void); \ @@ -478,6 +477,18 @@ _noreturn_ void log_test_failed_internal(const char *file, int line, const char }) #endif +#ifdef __COVERITY__ +# define ASSERT_PATH_EQ(expr1, expr2) __coverity_check__(path_equal((expr1), (expr2))) +#else +# define ASSERT_PATH_EQ(expr1, expr2) \ + ({ \ + const char *_expr1 = (expr1), *_expr2 = (expr2); \ + if (!path_equal(_expr1, _expr2)) \ + log_test_failed("Expected \"%s == %s\", got \"%s != %s\"", \ + #expr1, #expr2, strnull(_expr1), strnull(_expr2)); \ + }) +#endif + #ifdef __COVERITY__ # define ASSERT_NOT_STREQ(expr1, expr2) __coverity_check__(!streq_ptr((expr1), (expr2))) #else diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index e5694361422fd..9fe3e018693fc 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -3,6 +3,9 @@ #include #include +#include "sd-device.h" +#include "sd-dlopen.h" + #include "alloc-util.h" #include "ansi-color.h" #include "bitfield.h" @@ -10,7 +13,10 @@ #include "chase.h" #include "constants.h" #include "creds-util.h" +#include "crypto-util.h" #include "cryptsetup-util.h" +#include "device-private.h" +#include "device-util.h" #include "dirent-util.h" #include "dlfcn-util.h" #include "efi-api.h" @@ -43,16 +49,14 @@ #include "time-util.h" #include "tpm2-pcr.h" #include "tpm2-util.h" +#include "unaligned.h" #include "virt.h" -#if HAVE_OPENSSL -# include -#endif - #if HAVE_TPM2 static void *libtss2_esys_dl = NULL; static void *libtss2_rc_dl = NULL; static void *libtss2_mu_dl = NULL; +static void *libtss2_tcti_device_dl = NULL; static DLSYM_PROTOTYPE(Esys_Create) = NULL; static DLSYM_PROTOTYPE(Esys_CreateLoaded) = NULL; @@ -121,16 +125,17 @@ static DLSYM_PROTOTYPE(Tss2_MU_UINT32_Marshal) = NULL; static DLSYM_PROTOTYPE(Tss2_RC_Decode) = NULL; -static int dlopen_tpm2_esys(void) { +static int dlopen_tpm2_esys(int log_level) { int r; - ELF_NOTE_DLOPEN("tpm", + SD_ELF_NOTE_DLOPEN( + "tpm", "Support for TPM", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libtss2-esys.so.0"); r = dlopen_many_sym_or_warn( - &libtss2_esys_dl, "libtss2-esys.so.0", LOG_DEBUG, + &libtss2_esys_dl, "libtss2-esys.so.0", log_level, DLSYM_ARG(Esys_Create), DLSYM_ARG(Esys_CreateLoaded), DLSYM_ARG(Esys_CreatePrimary), @@ -186,25 +191,27 @@ static int dlopen_tpm2_esys(void) { return 0; } -static int dlopen_tpm2_rc(void) { - ELF_NOTE_DLOPEN("tpm", +static int dlopen_tpm2_rc(int log_level) { + SD_ELF_NOTE_DLOPEN( + "tpm", "Support for TPM", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libtss2-rc.so.0"); return dlopen_many_sym_or_warn( - &libtss2_rc_dl, "libtss2-rc.so.0", LOG_DEBUG, + &libtss2_rc_dl, "libtss2-rc.so.0", log_level, DLSYM_ARG(Tss2_RC_Decode)); } -static int dlopen_tpm2_mu(void) { - ELF_NOTE_DLOPEN("tpm", +static int dlopen_tpm2_mu(int log_level) { + SD_ELF_NOTE_DLOPEN( + "tpm", "Support for TPM", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libtss2-mu.so.0"); return dlopen_many_sym_or_warn( - &libtss2_mu_dl, "libtss2-mu.so.0", LOG_DEBUG, + &libtss2_mu_dl, "libtss2-mu.so.0", log_level, DLSYM_ARG(Tss2_MU_TPM2_CC_Marshal), DLSYM_ARG(Tss2_MU_TPM2_HANDLE_Marshal), DLSYM_ARG(Tss2_MU_TPM2B_DIGEST_Marshal), @@ -226,27 +233,48 @@ static int dlopen_tpm2_mu(void) { DLSYM_ARG(Tss2_MU_UINT32_Marshal)); } +static int dlopen_tpm2_tcti_device(int log_level) { + /* The "device" TCTI is the most relevant one, let's also load it explicitly on dlopen_tpm2(), even + * if we don't resolve any symbols here. */ + + SD_ELF_NOTE_DLOPEN( + "tpm", + "Support for TPM", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libtss2-tcti-device.so.0"); + + return dlopen_verbose( + &libtss2_tcti_device_dl, + "libtss2-tcti-device.so.0", + log_level); +} + #endif -int dlopen_tpm2(void) { +int dlopen_tpm2(int log_level) { #if HAVE_TPM2 int r; - r = dlopen_tpm2_esys(); + r = dlopen_tpm2_esys(log_level); + if (r < 0) + return r; + + r = dlopen_tpm2_rc(log_level); if (r < 0) return r; - r = dlopen_tpm2_rc(); + r = dlopen_tpm2_mu(log_level); if (r < 0) return r; - r = dlopen_tpm2_mu(); + r = dlopen_tpm2_tcti_device(log_level); if (r < 0) return r; return 0; #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 support is not compiled in."); #endif } @@ -329,6 +357,151 @@ static int tpm2_get_capability( return more == TPM2_YES; } +int tpm2_vendor_info_to_modalias(const Tpm2VendorInfo *info, char **ret) { + _cleanup_free_ char *m = NULL; + + assert(info); + assert(ret); + + /* Closely inspired by kernel modalias strings, this distills information from the TPM vendor data + * into a string suitable for matching hwdb */ + + if (asprintf(&m, + "fi%s:" + "lv%" PRIu32 ":" + "rv%" PRIu32 ".%" PRIu32 ":" + "sy%" PRIu32 ":" + "sd%" PRIu32 ":" + "mf%s:" + "vs%s:" + "ty%" PRIx32 ":" + "fw%" PRIu16 ".%" PRIu16 ".%" PRIu32 ":", + info->family_indicator, + info->level, + info->revision_major, + info->revision_minor, + info->year, + info->day_of_year, + info->manufacturer, + info->vendor_string, + info->vendor_tpm_type, + info->firmware_version_major, + info->firmware_version_minor, + info->firmware_version2) < 0) + return -ENOMEM; + + *ret = TAKE_PTR(m); + return 0; +} + +static char *mangle_vendor_chars(char *c, size_t n) { + char *end = c; + assert(c || n == 0); + + /* Suppress all control characters, whitespace and non-ASCII bytes */ + for (char *x = c; x < c + n; x++) { + if (!IN_SET(*x, ' ', 0)) + end = x + 1; + + if ((unsigned char) *x <= (unsigned char) ' ' || + (unsigned char) *x >= 127) + *x = '_'; + } + + /* Drop trailing spaces and NUL bytes */ + *end = 0; + return c; +} + +int tpm2_get_vendor_info( + Tpm2Context *c, + Tpm2VendorInfo *ret) { + + int r; + + assert(c); + assert(ret); + + TPMU_CAPABILITIES capabilities = {}; + r = tpm2_get_capability( + c, + TPM2_CAP_TPM_PROPERTIES, + TPM2_PT_FAMILY_INDICATOR, + TPM2_PT_FIRMWARE_VERSION_2 - TPM2_PT_FAMILY_INDICATOR + 1, /* get all relevant fields at once */ + &capabilities); + if (r < 0) + return r; + + Tpm2VendorInfo info = {}; + + for (uint32_t i = 0; i < capabilities.tpmProperties.count; i++) { + TPMS_TAGGED_PROPERTY *p = capabilities.tpmProperties.tpmProperty + i; + + switch (p->property) { + + case TPM2_PT_FAMILY_INDICATOR: + unaligned_write_be32(info.family_indicator, p->value); + mangle_vendor_chars(info.family_indicator, sizeof(info.family_indicator)); + break; + + case TPM2_PT_LEVEL: + info.level = p->value; + break; + + case TPM2_PT_REVISION: + info.revision_major = p->value / 100; + info.revision_minor = p->value % 100; + break; + + case TPM2_PT_DAY_OF_YEAR: + info.day_of_year = p->value; + break; + + case TPM2_PT_YEAR: + info.year = p->value; + break; + + case TPM2_PT_MANUFACTURER: + unaligned_write_be32(info.manufacturer, p->value); + mangle_vendor_chars(info.manufacturer, sizeof(info.manufacturer)); + break; + + case TPM2_PT_VENDOR_STRING_1: + unaligned_write_be32(info.vendor_string+0, p->value); + break; + + case TPM2_PT_VENDOR_STRING_2: + unaligned_write_be32(info.vendor_string+4, p->value); + break; + + case TPM2_PT_VENDOR_STRING_3: + unaligned_write_be32(info.vendor_string+8, p->value); + break; + + case TPM2_PT_VENDOR_STRING_4: + unaligned_write_be32(info.vendor_string+12, p->value); + break; + + case TPM2_PT_VENDOR_TPM_TYPE: + info.vendor_tpm_type = p->value; + break; + + case TPM2_PT_FIRMWARE_VERSION_1: + info.firmware_version_major = p->value >> 16; + info.firmware_version_minor = p->value & 0xFFFFU; + break; + + case TPM2_PT_FIRMWARE_VERSION_2: + info.firmware_version2 = p->value; + break; + } + } + + mangle_vendor_chars(info.vendor_string, sizeof(info.vendor_string)); + *ret = TAKE_STRUCT(info); + return 0; +} + #define TPMA_CC_TO_TPM2_CC(cca) (((cca) & TPMA_CC_COMMANDINDEX_MASK) >> TPMA_CC_COMMANDINDEX_SHIFT) static int tpm2_cache_capabilities(Tpm2Context *c) { @@ -671,6 +844,9 @@ static Tpm2Context *tpm2_context_free(Tpm2Context *c) { c->capability_commands = mfree(c->capability_commands); c->capability_ecc_curves = mfree(c->capability_ecc_curves); + c->tcti_driver = mfree(c->tcti_driver); + c->tcti_param = mfree(c->tcti_param); + return mfree(c); } @@ -697,9 +873,9 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { .n_ref = 1, }; - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; if (!device) { device = secure_getenv("SYSTEMD_TPM2_DEVICE"); @@ -716,7 +892,8 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { } if (device) { - const char *param, *driver, *fn; + _cleanup_free_ char *_driver = NULL; + const char *param, *driver; const TSS2_TCTI_INFO* info; TSS2_TCTI_INFO_FUNC func; size_t sz = 0; @@ -724,7 +901,9 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { param = strchr(device, ':'); if (param) { /* Syntax #1: Pair of driver string and arbitrary parameter */ - driver = strndupa_safe(device, param - device); + driver = _driver = strndup(device, param - device); + if (!driver) + return log_oom_debug(); if (isempty(driver)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 driver name is empty, refusing."); @@ -738,7 +917,9 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { log_debug("Using TPM2 TCTI driver '%s' with device '%s'.", driver, param); - fn = strjoina("libtss2-tcti-", driver, ".so.0"); + _cleanup_free_ char *fn = strjoin("libtss2-tcti-", driver, ".so.0"); + if (!fn) + return log_oom_debug(); /* Better safe than sorry, let's refuse strings that cannot possibly be valid driver early, before going to disk. */ if (!filename_is_valid(fn)) @@ -778,6 +959,14 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { if (rc != TPM2_RC_SUCCESS) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize TCTI context: %s", sym_Tss2_RC_Decode(rc)); + + context->tcti_driver = strdup(driver); + if (!context->tcti_driver) + return log_oom_debug(); + + context->tcti_param = strdup(param); + if (!context->tcti_param) + return log_oom_debug(); } rc = sym_Esys_Initialize(&context->esys_context, context->tcti_context, NULL); @@ -2317,7 +2506,7 @@ static int tpm2_load_external( } static int tpm2_marshal_private(const TPM2B_PRIVATE *private, void **ret, size_t *ret_size) { - size_t max_size = sizeof(*private), blob_size = 0; + size_t max_size = SIZEOF(*private), blob_size = 0; _cleanup_free_ void *blob = NULL; TSS2_RC rc; @@ -2360,7 +2549,7 @@ static int tpm2_unmarshal_private(const void *data, size_t size, TPM2B_PRIVATE * } int tpm2_marshal_public(const TPM2B_PUBLIC *public, void **ret, size_t *ret_size) { - size_t max_size = sizeof(*public), blob_size = 0; + size_t max_size = SIZEOF(*public), blob_size = 0; _cleanup_free_ void *blob = NULL; TSS2_RC rc; @@ -2403,7 +2592,7 @@ static int tpm2_unmarshal_public(const void *data, size_t size, TPM2B_PUBLIC *re } int tpm2_marshal_nv_public(const TPM2B_NV_PUBLIC *nv_public, void **ret, size_t *ret_size) { - size_t max_size = sizeof(*nv_public), blob_size = 0; + size_t max_size = SIZEOF(*nv_public), blob_size = 0; _cleanup_free_ void *blob = NULL; TSS2_RC rc; @@ -2702,13 +2891,15 @@ int tpm2_get_best_pcr_bank( uint32_t efi_banks; r = efi_get_active_pcr_banks(&efi_banks); if (r < 0) { - if (r != -ENOENT) + if (!IN_SET(r, -ENOENT, -EOPNOTSUPP)) return r; /* If variable is not set use guesswork below */ - log_debug("Boot loader didn't set the LoaderTpm2ActivePcrBanks EFI variable, we have to guess the used PCR banks."); + log_debug("Boot loader didn't set the LoaderTpm2ActivePcrBanks EFI variable or EFI support is unavailable, we have to guess the used PCR banks."); } else if (efi_banks == UINT32_MAX) log_debug("Boot loader set the LoaderTpm2ActivePcrBanks EFI variable to indicate that the GetActivePcrBanks() API is not available in the firmware. We have to guess the used PCR banks."); + else if (efi_banks == 0) + log_debug("Boot loader set the LoaderTpm2ActivePcrBanks EFI variable to zero to indicate that TPM support is not available in the firmware. We'll have to guess the used PCR banks."); else { if (BIT_SET(efi_banks, TPM2_ALG_SHA256)) *ret = TPM2_ALG_SHA256; @@ -2811,13 +3002,15 @@ int tpm2_get_good_pcr_banks( uint32_t efi_banks; r = efi_get_active_pcr_banks(&efi_banks); if (r < 0) { - if (r != -ENOENT) + if (!IN_SET(r, -ENOENT, -EOPNOTSUPP)) return r; /* If the variable is not set we have to guess via the code below */ - log_debug("Boot loader didn't set the LoaderTpm2ActivePcrBanks EFI variable, we have to guess the used PCR banks."); + log_debug("Boot loader didn't set the LoaderTpm2ActivePcrBanks EFI variable or EFI support is unavailable, we have to guess the used PCR banks."); } else if (efi_banks == UINT32_MAX) log_debug("Boot loader set the LoaderTpm2ActivePcrBanks EFI variable to indicate that the GetActivePcrBanks() API is not available in the firmware. We have to guess the used PCR banks."); + else if (efi_banks == 0) + log_debug("Boot loader set the LoaderTpm2ActivePcrBanks EFI variable to zero to indicate that TPM support is not available in the firmware. We'll have to guess the used PCR banks."); else { FOREACH_ARRAY(hash, tpm2_hash_algorithms, TPM2_N_HASH_ALGORITHMS) { if (!BIT_SET(efi_banks, *hash)) @@ -2891,11 +3084,15 @@ int tpm2_get_good_pcr_banks_strv( #if HAVE_OPENSSL _cleanup_free_ TPMI_ALG_HASH *algs = NULL; _cleanup_strv_free_ char **l = NULL; - int n_algs; + int n_algs, r; assert(c); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + n_algs = tpm2_get_good_pcr_banks(c, pcr_mask, &algs); if (n_algs < 0) return n_algs; @@ -2909,11 +3106,11 @@ int tpm2_get_good_pcr_banks_strv( if (!salg) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unknown PCR algorithm, can't measure."); - implementation = EVP_get_digestbyname(salg); + implementation = sym_EVP_get_digestbyname(salg); if (!implementation) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unsupported PCR algorithm, can't measure."); - n = strdup(ASSERT_PTR(EVP_MD_name(implementation))); + n = strdup(ASSERT_PTR(sym_EVP_MD_get0_name(implementation))); if (!n) return log_oom_debug(); @@ -3330,9 +3527,9 @@ int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name) assert(public); assert(ret_name); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; if (public->nameAlg != TPM2_ALG_SHA256) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), @@ -3414,9 +3611,9 @@ int tpm2_calculate_nv_index_name(const TPMS_NV_PUBLIC *nvpublic, TPM2B_NAME *ret assert(nvpublic); assert(ret_name); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; if (nvpublic->nameAlg != TPM2_ALG_SHA256) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), @@ -3470,9 +3667,9 @@ int tpm2_calculate_policy_auth_value(TPM2B_DIGEST *digest) { assert(digest); assert(digest->size == SHA256_DIGEST_SIZE); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; uint8_t buf[sizeof(command)]; size_t offset = 0; @@ -3531,9 +3728,9 @@ int tpm2_calculate_policy_signed(TPM2B_DIGEST *digest, const TPM2B_NAME *name) { assert(digest->size == SHA256_DIGEST_SIZE); assert(name); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; uint8_t buf[sizeof(command)]; size_t offset = 0; @@ -3587,6 +3784,10 @@ int tpm2_policy_signed_hmac_sha256( * specified in the hmac_key parameter. The secret key must be loaded into the TPM already and * referenced in hmac_key_handle. */ + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + log_debug("Submitting PolicySigned policy for HMAC-SHA256."); /* Acquire the nonce from the TPM that we shall sign */ @@ -3622,7 +3823,7 @@ int tpm2_policy_signed_hmac_sha256( unsigned hmac_signature_size = sizeof(hmac_signature); /* And sign this with our key */ - if (!HMAC(EVP_sha256(), + if (!sym_HMAC(sym_EVP_sha256(), hmac_key->iov_base, hmac_key->iov_len, digest_to_sign.buffer, @@ -3676,9 +3877,9 @@ int tpm2_calculate_policy_authorize_nv( assert(digest); assert(digest->size == SHA256_DIGEST_SIZE); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; uint8_t buf[sizeof(command)]; size_t offset = 0; @@ -3811,9 +4012,9 @@ int tpm2_calculate_policy_or(const TPM2B_DIGEST *branches, size_t n_branches, TP if (n_branches > 8) return -E2BIG; - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_ERR); if (r < 0) - return log_error_errno(r, "TPM2 support not installed: %m"); + return r; uint8_t buf[sizeof(command)]; size_t offset = 0; @@ -3866,9 +4067,9 @@ int tpm2_calculate_policy_pcr( assert(digest); assert(digest->size == SHA256_DIGEST_SIZE); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; TPML_PCR_SELECTION pcr_selection; _cleanup_free_ TPM2B_DIGEST *values = NULL; @@ -3958,9 +4159,9 @@ int tpm2_calculate_policy_authorize( assert(digest); assert(digest->size == SHA256_DIGEST_SIZE); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; uint8_t buf[sizeof(command)]; size_t offset = 0; @@ -4359,6 +4560,10 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) assert(pkey); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + TPMT_PUBLIC public = { .nameAlg = TPM2_ALG_SHA256, .objectAttributes = TPMA_OBJECT_DECRYPT | TPMA_OBJECT_SIGN_ENCRYPT | TPMA_OBJECT_USERWITHAUTH, @@ -4368,7 +4573,7 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) }, }; - int key_id = EVP_PKEY_get_id(pkey); + int key_id = sym_EVP_PKEY_get_id(pkey); switch (key_id) { case EVP_PKEY_EC: { public.type = TPM2_ALG_ECC; @@ -4466,8 +4671,12 @@ int tpm2_tpm2b_public_to_fingerprint( if (r < 0) return r; + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + /* Hardcode fingerprint to SHA256 */ - return pubkey_fingerprint(pkey, EVP_sha256(), ret_fingerprint, ret_fingerprint_size); + return pubkey_fingerprint(pkey, sym_EVP_sha256(), ret_fingerprint, ret_fingerprint_size); #else return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); #endif @@ -4734,7 +4943,7 @@ static int tpm2_kdfa( if (!hash_alg_name) return -EOPNOTSUPP; - _cleanup_free_ void *buf = NULL; + _cleanup_(erase_and_freep) void *buf = NULL; r = kdf_kb_hmac_derive( "COUNTER", hash_alg_name, @@ -4812,7 +5021,7 @@ static int tpm2_kdfe( /* assert we copied exactly the right amount that we allocated */ assert(end > info && (uintptr_t) end - (uintptr_t) info == info_len); - _cleanup_free_ void *buf = NULL; + _cleanup_(erase_and_freep) void *buf = NULL; r = kdf_ss_derive( hash_alg_name, shared_secret, @@ -4844,6 +5053,8 @@ static int tpm2_calculate_seal_public( int r; assert(parent); + POINTER_MAY_BE_NULL(attributes); + POINTER_MAY_BE_NULL(policy); assert(seed); assert(secret); assert(ret); @@ -4899,7 +5110,7 @@ static int tpm2_calculate_seal_private( log_debug("Calculating private part of sealed object."); - _cleanup_free_ void *storage_key = NULL; + _cleanup_(erase_and_freep) void *storage_key = NULL; size_t storage_key_size; r = tpm2_kdfa(parent->publicArea.nameAlg, seed->buffer, @@ -4919,7 +5130,7 @@ static int tpm2_calculate_seal_private( size_t bits = (size_t) r * 8; - _cleanup_free_ void *integrity_key = NULL; + _cleanup_(erase_and_freep) void *integrity_key = NULL; size_t integrity_key_size; r = tpm2_kdfa(parent->publicArea.nameAlg, seed->buffer, @@ -4934,6 +5145,7 @@ static int tpm2_calculate_seal_private( return log_debug_errno(r, "Could not calculate integrity key KDFa: %m"); TPM2B_AUTH auth = {}; + CLEANUP_ERASE(auth); if (pin) { r = tpm2_auth_value_from_pin(parent->publicArea.nameAlg, pin, &auth); if (r < 0) @@ -4949,8 +5161,9 @@ static int tpm2_calculate_seal_private( .sensitive.bits = TPM2B_SENSITIVE_DATA_MAKE(secret, secret_size), }, }; + CLEANUP_ERASE(sensitive); - _cleanup_free_ void *marshalled_sensitive = malloc(sizeof(sensitive)); + _cleanup_(erase_and_freep) void *marshalled_sensitive = malloc(sizeof(sensitive)); if (!marshalled_sensitive) return log_oom_debug(); @@ -5057,7 +5270,7 @@ static int tpm2_calculate_seal_rsa_seed( size_t seed_size = (size_t) r; - _cleanup_free_ void *seed = malloc(seed_size); + _cleanup_(erase_and_freep) void *seed = malloc(seed_size); if (!seed) return log_oom_debug(); @@ -5127,7 +5340,7 @@ static int tpm2_calculate_seal_ecc_seed( if (r < 0) return r; - _cleanup_free_ void *shared_secret = NULL; + _cleanup_(erase_and_freep) void *shared_secret = NULL; size_t shared_secret_size; r = ecc_ecdh(pkey, parent_pkey, &shared_secret, &shared_secret_size); if (r < 0) @@ -5168,7 +5381,7 @@ static int tpm2_calculate_seal_ecc_seed( size_t bits = (size_t) r * 8; - _cleanup_free_ void *seed = NULL; + _cleanup_(erase_and_freep) void *seed = NULL; size_t seed_size = 0; /* Explicit initialization to appease gcc */ r = tpm2_kdfe(parent->publicArea.nameAlg, shared_secret, @@ -5205,7 +5418,8 @@ static int tpm2_calculate_seal_seed( log_debug("Calculating encrypted seed for sealed object."); - _cleanup_free_ void *seed = NULL, *encrypted_seed = NULL; + _cleanup_(erase_and_freep) void *seed = NULL; + _cleanup_free_ void *encrypted_seed = NULL; size_t seed_size = 0, encrypted_seed_size = 0; /* Explicit initialization to appease gcc */ if (parent->publicArea.type == TPM2_ALG_RSA) r = tpm2_calculate_seal_rsa_seed(parent, &seed, &seed_size, &encrypted_seed, &encrypted_seed_size); @@ -5976,7 +6190,8 @@ int tpm2_undefine_nv_index( return 0; } -int tpm2_define_nvpcr_nv_index( +#if HAVE_OPENSSL +static int tpm2_define_nvpcr_nv_index( Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, @@ -6095,7 +6310,7 @@ int tpm2_define_nvpcr_nv_index( return 1; } -int tpm2_extend_nvpcr_nv_index( +static int tpm2_extend_nvpcr_nv_index( Tpm2Context *c, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, @@ -6136,6 +6351,7 @@ int tpm2_extend_nvpcr_nv_index( return 0; } +#endif int tpm2_read_nv_index( Tpm2Context *c, @@ -6211,6 +6427,7 @@ int tpm2_seal_data( assert(c); assert(data); assert(primary_handle); + POINTER_MAY_BE_NULL(policy); /* This is a generic version of tpm2_seal(), that doesn't imply any policy or any specific * combination of the two keypairs in their marshalling. tpm2_seal() is somewhat specific to the FDE @@ -6276,6 +6493,7 @@ int tpm2_unseal_data( assert(public_blob); assert(private_blob); assert(primary_handle); + assert(ret_data); TPM2B_PUBLIC public; r = tpm2_unmarshal_public(public_blob->iov_base, public_blob->iov_len, &public); @@ -6325,9 +6543,9 @@ int tpm2_list_devices(bool legend, bool quiet) { _cleanup_closedir_ DIR *d = NULL; int r; - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_ERR); if (r < 0) - return log_error_errno(r, "TPM2 support is not installed."); + return r; t = table_new("path", "device", "driver"); if (!t) @@ -6385,11 +6603,7 @@ int tpm2_list_devices(bool legend, bool quiet) { return 0; } - r = table_print(t, stdout); - if (r < 0) - return log_error_errno(r, "Failed to show device table: %m"); - - return 0; + return table_print_or_warn(t); #else return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 not supported on this build."); @@ -6401,9 +6615,9 @@ int tpm2_find_device_auto(char **ret) { _cleanup_closedir_ DIR *d = NULL; int r; - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support is not installed."); + return r; d = opendir("/sys/class/tpmrm"); if (!d) { @@ -6483,6 +6697,8 @@ static const char* tpm2_userspace_event_type_table[_TPM2_USERSPACE_EVENT_TYPE_MA [TPM2_EVENT_NVPCR_INIT] = "nvpcr-init", [TPM2_EVENT_NVPCR_SEPARATOR] = "nvpcr-separator", [TPM2_EVENT_DM_VERITY] = "dm-verity", + [TPM2_EVENT_IMDS_USERDATA] = "imds-userdata", + [TPM2_EVENT_OS_SEPARATOR] = "os-separator", }; DEFINE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType); @@ -6609,17 +6825,21 @@ static int tpm2_userspace_log( if (fd < 0) /* Apparently tpm2_local_log_open() failed earlier, let's not complain again */ return 0; + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + for (size_t i = 0; i < values->count; i++) { const EVP_MD *implementation; const char *a; assert_se(a = tpm2_hash_alg_to_string(values->digests[i].hashAlg)); - assert_se(implementation = EVP_get_digestbyname(a)); + assert_se(implementation = sym_EVP_get_digestbyname(a)); r = sd_json_variant_append_arraybo( &array, SD_JSON_BUILD_PAIR_STRING("hashAlg", a), - SD_JSON_BUILD_PAIR_HEX("digest", &values->digests[i].digest, EVP_MD_size(implementation))); + SD_JSON_BUILD_PAIR_HEX("digest", &values->digests[i].digest, sym_EVP_MD_get_size(implementation))); if (r < 0) return log_debug_errno(r, "Failed to append digest object to JSON array: %m"); } @@ -6677,6 +6897,7 @@ int tpm2_pcr_extend_bytes( _cleanup_close_ int log_fd = -EBADF; TPML_DIGEST_VALUES values = {}; TSS2_RC rc; + int r; assert(c); assert(iovec_is_valid(data)); @@ -6691,19 +6912,23 @@ int tpm2_pcr_extend_bytes( if (strv_isempty(banks)) return 0; + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + STRV_FOREACH(bank, banks) { const EVP_MD *implementation; int id; - assert_se(implementation = EVP_get_digestbyname(*bank)); + assert_se(implementation = sym_EVP_get_digestbyname(*bank)); if (values.count >= ELEMENTSOF(values.digests)) return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Too many banks selected."); - if ((size_t) EVP_MD_size(implementation) > sizeof(values.digests[values.count].digest)) + if ((size_t) sym_EVP_MD_get_size(implementation) > sizeof(values.digests[values.count].digest)) return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Hash result too large for TPM2."); - id = tpm2_hash_alg_from_string(EVP_MD_name(implementation)); + id = tpm2_hash_alg_from_string(sym_EVP_MD_get0_name(implementation)); if (id < 0) return log_debug_errno(id, "Can't map hash name to TPM2."); @@ -6716,9 +6941,9 @@ int tpm2_pcr_extend_bytes( * some unrelated purpose, who knows). Hence we instead measure an HMAC signature of a * private non-secret string instead. */ if (iovec_is_set(secret) > 0) { - if (!HMAC(implementation, secret->iov_base, secret->iov_len, data->iov_base, data->iov_len, (unsigned char*) &values.digests[values.count].digest, NULL)) + if (!sym_HMAC(implementation, secret->iov_base, secret->iov_len, data->iov_base, data->iov_len, (unsigned char*) &values.digests[values.count].digest, NULL)) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure."); - } else if (EVP_Digest(data->iov_base, data->iov_len, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1) + } else if (sym_EVP_Digest(data->iov_base, data->iov_len, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data to measure."); values.count++; @@ -6851,6 +7076,10 @@ int tpm2_nvpcr_extend_bytes( assert(iovec_is_valid(data)); assert(iovec_is_valid(secret)); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + _cleanup_(nvpcr_data_done) NvPCRData p = {}; r = nvpcr_data_load(name, &p); if (r < 0) @@ -6874,10 +7103,10 @@ int tpm2_nvpcr_extend_bytes( return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported algorithm for NvPCR, refusing."); const EVP_MD *implementation; - assert_se(implementation = EVP_get_digestbyname(an)); + assert_se(implementation = sym_EVP_get_digestbyname(an)); _cleanup_(iovec_done) struct iovec digest = { - .iov_len = EVP_MD_size(implementation), + .iov_len = sym_EVP_MD_get_size(implementation), }; digest.iov_base = malloc(digest.iov_len); @@ -6888,9 +7117,9 @@ int tpm2_nvpcr_extend_bytes( data = &iovec_empty; if (iovec_is_set(secret)) { - if (!HMAC(implementation, secret->iov_base, secret->iov_len, data->iov_base, data->iov_len, digest.iov_base, NULL)) + if (!sym_HMAC(implementation, secret->iov_base, secret->iov_len, data->iov_base, data->iov_len, digest.iov_base, NULL)) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure."); - } else if (EVP_Digest(data->iov_base, data->iov_len, digest.iov_base, NULL, implementation, NULL) != 1) + } else if (sym_EVP_Digest(data->iov_base, data->iov_len, digest.iov_base, NULL, implementation, NULL) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data to measure."); _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; @@ -6975,7 +7204,7 @@ static int tpm2_nvpcr_write_anchor_secret( if (r < 0) { if (r != -ENOENT) return log_error_errno(r, "Failed to read '%s' file: %m", joined); - } else if (iovec_memcmp(&existing, credential) == 0) { + } else if (iovec_equal(&existing, credential)) { log_debug("Anchor secret file '%s' already matches expectations, not updating.", joined); return 0; } else @@ -7307,6 +7536,44 @@ int tpm2_nvpcr_acquire_anchor_secret(struct iovec *ret, bool sync_secondary) { #endif } +#if HAVE_OPENSSL +static int tpm2_context_can_nvindex(Tpm2Context *c) { + int r; + + assert(c); + + if (!streq_ptr(c->tcti_driver, "device")) { + log_debug("Not checking udev database, because not using 'device' TCTI."); + return 1; + } + + if (!c->tcti_param) { + log_debug("No device specified, not checking udev database."); + return 1; + } + + _cleanup_(sd_device_unrefp) sd_device *d = NULL; + r = sd_device_new_from_devname(&d, c->tcti_param); + if (r < 0) + return log_debug_errno(r, "Failed to acquire udev entry for device '%s': %m", c->tcti_param); + + r = device_get_property_bool(d, "TPM2_BROKEN_NVPCR"); + if (r == -ENOENT) { + log_device_debug_errno(d, r, "No TPM2_BROKEN_NVPCR property for '%s', assuming NvPCRs work.", c->tcti_param); + return 1; + } + if (r < 0) + return log_device_debug_errno(d, r, "Failed to query TPM2_BROKEN_NVPCR for '%s': %m", c->tcti_param); + if (r > 0) { + log_device_debug(d, "TPM2_BROKEN_NVPCR property for '%s' explicitly set, NvPCRs do not work.", c->tcti_param); + return 0; + } + + log_device_debug(d, "TPM2_BROKEN_NVPCR property for '%s' explicitly set to false, hence NvPCRs work.", c->tcti_param); + return 1; +} +#endif + int tpm2_nvpcr_initialize( Tpm2Context *c, const Tpm2Handle *session, @@ -7320,6 +7587,12 @@ int tpm2_nvpcr_initialize( assert(c); assert(name); + r = tpm2_context_can_nvindex(c); + if (r < 0) + return r; + if (r == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 device does not support NvPCRs, not initializing."); + _cleanup_(nvpcr_data_done) NvPCRData p = {}; r = nvpcr_data_load(name, &p); if (r < 0) @@ -7348,10 +7621,14 @@ int tpm2_nvpcr_initialize( if (!an) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported algorithm for NvPCR, refusing."); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + const EVP_MD *implementation; - assert_se(implementation = EVP_get_digestbyname(an)); + assert_se(implementation = sym_EVP_get_digestbyname(an)); - int digest_size = EVP_MD_get_size(implementation); + int digest_size = sym_EVP_MD_get_size(implementation); assert_se(digest_size > 0); if ((size_t) digest_size > sizeof_field(TPM2B_MAX_NV_BUFFER, buffer)) @@ -7372,7 +7649,7 @@ int tpm2_nvpcr_initialize( CLEANUP_ERASE(buf); /* We measure HMAC(anchor_secret, name) into the NvPCR to anchor it on our secret. */ - if (!HMAC(implementation, anchor_secret->iov_base, anchor_secret->iov_len, hmac_buffer, hmac_buffer_size, buf.buffer, NULL)) + if (!sym_HMAC(implementation, anchor_secret->iov_base, anchor_secret->iov_len, hmac_buffer, hmac_buffer_size, buf.buffer, NULL)) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure."); _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; @@ -8077,6 +8354,8 @@ int tpm2_pcrlock_policy_load( _cleanup_fclose_ FILE *f = NULL; int r; + assert(ret_policy); + r = tpm2_pcrlock_search_file(path, &f, &discovered_path); if (r == -ENOENT) { *ret_policy = (Tpm2PCRLockPolicy) {}; @@ -8213,8 +8492,8 @@ int tpm2_pcrlock_policy_from_credentials( continue; } - if ((!srk || iovec_memcmp(srk, &loaded_policy.srk_handle) == 0) && - (!nv || iovec_memcmp(nv, &loaded_policy.nv_handle) == 0)) { + if ((!srk || iovec_equal(srk, &loaded_policy.srk_handle)) && + (!nv || iovec_equal(nv, &loaded_policy.nv_handle))) { *ret = TAKE_STRUCT(loaded_policy); return 1; } @@ -8235,9 +8514,9 @@ int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret) { assert(path); assert(ret); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; r = read_full_file(path, &device_key_buffer, &device_key_buffer_size); if (r < 0) @@ -8898,15 +9177,15 @@ Tpm2Support tpm2_support_full(Tpm2Support mask) { support |= TPM2_SUPPORT_SYSTEM; if ((mask & (TPM2_SUPPORT_LIBRARIES|TPM2_SUPPORT_LIBTSS2_ALL)) != 0) { - r = dlopen_tpm2_esys(); + r = dlopen_tpm2_esys(LOG_DEBUG); if (r >= 0) support |= TPM2_SUPPORT_LIBTSS2_ESYS; - r = dlopen_tpm2_rc(); + r = dlopen_tpm2_rc(LOG_DEBUG); if (r >= 0) support |= TPM2_SUPPORT_LIBTSS2_RC; - r = dlopen_tpm2_mu(); + r = dlopen_tpm2_mu(LOG_DEBUG); if (r >= 0) support |= TPM2_SUPPORT_LIBTSS2_MU; @@ -9242,7 +9521,6 @@ DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_FALLBACK(tpm2_pcr_index, int, TPM2_P DEFINE_STRING_TABLE_LOOKUP_TO_STRING(tpm2_pcr_index, int); bool tpm2_nvpcr_name_is_valid(const char *name) { - return filename_is_valid(name) && - string_is_safe(name) && + return string_is_safe(name, STRING_FILENAME) && tpm2_pcr_index_from_string(name) < 0; /* don't allow nvpcrs to be name like pcrs */ } diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 68289bec48a1c..b5a8ec1c1eaf7 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -2,8 +2,9 @@ #pragma once #include "bitfield.h" -#include "openssl-util.h" +#include "iovec-util.h" #include "shared-forward.h" +#include "sha256-fundamental.h" typedef enum TPM2Flags { TPM2_FLAGS_USE_PIN = 1 << 0, @@ -39,7 +40,7 @@ static inline bool TPM2_PCR_MASK_VALID(uint32_t pcr_mask) { #define TPM2_N_HASH_ALGORITHMS 4U -int dlopen_tpm2(void); +int dlopen_tpm2(int log_level); #if HAVE_TPM2 @@ -53,6 +54,8 @@ typedef struct Tpm2Context { void *tcti_dl; TSS2_TCTI_CONTEXT *tcti_context; ESYS_CONTEXT *esys_context; + char *tcti_driver; + char *tcti_param; /* Some selected cached capabilities of the TPM */ TPMS_ALG_PROPERTY *capability_algorithms; @@ -146,6 +149,8 @@ typedef enum Tpm2UserspaceEventType { TPM2_EVENT_NVPCR_INIT, TPM2_EVENT_NVPCR_SEPARATOR, TPM2_EVENT_DM_VERITY, + TPM2_EVENT_IMDS_USERDATA, + TPM2_EVENT_OS_SEPARATOR, _TPM2_USERSPACE_EVENT_TYPE_MAX, _TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL, } Tpm2UserspaceEventType; @@ -195,6 +200,24 @@ static inline int tpm2_digest_init(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { return tpm2_digest_many(alg, digest, NULL, 0, false); } +typedef struct Tpm2VendorInfo { + uint32_t level; + uint32_t revision_major; + uint32_t revision_minor; + uint32_t day_of_year; + uint32_t year; + uint32_t vendor_tpm_type; + uint16_t firmware_version_major; + uint16_t firmware_version_minor; + uint32_t firmware_version2; + char family_indicator[4+1]; + char manufacturer[4+1]; + char vendor_string[4*4+1]; +} Tpm2VendorInfo; + +int tpm2_vendor_info_to_modalias(const Tpm2VendorInfo *info, char **ret); +int tpm2_get_vendor_info(Tpm2Context *c, Tpm2VendorInfo *ret); + void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const char *msg); void tpm2_log_debug_pcr_value(const Tpm2PCRValue *pcr_value, const char *msg); void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg); @@ -304,8 +327,6 @@ int tpm2_tpm2b_public_to_fingerprint(const TPM2B_PUBLIC *public, void **ret_fing int tpm2_define_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public); int tpm2_write_policy_nv_index(Tpm2Context *c, const Tpm2Handle *policy_session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, const TPM2B_DIGEST *policy_digest); -int tpm2_define_nvpcr_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, TPMI_ALG_HASH algorithm, Tpm2Handle **ret_nv_handle); -int tpm2_extend_nvpcr_nv_index(Tpm2Context *c, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, const struct iovec *digest); int tpm2_undefine_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle); int tpm2_read_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, struct iovec *ret_value); @@ -477,6 +498,7 @@ typedef enum Tpm2Support { /* Combined flags for generic (i.e. not tool-specific) support */ TPM2_SUPPORT_FULL = TPM2_SUPPORT_API|TPM2_SUPPORT_LIBTSS2_ALL, + TPM2_SUPPORT_SOFTWARE = TPM2_SUPPORT_FULL & ~TPM2_SUPPORT_FIRMWARE, /* Same, just without PC firmware support */ } Tpm2Support; Tpm2Support tpm2_support_full(Tpm2Support mask); @@ -486,6 +508,9 @@ static inline Tpm2Support tpm2_support(void) { static inline bool tpm2_is_fully_supported(void) { return tpm2_support() == TPM2_SUPPORT_FULL; } +static inline bool tpm2_is_mostly_supported(void) { + return (tpm2_support() & TPM2_SUPPORT_SOFTWARE) == TPM2_SUPPORT_SOFTWARE; +} int verb_has_tpm2_generic(bool quiet); diff --git a/src/shared/unit-file.c b/src/shared/unit-file.c index f056b679a9ce4..f0babafa5e24f 100644 --- a/src/shared/unit-file.c +++ b/src/shared/unit-file.c @@ -752,6 +752,8 @@ int unit_file_find_fragment( _cleanup_set_free_ Set *names = NULL; int r; + assert(ret_fragment_path); + /* Finds a fragment path, and returns the set of names: * if we have …/foo.service and …/foo-alias.service→foo.service, * and …/foo@.service and …/foo-alias@.service→foo@.service, diff --git a/src/shared/user-record-show.c b/src/shared/user-record-show.c index a43328c9fe02b..11edf2557c3b5 100644 --- a/src/shared/user-record-show.c +++ b/src/shared/user-record-show.c @@ -342,6 +342,8 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { printf(" Email: %s\n", hr->email_address); if (hr->location) printf(" Location: %s\n", hr->location); + if (BIRTH_DATE_IS_SET(hr->birth_date)) + printf(" Birth Date: %04d-%02d-%02d\n", hr->birth_date.tm_year + 1900, hr->birth_date.tm_mon + 1, hr->birth_date.tm_mday); if (hr->password_hint) printf(" Passw. Hint: %s\n", hr->password_hint); if (hr->icon_name) diff --git a/src/shared/user-record.c b/src/shared/user-record.c index e237f6e6ca2db..4dfb2c72d70f0 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -46,6 +46,7 @@ UserRecord* user_record_new(void) { .nice_level = INT_MAX, .not_before_usec = UINT64_MAX, .not_after_usec = UINT64_MAX, + .birth_date = BIRTH_DATE_UNSET, .locked = -1, .storage = _USER_STORAGE_INVALID, .access_mode = MODE_INVALID, @@ -211,12 +212,15 @@ static UserRecord* user_record_free(UserRecord *h) { for (size_t i = 0; i < h->n_fido2_hmac_credential; i++) fido2_hmac_credential_done(h->fido2_hmac_credential + i); + free(h->fido2_hmac_credential); for (size_t i = 0; i < h->n_fido2_hmac_salt; i++) fido2_hmac_salt_done(h->fido2_hmac_salt + i); + free(h->fido2_hmac_salt); strv_free(h->recovery_key_type); for (size_t i = 0; i < h->n_recovery_key; i++) recovery_key_done(h->recovery_key + i); + free(h->recovery_key); strv_free(h->self_modifiable_fields); strv_free(h->self_modifiable_blobs); @@ -414,6 +418,28 @@ static int json_dispatch_filename_or_path(const char *name, sd_json_variant *var return 0; } +static int json_dispatch_birth_date(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + struct tm *ret = ASSERT_PTR(userdata); + const char *s; + int r; + + if (sd_json_variant_is_null(variant)) { + *ret = BIRTH_DATE_UNSET; + return 0; + } + + if (!sd_json_variant_is_string(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); + + s = sd_json_variant_string(variant); + + r = parse_birth_date(s, ret); + if (r < 0) + return json_log(variant, flags, r, "JSON field '%s' is not a valid ISO 8601 date (expected YYYY-MM-DD).", strna(name)); + + return 0; +} + static int json_dispatch_home_directory(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { char **s = userdata; const char *n; @@ -554,11 +580,11 @@ static int json_dispatch_weight(const char *name, sd_json_variant *variant, sd_j return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name)); k = sd_json_variant_unsigned(variant); - if (k <= CGROUP_WEIGHT_MIN || k >= CGROUP_WEIGHT_MAX) + if (k < CGROUP_WEIGHT_MIN || k > CGROUP_WEIGHT_MAX) return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' is not in valid range %" PRIu64 "%s%" PRIu64 ".", - strna(name), (uint64_t) CGROUP_WEIGHT_MIN, - glyph(GLYPH_ELLIPSIS), (uint64_t) CGROUP_WEIGHT_MAX); + strna(name), CGROUP_WEIGHT_MIN, + glyph(GLYPH_ELLIPSIS), CGROUP_WEIGHT_MAX); *weight = k; return 0; @@ -1161,7 +1187,7 @@ int per_machine_hostname_match(sd_json_variant *hns, sd_json_dispatch_flags_t fl continue; } - if (streq(sd_json_variant_string(hns), hn)) + if (streq(sd_json_variant_string(e), hn)) return true; } @@ -1488,10 +1514,18 @@ int user_group_record_mangle( if (USER_RECORD_STRIP_MASK(load_flags) == _USER_RECORD_MASK_MAX) /* strip everything? */ return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Stripping everything from record, refusing."); - /* Extra safety: mark the "secret" part (that contains literal passwords and such) as sensitive, so - * that it is not included in debug output and erased from memory when we are done. We do this for - * any record that passes through here. */ - sd_json_variant_sensitive(sd_json_variant_by_key(v, "secret")); + /* Extra safety: mark sensitive parts of the JSON as such, so that they are not included in debug + * output and erased from memory when we are done. We do this for any record that passes through here. */ + FOREACH_STRING(key, + /* This section contains literal passwords and such in plain text */ + "secret", + + /* Personally Identifiable Information (PII) — avoid leaking in logs */ + "realName", + "location", + "emailAddress", + "birthDate") + sd_json_variant_sensitive(sd_json_variant_by_key(v, key)); /* Check if we have the special sections and if they match our flags set */ FOREACH_ELEMENT(i, mask_field) { @@ -1585,6 +1619,7 @@ int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load { "emailAddress", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(UserRecord, email_address), SD_JSON_STRICT }, { "iconName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(UserRecord, icon_name), SD_JSON_STRICT }, { "location", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(UserRecord, location), 0 }, + { "birthDate", SD_JSON_VARIANT_STRING, json_dispatch_birth_date, offsetof(UserRecord, birth_date), 0 }, { "disposition", SD_JSON_VARIANT_STRING, json_dispatch_user_disposition, offsetof(UserRecord, disposition), 0 }, { "lastChangeUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(UserRecord, last_change_usec), 0 }, { "lastPasswordChangeUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(UserRecord, last_password_change_usec), 0 }, @@ -1826,6 +1861,16 @@ const char* user_record_image_path(UserRecord *h) { user_record_home_directory_real(h) : NULL; } +static bool user_record_image_is_blockdev(UserRecord *h) { + assert(h); + + const char *p = user_record_image_path(h); + if (!p) + return false; + + return path_startswith(p, "/dev/"); +} + const char* user_record_cifs_user_name(UserRecord *h) { assert(h); @@ -1877,24 +1922,18 @@ const char* user_record_real_name(UserRecord *h) { } bool user_record_luks_discard(UserRecord *h) { - const char *ip; - assert(h); if (h->luks_discard >= 0) return h->luks_discard; - ip = user_record_image_path(h); - if (!ip) - return false; - /* Use discard by default if we are referring to a real block device, but not when operating on a * loopback device. We want to optimize for SSD and flash storage after all, but we should be careful * when storing stuff on top of regular file systems in loopback files as doing discard then would * mean thin provisioning and we should not do that willy-nilly since it means we'll risk EIO later * on should the disk space to back our file systems not be available. */ - return path_startswith(ip, "/dev/"); + return user_record_image_is_blockdev(h); } bool user_record_luks_offline_discard(UserRecord *h) { @@ -2060,7 +2099,7 @@ int user_record_removable(UserRecord *h) { return -1; /* For now consider only LUKS home directories with a reference by path as removable */ - return storage == USER_LUKS && path_startswith(user_record_image_path(h), "/dev/"); + return storage == USER_LUKS && user_record_image_is_blockdev(h); } uint64_t user_record_ratelimit_interval_usec(UserRecord *h) { diff --git a/src/shared/user-record.h b/src/shared/user-record.h index c3d52d2bd71aa..4eb35d43e4487 100644 --- a/src/shared/user-record.h +++ b/src/shared/user-record.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include + #include "sd-id128.h" #include "bitfield.h" @@ -266,6 +268,7 @@ typedef struct UserRecord { char *password_hint; char *icon_name; char *location; + struct tm birth_date; char *blob_directory; Hashmap *blob_manifest; diff --git a/src/shared/utmp-wtmp.c b/src/shared/utmp-wtmp.c index 6d150e7dcf3ef..cfc6ae5a3ddba 100644 --- a/src/shared/utmp-wtmp.c +++ b/src/shared/utmp-wtmp.c @@ -17,6 +17,8 @@ static void init_timestamp(struct utmpx *store, usec_t t) { if (t <= 0) t = now(CLOCK_REALTIME); + /* Silence static analyzers */ + assert_cc(USEC_PER_SEC > 0); store->ut_tv.tv_sec = t / USEC_PER_SEC; store->ut_tv.tv_usec = t % USEC_PER_SEC; } diff --git a/src/shared/varlink-idl-common.c b/src/shared/varlink-idl-common.c index 1dc44cd16c7a7..24eec3bfcf48a 100644 --- a/src/shared/varlink-idl-common.c +++ b/src/shared/varlink-idl-common.c @@ -53,3 +53,66 @@ SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD_BY_TYPE(NICE, ResourceLimit, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD_BY_TYPE(RTPRIO, ResourceLimit, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD_BY_TYPE(RTTIME, ResourceLimit, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_STRUCT_TYPE( + ExecCommand, + SD_VARLINK_FIELD_COMMENT("Path"), + SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Arguments"), + SD_VARLINK_DEFINE_FIELD(arguments, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Ignore failure of the command"), + SD_VARLINK_DEFINE_FIELD(ignoreFailure, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Run with full privileges"), + SD_VARLINK_DEFINE_FIELD(privileged, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Skip setuid handling"), + SD_VARLINK_DEFINE_FIELD(noSetuid, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Skip environment variable expansion"), + SD_VARLINK_DEFINE_FIELD(noEnvExpand, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Run via shell"), + SD_VARLINK_DEFINE_FIELD(viaShell, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ExecOutputType, + SD_VARLINK_DEFINE_ENUM_VALUE(inherit), + SD_VARLINK_DEFINE_ENUM_VALUE(null), + SD_VARLINK_DEFINE_ENUM_VALUE(tty), + SD_VARLINK_DEFINE_ENUM_VALUE(kmsg), + SD_VARLINK_DEFINE_ENUM_VALUE(kmsg_console), + SD_VARLINK_DEFINE_ENUM_VALUE(journal), + SD_VARLINK_DEFINE_ENUM_VALUE(journal_console), + SD_VARLINK_DEFINE_ENUM_VALUE(socket), + SD_VARLINK_DEFINE_ENUM_VALUE(fd), + SD_VARLINK_DEFINE_ENUM_VALUE(file), + SD_VARLINK_DEFINE_ENUM_VALUE(append), + SD_VARLINK_DEFINE_ENUM_VALUE(truncate)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + CGroupPressureWatch, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes), + SD_VARLINK_DEFINE_ENUM_VALUE(auto), + SD_VARLINK_DEFINE_ENUM_VALUE(skip)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ManagedOOMMode, + SD_VARLINK_DEFINE_ENUM_VALUE(auto), + SD_VARLINK_DEFINE_ENUM_VALUE(kill)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + EmergencyAction, + SD_VARLINK_DEFINE_ENUM_VALUE(none), + SD_VARLINK_DEFINE_ENUM_VALUE(exit), + SD_VARLINK_DEFINE_ENUM_VALUE(exit_force), + SD_VARLINK_DEFINE_ENUM_VALUE(reboot), + SD_VARLINK_DEFINE_ENUM_VALUE(reboot_force), + SD_VARLINK_DEFINE_ENUM_VALUE(reboot_immediate), + SD_VARLINK_DEFINE_ENUM_VALUE(poweroff), + SD_VARLINK_DEFINE_ENUM_VALUE(poweroff_force), + SD_VARLINK_DEFINE_ENUM_VALUE(poweroff_immediate), + SD_VARLINK_DEFINE_ENUM_VALUE(soft_reboot), + SD_VARLINK_DEFINE_ENUM_VALUE(soft_reboot_force), + SD_VARLINK_DEFINE_ENUM_VALUE(kexec), + SD_VARLINK_DEFINE_ENUM_VALUE(kexec_force), + SD_VARLINK_DEFINE_ENUM_VALUE(halt), + SD_VARLINK_DEFINE_ENUM_VALUE(halt_force), + SD_VARLINK_DEFINE_ENUM_VALUE(halt_immediate)); diff --git a/src/shared/varlink-idl-common.h b/src/shared/varlink-idl-common.h index de5177de4b3f8..fdfbfc7986faa 100644 --- a/src/shared/varlink-idl-common.h +++ b/src/shared/varlink-idl-common.h @@ -8,3 +8,8 @@ extern const sd_varlink_symbol vl_type_ProcessId; extern const sd_varlink_symbol vl_type_RateLimit; extern const sd_varlink_symbol vl_type_ResourceLimit; extern const sd_varlink_symbol vl_type_ResourceLimitTable; +extern const sd_varlink_symbol vl_type_ExecCommand; +extern const sd_varlink_symbol vl_type_ExecOutputType; +extern const sd_varlink_symbol vl_type_CGroupPressureWatch; +extern const sd_varlink_symbol vl_type_ManagedOOMMode; +extern const sd_varlink_symbol vl_type_EmergencyAction; diff --git a/src/shared/varlink-io.systemd.FactoryReset.c b/src/shared/varlink-io.systemd.FactoryReset.c index 7d55041e0f237..f7574e9e174f3 100644 --- a/src/shared/varlink-io.systemd.FactoryReset.c +++ b/src/shared/varlink-io.systemd.FactoryReset.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-varlink-idl.h" - #include "varlink-io.systemd.FactoryReset.h" static SD_VARLINK_DEFINE_ENUM_TYPE( diff --git a/src/shared/varlink-io.systemd.Facts.c b/src/shared/varlink-io.systemd.Facts.c new file mode 100644 index 0000000000000..dad1271c7248b --- /dev/null +++ b/src/shared/varlink-io.systemd.Facts.c @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink-idl.h" + +#include "varlink-io.systemd.Facts.h" + +static SD_VARLINK_DEFINE_ERROR(NoSuchFact); + +static SD_VARLINK_DEFINE_METHOD_FULL( + List, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("Fact family name, e.g. io.systemd.Basic.Hostname"), + SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), + /* This is currently an unused placeholder. Add examples when we have them. */ + SD_VARLINK_FIELD_COMMENT("Fact object name"), + SD_VARLINK_DEFINE_OUTPUT(object, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Fact value"), + SD_VARLINK_DEFINE_OUTPUT(value, SD_VARLINK_ANY, 0)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + Describe, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("Fact family name, e.g. io.systemd.Basic.Hostname"), + SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Fact family description"), + SD_VARLINK_DEFINE_OUTPUT(description, SD_VARLINK_STRING, 0)); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_Facts, + "io.systemd.Facts", + SD_VARLINK_INTERFACE_COMMENT("Facts APIs"), + SD_VARLINK_SYMBOL_COMMENT("Method to get a list of facts and their values"), + &vl_method_List, + SD_VARLINK_SYMBOL_COMMENT("Method to get the fact families"), + &vl_method_Describe, + SD_VARLINK_SYMBOL_COMMENT("No such fact found"), + &vl_error_NoSuchFact); diff --git a/src/shared/varlink-io.systemd.Facts.h b/src/shared/varlink-io.systemd.Facts.h new file mode 100644 index 0000000000000..ce07de32fb9df --- /dev/null +++ b/src/shared/varlink-io.systemd.Facts.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_interface vl_interface_io_systemd_Facts; diff --git a/src/shared/varlink-io.systemd.InstanceMetadata.c b/src/shared/varlink-io.systemd.InstanceMetadata.c new file mode 100644 index 0000000000000..b40bb6d4f35ed --- /dev/null +++ b/src/shared/varlink-io.systemd.InstanceMetadata.c @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bus-polkit.h" +#include "varlink-io.systemd.InstanceMetadata.h" + +static SD_VARLINK_DEFINE_ENUM_TYPE( + WellKnown, + SD_VARLINK_DEFINE_ENUM_VALUE(base), + SD_VARLINK_DEFINE_ENUM_VALUE(hostname), + SD_VARLINK_DEFINE_ENUM_VALUE(region), + SD_VARLINK_DEFINE_ENUM_VALUE(zone), + SD_VARLINK_DEFINE_ENUM_VALUE(ipv4_public), + SD_VARLINK_DEFINE_ENUM_VALUE(ipv6_public), + SD_VARLINK_DEFINE_ENUM_VALUE(ssh_key), + SD_VARLINK_DEFINE_ENUM_VALUE(userdata), + SD_VARLINK_DEFINE_ENUM_VALUE(userdata_base), + SD_VARLINK_DEFINE_ENUM_VALUE(userdata_base64)); + +static SD_VARLINK_DEFINE_METHOD( + Get, + SD_VARLINK_FIELD_COMMENT("The key to retrieve"), + SD_VARLINK_DEFINE_INPUT(key, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Start with a well-known key"), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(wellKnown, WellKnown, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The network interface to use"), + SD_VARLINK_DEFINE_INPUT(interface, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Refresh cached data if older (CLOCK_BOOTTIME, µs)"), + SD_VARLINK_DEFINE_INPUT(refreshUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Whether to accept cached data"), + SD_VARLINK_DEFINE_INPUT(cache, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The firewall mark value to use"), + SD_VARLINK_DEFINE_INPUT(firewallMark, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Controls whether to wait for connectivity"), + SD_VARLINK_DEFINE_INPUT(wait, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("The data in Base64 encoding."), + SD_VARLINK_DEFINE_OUTPUT(data, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The interface the data was found on."), + SD_VARLINK_DEFINE_OUTPUT(interface, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + GetVendorInfo, + SD_VARLINK_FIELD_COMMENT("The detected cloud vendor"), + SD_VARLINK_DEFINE_OUTPUT(vendor, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The URL to acquire the token from"), + SD_VARLINK_DEFINE_OUTPUT(tokenUrl, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The HTTP header to configure the refresh timeout for the token in"), + SD_VARLINK_DEFINE_OUTPUT(refreshHeaderName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The base URL to acquire the data from"), + SD_VARLINK_DEFINE_OUTPUT(dataUrl, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("A suffix to append to the data URL"), + SD_VARLINK_DEFINE_OUTPUT(dataUrlSuffix, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The HTTP header to pass the token in when requesting data"), + SD_VARLINK_DEFINE_OUTPUT(tokenHeaderName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Additional HTTP headers to pass when acquiring data"), + SD_VARLINK_DEFINE_OUTPUT(extraHeader, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("IPv4 address of IMDS server"), + SD_VARLINK_DEFINE_OUTPUT(addressIPv4, SD_VARLINK_INT, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("IPv6 address of IMDS server"), + SD_VARLINK_DEFINE_OUTPUT(addressIPv6, SD_VARLINK_INT, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("Well-known fields"), + SD_VARLINK_DEFINE_OUTPUT(wellKnown, SD_VARLINK_OBJECT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_ERROR( + KeyNotFound); + +static SD_VARLINK_DEFINE_ERROR( + WellKnownKeyUnset); + +static SD_VARLINK_DEFINE_ERROR( + NotAvailable); + +static SD_VARLINK_DEFINE_ERROR( + NotSupported); + +static SD_VARLINK_DEFINE_ERROR( + CommunicationFailure); + +static SD_VARLINK_DEFINE_ERROR( + Timeout); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_InstanceMetadata, + "io.systemd.InstanceMetadata", + SD_VARLINK_INTERFACE_COMMENT("APIs for acquiring cloud IMDS information."), + SD_VARLINK_SYMBOL_COMMENT("Well known data fields"), + &vl_type_WellKnown, + SD_VARLINK_SYMBOL_COMMENT("Acquire data."), + &vl_method_Get, + SD_VARLINK_SYMBOL_COMMENT("Get information about cloud vendor and IMDS connectivity."), + &vl_method_GetVendorInfo, + SD_VARLINK_SYMBOL_COMMENT("The requested key is not found on the IMDS server."), + &vl_error_KeyNotFound, + SD_VARLINK_SYMBOL_COMMENT("IMDS is disabled or otherwise not available."), + &vl_error_NotAvailable, + SD_VARLINK_SYMBOL_COMMENT("IMDS is not supported."), + &vl_error_NotSupported, + SD_VARLINK_SYMBOL_COMMENT("Well-known key is not set."), + &vl_error_WellKnownKeyUnset, + SD_VARLINK_SYMBOL_COMMENT("Communication with IMDS failed."), + &vl_error_CommunicationFailure, + SD_VARLINK_SYMBOL_COMMENT("Timeout reached"), + &vl_error_Timeout); diff --git a/src/shared/varlink-io.systemd.InstanceMetadata.h b/src/shared/varlink-io.systemd.InstanceMetadata.h new file mode 100644 index 0000000000000..60920bd9c9f55 --- /dev/null +++ b/src/shared/varlink-io.systemd.InstanceMetadata.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_interface vl_interface_io_systemd_InstanceMetadata; diff --git a/src/shared/varlink-io.systemd.Login.c b/src/shared/varlink-io.systemd.Login.c index cf09d18286679..9a07155ef20ca 100644 --- a/src/shared/varlink-io.systemd.Login.c +++ b/src/shared/varlink-io.systemd.Login.c @@ -94,6 +94,7 @@ static SD_VARLINK_DEFINE_ERROR(AlreadySessionMember); static SD_VARLINK_DEFINE_ERROR(VirtualTerminalAlreadyTaken); static SD_VARLINK_DEFINE_ERROR(TooManySessions); static SD_VARLINK_DEFINE_ERROR(UnitAllocationFailed); +static SD_VARLINK_DEFINE_ERROR(NoSessionPIDFD); SD_VARLINK_DEFINE_INTERFACE( io_systemd_Login, @@ -120,4 +121,6 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_SYMBOL_COMMENT("Maximum number of sessions reached"), &vl_error_TooManySessions, SD_VARLINK_SYMBOL_COMMENT("Failed to allocate a unit for the session"), - &vl_error_UnitAllocationFailed); + &vl_error_UnitAllocationFailed, + SD_VARLINK_SYMBOL_COMMENT("The session leader process does not have a pidfd"), + &vl_error_NoSessionPIDFD); diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c index 5f70e0a823848..cb1b0665d6092 100644 --- a/src/shared/varlink-io.systemd.Machine.c +++ b/src/shared/varlink-io.systemd.Machine.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-varlink-idl.h" - #include "bus-polkit.h" #include "varlink-idl-common.h" #include "varlink-io.systemd.Machine.h" @@ -13,6 +11,18 @@ SD_VARLINK_DEFINE_INPUT_BY_TYPE(pid, ProcessId, SD_VARLINK_NULLABLE), \ VARLINK_DEFINE_POLKIT_INPUT +SD_VARLINK_DEFINE_ENUM_TYPE( + MachineClass, + SD_VARLINK_DEFINE_ENUM_VALUE(container), + SD_VARLINK_DEFINE_ENUM_VALUE(vm), + SD_VARLINK_DEFINE_ENUM_VALUE(host)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + KillWhom, + SD_VARLINK_DEFINE_ENUM_VALUE(leader), + SD_VARLINK_DEFINE_ENUM_VALUE(supervisor), + SD_VARLINK_DEFINE_ENUM_VALUE(all)); + static SD_VARLINK_DEFINE_ENUM_TYPE( AcquireMetadata, SD_VARLINK_FIELD_COMMENT("Do not include metadata in the output"), @@ -33,7 +43,7 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0), SD_VARLINK_DEFINE_INPUT(id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_DEFINE_INPUT(class, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(class, MachineClass, 0), SD_VARLINK_FIELD_COMMENT("The leader PID as simple positive integer."), SD_VARLINK_DEFINE_INPUT(leader, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The leader PID as ProcessId structure. If both the leader and leaderProcessId parameters are specified they must reference the same process. Typically one would only specify one or the other however. It's generally recommended to specify leaderProcessId as it references a process in a robust way without risk of identifier recycling."), @@ -47,6 +57,8 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_DEFINE_INPUT(vSockCid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(sshAddress, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(sshPrivateKeyPath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Varlink socket address for direct machine control. The server at this address is expected to implement io.systemd.MachineInstance and optionally io.systemd.VirtualMachineInstance."), + SD_VARLINK_DEFINE_INPUT(controlAddress, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Controls whether to allocate a scope unit for the machine to register. If false, the client already took care of that and registered a service/scope specific to the machine."), SD_VARLINK_DEFINE_INPUT(allocateUnit, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), VARLINK_DEFINE_POLKIT_INPUT); @@ -63,7 +75,7 @@ static SD_VARLINK_DEFINE_METHOD( Kill, VARLINK_DEFINE_MACHINE_LOOKUP_AND_POLKIT_INPUT_FIELDS, SD_VARLINK_FIELD_COMMENT("Identifier that specifies what precisely to send the signal to (either 'leader', 'supervisor', or 'all')."), - SD_VARLINK_DEFINE_INPUT(whom, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(whom, KillWhom, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Numeric UNIX signal integer."), SD_VARLINK_DEFINE_INPUT(signal, SD_VARLINK_INT, 0)); @@ -80,7 +92,7 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_FIELD_COMMENT("Name of the software that registered this machine"), SD_VARLINK_DEFINE_OUTPUT(service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The class of this machine"), - SD_VARLINK_DEFINE_OUTPUT(class, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(class, MachineClass, 0), SD_VARLINK_FIELD_COMMENT("Leader process PID of this machine"), SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(leader, ProcessId, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Supervisor process PID of this machine"), @@ -97,6 +109,8 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_DEFINE_OUTPUT(sshAddress, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Path to private SSH key"), SD_VARLINK_DEFINE_OUTPUT(sshPrivateKeyPath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Varlink socket address for direct machine control, implementing io.systemd.MachineInstance and optionally further interfaces"), + SD_VARLINK_DEFINE_OUTPUT(controlAddress, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("List of addresses of the machine"), SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(addresses, Address, SD_VARLINK_ARRAY | SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("OS release information of the machine. It contains an array of key value pairs read from the os-release(5) file in the image."), @@ -218,6 +232,10 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_ProcessId, SD_VARLINK_SYMBOL_COMMENT("A timestamp object consisting of both CLOCK_REALTIME and CLOCK_MONOTONIC timestamps"), &vl_type_Timestamp, + SD_VARLINK_SYMBOL_COMMENT("The class of a machine"), + &vl_type_MachineClass, + SD_VARLINK_SYMBOL_COMMENT("What to send a signal to in a machine"), + &vl_type_KillWhom, SD_VARLINK_SYMBOL_COMMENT("A enum field allowing to gracefully get metadata"), &vl_type_AcquireMetadata, SD_VARLINK_SYMBOL_COMMENT("An address object"), diff --git a/src/shared/varlink-io.systemd.Machine.h b/src/shared/varlink-io.systemd.Machine.h index 605a31452642a..2f604c5acba11 100644 --- a/src/shared/varlink-io.systemd.Machine.h +++ b/src/shared/varlink-io.systemd.Machine.h @@ -4,3 +4,6 @@ #include "sd-varlink-idl.h" extern const sd_varlink_interface vl_interface_io_systemd_Machine; + +extern const sd_varlink_symbol vl_type_MachineClass; +extern const sd_varlink_symbol vl_type_KillWhom; diff --git a/src/shared/varlink-io.systemd.MachineImage.c b/src/shared/varlink-io.systemd.MachineImage.c index 2642852e03496..7f4e8f3940ca6 100644 --- a/src/shared/varlink-io.systemd.MachineImage.c +++ b/src/shared/varlink-io.systemd.MachineImage.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-varlink-idl.h" - #include "bus-polkit.h" #include "varlink-io.systemd.MachineImage.h" diff --git a/src/shared/varlink-io.systemd.MachineInstance.c b/src/shared/varlink-io.systemd.MachineInstance.c new file mode 100644 index 0000000000000..365b6f5f9e1af --- /dev/null +++ b/src/shared/varlink-io.systemd.MachineInstance.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.MachineInstance.h" + +static SD_VARLINK_DEFINE_METHOD(Terminate); +static SD_VARLINK_DEFINE_METHOD(PowerOff); +static SD_VARLINK_DEFINE_METHOD(Reboot); +static SD_VARLINK_DEFINE_METHOD(Pause); +static SD_VARLINK_DEFINE_METHOD(Resume); + +static SD_VARLINK_DEFINE_METHOD( + Describe, + SD_VARLINK_FIELD_COMMENT("True iff vCPUs are executing"), + SD_VARLINK_DEFINE_OUTPUT(running, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Backend-specific state string (e.g. 'running', 'paused', 'shutdown'); 'unknown' if unavailable"), + SD_VARLINK_DEFINE_OUTPUT(status, SD_VARLINK_STRING, 0)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + SubscribeEvents, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("If specified, only deliver events whose name matches one of these strings; null means all events"), + SD_VARLINK_DEFINE_INPUT(filter, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("Name of the event"), + SD_VARLINK_DEFINE_OUTPUT(event, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Event-specific payload"), + SD_VARLINK_DEFINE_OUTPUT(data, SD_VARLINK_OBJECT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_ERROR(NotConnected); +static SD_VARLINK_DEFINE_ERROR(NotSupported); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_MachineInstance, + "io.systemd.MachineInstance", + SD_VARLINK_SYMBOL_COMMENT("Forcefully terminate the machine immediately"), + &vl_method_Terminate, + SD_VARLINK_SYMBOL_COMMENT("Request a clean shutdown of the machine"), + &vl_method_PowerOff, + SD_VARLINK_SYMBOL_COMMENT("Reboot the machine"), + &vl_method_Reboot, + SD_VARLINK_SYMBOL_COMMENT("Pause/freeze the machine"), + &vl_method_Pause, + SD_VARLINK_SYMBOL_COMMENT("Resume a paused machine"), + &vl_method_Resume, + SD_VARLINK_SYMBOL_COMMENT("Query the current status of the machine"), + &vl_method_Describe, + SD_VARLINK_SYMBOL_COMMENT("Subscribe to machine events. Returns a stream of events as they occur."), + &vl_method_SubscribeEvents, + SD_VARLINK_SYMBOL_COMMENT("The connection to the machine backend is not available"), + &vl_error_NotConnected, + SD_VARLINK_SYMBOL_COMMENT("The requested operation is not supported"), + &vl_error_NotSupported); diff --git a/src/shared/varlink-io.systemd.MachineInstance.h b/src/shared/varlink-io.systemd.MachineInstance.h new file mode 100644 index 0000000000000..fa473b21510cd --- /dev/null +++ b/src/shared/varlink-io.systemd.MachineInstance.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_interface vl_interface_io_systemd_MachineInstance; diff --git a/src/shared/varlink-io.systemd.ManagedOOM.c b/src/shared/varlink-io.systemd.ManagedOOM.c index 763b0abfbd886..3e6a66559c0af 100644 --- a/src/shared/varlink-io.systemd.ManagedOOM.c +++ b/src/shared/varlink-io.systemd.ManagedOOM.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "varlink-idl-common.h" #include "varlink-io.systemd.ManagedOOM.h" /* Pull in vl_type_ControlGroup, since both interfaces need it */ @@ -19,6 +20,7 @@ static SD_VARLINK_DEFINE_ERROR(SubscriptionTaken); SD_VARLINK_DEFINE_INTERFACE( io_systemd_ManagedOOM, "io.systemd.ManagedOOM", + &vl_type_ManagedOOMMode, &vl_method_SubscribeManagedOOMCGroups, &vl_type_ControlGroup, &vl_error_SubscriptionTaken); diff --git a/src/shared/varlink-io.systemd.Manager.c b/src/shared/varlink-io.systemd.Manager.c index cb304f2295029..0c5ab53702b0d 100644 --- a/src/shared/varlink-io.systemd.Manager.c +++ b/src/shared/varlink-io.systemd.Manager.c @@ -3,6 +3,24 @@ #include "varlink-idl-common.h" #include "varlink-io.systemd.Manager.h" +SD_VARLINK_DEFINE_ENUM_TYPE( + LogTarget, + SD_VARLINK_DEFINE_ENUM_VALUE(console), + SD_VARLINK_DEFINE_ENUM_VALUE(kmsg), + SD_VARLINK_DEFINE_ENUM_VALUE(journal), + SD_VARLINK_DEFINE_ENUM_VALUE(syslog), + SD_VARLINK_DEFINE_ENUM_VALUE(console_prefixed), + SD_VARLINK_DEFINE_ENUM_VALUE(journal_or_kmsg), + SD_VARLINK_DEFINE_ENUM_VALUE(syslog_or_kmsg), + SD_VARLINK_DEFINE_ENUM_VALUE(auto), + SD_VARLINK_DEFINE_ENUM_VALUE(null)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + OOMPolicy, + SD_VARLINK_DEFINE_ENUM_VALUE(continue), + SD_VARLINK_DEFINE_ENUM_VALUE(stop), + SD_VARLINK_DEFINE_ENUM_VALUE(kill)); + static SD_VARLINK_DEFINE_STRUCT_TYPE( LogLevelStruct, SD_VARLINK_FIELD_COMMENT("'console' target log level"), @@ -25,13 +43,13 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#LogColor="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(LogLevel, LogLevelStruct, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#LogColor="), - SD_VARLINK_DEFINE_FIELD(LogTarget, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(LogTarget, LogTarget, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#ManagerEnvironment="), SD_VARLINK_DEFINE_FIELD(Environment, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultStandardOutput="), - SD_VARLINK_DEFINE_FIELD(DefaultStandardOutput, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DefaultStandardOutput, ExecOutputType, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultStandardError="), - SD_VARLINK_DEFINE_FIELD(DefaultStandardError, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DefaultStandardError, ExecOutputType, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#ServiceWatchdogs="), SD_VARLINK_DEFINE_FIELD(ServiceWatchdogs, SD_VARLINK_BOOL, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultTimerAccuracySec="), @@ -63,7 +81,15 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultMemoryPressureThresholdUSec="), SD_VARLINK_DEFINE_FIELD(DefaultMemoryPressureThresholdUSec, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultMemoryPressureWatch="), - SD_VARLINK_DEFINE_FIELD(DefaultMemoryPressureWatch, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DefaultMemoryPressureWatch, CGroupPressureWatch, 0), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultCPUPressureThresholdUSec="), + SD_VARLINK_DEFINE_FIELD(DefaultCPUPressureThresholdUSec, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultCPUPressureWatch="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DefaultCPUPressureWatch, CGroupPressureWatch, 0), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultIOPressureThresholdUSec="), + SD_VARLINK_DEFINE_FIELD(DefaultIOPressureThresholdUSec, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultIOPressureWatch="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DefaultIOPressureWatch, CGroupPressureWatch, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#RuntimeWatchdogSec="), SD_VARLINK_DEFINE_FIELD(RuntimeWatchdogUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#RebootWatchdogSec="), @@ -79,17 +105,19 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#TimerSlackNSec="), SD_VARLINK_DEFINE_FIELD(TimerSlackNSec, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultOOMPolicy="), - SD_VARLINK_DEFINE_FIELD(DefaultOOMPolicy, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DefaultOOMPolicy, OOMPolicy, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultOOMScoreAdjust="), SD_VARLINK_DEFINE_FIELD(DefaultOOMScoreAdjust, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultRestrictSUIDSGID="), SD_VARLINK_DEFINE_FIELD(DefaultRestrictSUIDSGID, SD_VARLINK_BOOL, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#CtrlAltDelBurstAction="), - SD_VARLINK_DEFINE_FIELD(CtrlAltDelBurstAction, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CtrlAltDelBurstAction, EmergencyAction, 0), SD_VARLINK_FIELD_COMMENT("The console on which systemd asks for confirmation when spawning processes"), SD_VARLINK_DEFINE_FIELD(ConfirmSpawn, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Root of the control group hierarchy that the manager is running in"), - SD_VARLINK_DEFINE_FIELD(ControlGroup, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD(ControlGroup, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultMemoryZSwapWriteback="), + SD_VARLINK_DEFINE_FIELD(DefaultMemoryZSwapWriteback, SD_VARLINK_BOOL, 0)); static SD_VARLINK_DEFINE_STRUCT_TYPE( ManagerRuntime, @@ -193,6 +221,15 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_FIELD_COMMENT("Job enqueue error message (on failure)"), SD_VARLINK_DEFINE_OUTPUT(errorMessage, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD(PowerOff); +static SD_VARLINK_DEFINE_METHOD(Reboot); +static SD_VARLINK_DEFINE_METHOD(Halt); +static SD_VARLINK_DEFINE_METHOD(KExec); +static SD_VARLINK_DEFINE_METHOD( + SoftReboot, + SD_VARLINK_FIELD_COMMENT("New root directory for the soft reboot"), + SD_VARLINK_DEFINE_INPUT(root, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + static SD_VARLINK_DEFINE_ERROR(RateLimitReached); SD_VARLINK_DEFINE_INTERFACE( @@ -205,6 +242,16 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_Reload, SD_VARLINK_SYMBOL_COMMENT("Enqueue all marked jobs"), &vl_method_EnqueueMarkedJobs, + SD_VARLINK_SYMBOL_COMMENT("Power off the system"), + &vl_method_PowerOff, + SD_VARLINK_SYMBOL_COMMENT("Reboot the system"), + &vl_method_Reboot, + SD_VARLINK_SYMBOL_COMMENT("Halt the system"), + &vl_method_Halt, + SD_VARLINK_SYMBOL_COMMENT("Reboot the system via kexec"), + &vl_method_KExec, + SD_VARLINK_SYMBOL_COMMENT("Soft-reboot the userspace"), + &vl_method_SoftReboot, &vl_error_RateLimitReached, &vl_type_ManagerContext, &vl_type_ManagerRuntime, @@ -212,4 +259,9 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_ResourceLimit, &vl_type_ResourceLimitTable, &vl_type_RateLimit, - &vl_type_LogLevelStruct); + &vl_type_LogLevelStruct, + &vl_type_LogTarget, + &vl_type_OOMPolicy, + &vl_type_ExecOutputType, + &vl_type_CGroupPressureWatch, + &vl_type_EmergencyAction); diff --git a/src/shared/varlink-io.systemd.Manager.h b/src/shared/varlink-io.systemd.Manager.h index ce411888f92c3..48247dd350679 100644 --- a/src/shared/varlink-io.systemd.Manager.h +++ b/src/shared/varlink-io.systemd.Manager.h @@ -4,3 +4,6 @@ #include "sd-varlink-idl.h" extern const sd_varlink_interface vl_interface_io_systemd_Manager; + +extern const sd_varlink_symbol vl_type_LogTarget; +extern const sd_varlink_symbol vl_type_OOMPolicy; diff --git a/src/shared/varlink-io.systemd.Metrics.c b/src/shared/varlink-io.systemd.Metrics.c index f4623f1f159a7..4c210c9b42276 100644 --- a/src/shared/varlink-io.systemd.Metrics.c +++ b/src/shared/varlink-io.systemd.Metrics.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-varlink-idl.h" - #include "varlink-io.systemd.Metrics.h" static SD_VARLINK_DEFINE_ENUM_TYPE( diff --git a/src/shared/varlink-io.systemd.MuteConsole.c b/src/shared/varlink-io.systemd.MuteConsole.c index 723b19985e8be..2500e2cfa059b 100644 --- a/src/shared/varlink-io.systemd.MuteConsole.c +++ b/src/shared/varlink-io.systemd.MuteConsole.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-varlink-idl.h" - #include "varlink-io.systemd.MuteConsole.h" static SD_VARLINK_DEFINE_METHOD_FULL( diff --git a/src/shared/varlink-io.systemd.Network.Link.c b/src/shared/varlink-io.systemd.Network.Link.c index ccbb046ca00ac..82807939e3229 100644 --- a/src/shared/varlink-io.systemd.Network.Link.c +++ b/src/shared/varlink-io.systemd.Network.Link.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "bus-polkit.h" +#include "varlink-io.systemd.Network.h" #include "varlink-io.systemd.Network.Link.h" #define VARLINK_NETWORK_INTERFACE_INPUTS \ @@ -9,6 +10,12 @@ SD_VARLINK_FIELD_COMMENT("Name of the interface. If specified together with InterfaceIndex, both must reference the same link."), \ SD_VARLINK_DEFINE_INPUT(InterfaceName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE) +static SD_VARLINK_DEFINE_METHOD( + Describe, + VARLINK_NETWORK_INTERFACE_INPUTS, + SD_VARLINK_FIELD_COMMENT("Interface description"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(Interface, Interface, 0)); + static SD_VARLINK_DEFINE_METHOD( Up, VARLINK_NETWORK_INTERFACE_INPUTS, @@ -19,10 +26,64 @@ static SD_VARLINK_DEFINE_METHOD( VARLINK_NETWORK_INTERFACE_INPUTS, VARLINK_DEFINE_POLKIT_INPUT); +static SD_VARLINK_DEFINE_METHOD( + Renew, + VARLINK_NETWORK_INTERFACE_INPUTS, + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + ForceRenew, + VARLINK_NETWORK_INTERFACE_INPUTS, + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + Reconfigure, + VARLINK_NETWORK_INTERFACE_INPUTS, + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_ERROR(InterfaceUnmanaged); + SD_VARLINK_DEFINE_INTERFACE( io_systemd_Network_Link, "io.systemd.Network.Link", SD_VARLINK_SYMBOL_COMMENT("Bring the specified link up."), &vl_method_Up, SD_VARLINK_SYMBOL_COMMENT("Bring the specified link down."), - &vl_method_Down); + &vl_method_Down, + SD_VARLINK_SYMBOL_COMMENT("Renew DHCP leases on the specified link."), + &vl_method_Renew, + SD_VARLINK_SYMBOL_COMMENT("Force-renew DHCP server leases on the specified link."), + &vl_method_ForceRenew, + SD_VARLINK_SYMBOL_COMMENT("Unconditionally reconfigure the specified link."), + &vl_method_Reconfigure, + SD_VARLINK_SYMBOL_COMMENT("Describe the specified link by index or name."), + &vl_method_Describe, + SD_VARLINK_SYMBOL_COMMENT("The specified interface is not managed by systemd-networkd."), + &vl_error_InterfaceUnmanaged, + &vl_type_Address, + &vl_type_BitRates, + &vl_type_DHCPLease, + &vl_type_DHCPServer, + &vl_type_DHCPServerLease, + &vl_type_DHCPv6Client, + &vl_type_DHCPv6ClientPD, + &vl_type_DHCPv6ClientVendorOption, + &vl_type_DNS, + &vl_type_DNSSECNegativeTrustAnchor, + &vl_type_DNSSetting, + &vl_type_Domain, + &vl_type_Interface, + &vl_type_LinkState, + &vl_type_LinkAddressState, + &vl_type_LinkOnlineState, + &vl_type_LinkRequiredAddressFamily, + &vl_type_LLDPNeighbor, + &vl_type_NDisc, + &vl_type_Neighbor, + &vl_type_NextHop, + &vl_type_NextHopGroup, + &vl_type_NTP, + &vl_type_Pref64, + &vl_type_PrivateOption, + &vl_type_Route, + &vl_type_SIP); diff --git a/src/shared/varlink-io.systemd.Network.c b/src/shared/varlink-io.systemd.Network.c index c67b857f12dcb..e98d517cc7ec4 100644 --- a/src/shared/varlink-io.systemd.Network.c +++ b/src/shared/varlink-io.systemd.Network.c @@ -9,7 +9,7 @@ SD_VARLINK_FIELD_COMMENT(comment " (human-readable format)"), \ SD_VARLINK_DEFINE_FIELD(field_name##String, SD_VARLINK_STRING, (flags)) -static SD_VARLINK_DEFINE_ENUM_TYPE( +SD_VARLINK_DEFINE_ENUM_TYPE( LinkState, SD_VARLINK_DEFINE_ENUM_VALUE(pending), SD_VARLINK_DEFINE_ENUM_VALUE(initialized), @@ -93,7 +93,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Configuration state of this rule"), SD_VARLINK_DEFINE_FIELD(ConfigState, SD_VARLINK_STRING, 0)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( Route, SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6)"), SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, 0), @@ -149,14 +149,14 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("TCP congestion control algorithm for this route"), SD_VARLINK_DEFINE_FIELD(TCPCongestionControlAlgorithm, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( NextHopGroup, SD_VARLINK_FIELD_COMMENT("Next hop identifier in the multipath group"), SD_VARLINK_DEFINE_FIELD(ID, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("Weight for load balancing (higher means more traffic)"), SD_VARLINK_DEFINE_FIELD(Weight, SD_VARLINK_INT, 0)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( NextHop, SD_VARLINK_FIELD_COMMENT("Next hop identifier"), SD_VARLINK_DEFINE_FIELD(ID, SD_VARLINK_INT, 0), @@ -181,7 +181,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Configuration state of this next hop"), SD_VARLINK_DEFINE_FIELD(ConfigState, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( LLDPNeighbor, SD_VARLINK_FIELD_COMMENT("Chassis identifier in human-readable format"), SD_VARLINK_DEFINE_FIELD(ChassisID, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), @@ -204,7 +204,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("VLAN identifier"), SD_VARLINK_DEFINE_FIELD(VlanID, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DNS, SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6)"), SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, 0), @@ -219,7 +219,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0), VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of the configuration provider", SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( NTP, SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6) for address-based servers"), SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, SD_VARLINK_NULLABLE), @@ -230,7 +230,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0), VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of the configuration provider", SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( SIP, SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6) for address-based servers"), SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, SD_VARLINK_NULLABLE), @@ -241,7 +241,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0), VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of the configuration provider", SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( Domain, SD_VARLINK_FIELD_COMMENT("DNS search or route domain name"), SD_VARLINK_DEFINE_FIELD(Domain, SD_VARLINK_STRING, 0), @@ -249,14 +249,14 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0), VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of the configuration provider", SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DNSSECNegativeTrustAnchor, SD_VARLINK_FIELD_COMMENT("Domain name for which DNSSEC validation is disabled"), SD_VARLINK_DEFINE_FIELD(DNSSECNegativeTrustAnchor, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("Configuration source for this negative trust anchor"), SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DNSSetting, SD_VARLINK_FIELD_COMMENT("Link-Local Multicast Name Resolution setting"), SD_VARLINK_DEFINE_FIELD(LLMNR, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), @@ -269,7 +269,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Configuration source for this DNS setting"), SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( Pref64, SD_VARLINK_FIELD_COMMENT("IPv6 prefix for NAT64/DNS64"), SD_VARLINK_DEFINE_FIELD(Prefix, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), @@ -281,12 +281,12 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(LifetimeUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of router that provided this prefix", SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( NDisc, SD_VARLINK_FIELD_COMMENT("PREF64 (RFC8781) prefixes advertised via IPv6 Router Advertisements"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(PREF64, Pref64, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( Address, SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6)"), SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, 0), @@ -319,7 +319,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Configuration state of this address"), SD_VARLINK_DEFINE_FIELD(ConfigState, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( Neighbor, SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6)"), SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, 0), @@ -331,7 +331,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Configuration state of this neighbor entry"), SD_VARLINK_DEFINE_FIELD(ConfigState, SD_VARLINK_STRING, 0)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DHCPLease, SD_VARLINK_FIELD_COMMENT("Timestamp when the lease was acquired in microseconds"), SD_VARLINK_DEFINE_FIELD(LeaseTimestampUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), @@ -342,14 +342,14 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Hostname received from DHCP server"), SD_VARLINK_DEFINE_FIELD(Hostname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( PrivateOption, SD_VARLINK_FIELD_COMMENT("DHCP option number"), SD_VARLINK_DEFINE_FIELD(Option, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Raw data of the private DHCP option"), SD_VARLINK_DEFINE_FIELD(PrivateOptionData, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DHCPv6ClientPD, SD_VARLINK_FIELD_COMMENT("Delegated IPv6 prefix"), SD_VARLINK_DEFINE_FIELD(Prefix, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), @@ -362,7 +362,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Valid lifetime of the prefix in microseconds"), SD_VARLINK_DEFINE_FIELD(ValidLifetimeUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DHCPv6ClientVendorOption, SD_VARLINK_FIELD_COMMENT("IANA enterprise number identifying the vendor"), SD_VARLINK_DEFINE_FIELD(EnterpriseId, SD_VARLINK_INT, 0), @@ -371,7 +371,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Raw data of the vendor-specific sub-option"), SD_VARLINK_DEFINE_FIELD(SubOptionData, SD_VARLINK_STRING, 0)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DHCPv6Client, SD_VARLINK_FIELD_COMMENT("DHCPv6 lease information including timestamps and timeouts"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Lease, DHCPLease, SD_VARLINK_NULLABLE), @@ -382,7 +382,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("DHCP Unique Identifier (DUID) of the client"), SD_VARLINK_DEFINE_FIELD(DUID, SD_VARLINK_INT, SD_VARLINK_ARRAY)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DHCPServerLease, SD_VARLINK_FIELD_COMMENT("DHCP client identifier"), SD_VARLINK_DEFINE_FIELD(ClientId, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), @@ -400,7 +400,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Lease expiration time in realtime microseconds"), SD_VARLINK_DEFINE_FIELD(ExpirationRealtimeUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DHCPServer, SD_VARLINK_FIELD_COMMENT("Offset from the network address for the DHCP address pool"), SD_VARLINK_DEFINE_FIELD(PoolOffset, SD_VARLINK_INT, 0), @@ -411,7 +411,14 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Static DHCP leases configured for specific clients"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(StaticLeases, DHCPServerLease, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( + BitRates, + SD_VARLINK_FIELD_COMMENT("Transmit bitrate in bits per second"), + SD_VARLINK_DEFINE_FIELD(TxBitRate, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("Receive bitrate in bits per second"), + SD_VARLINK_DEFINE_FIELD(RxBitRate, SD_VARLINK_INT, 0)); + +SD_VARLINK_DEFINE_STRUCT_TYPE( Interface, SD_VARLINK_FIELD_COMMENT("Network interface index"), SD_VARLINK_DEFINE_FIELD(Index, SD_VARLINK_INT, 0), @@ -532,8 +539,10 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(DHCPv4Client, SD_VARLINK_OBJECT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("DHCPv6 client configuration and lease information"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(DHCPv6Client, DHCPv6Client, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("LLDP neighbors discovered on this interface"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(LLDP, LLDPNeighbor, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); + SD_VARLINK_FIELD_COMMENT("LLDP transmit configuration for this interface"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(LLDP, LLDPNeighbor, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Current transmit/receive bitrates from speed meter"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(BitRates, BitRates, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( Describe, @@ -593,6 +602,7 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_FIELD_COMMENT("Whether persistent storage is ready and writable"), SD_VARLINK_DEFINE_INPUT(Ready, SD_VARLINK_BOOL, 0)); +static SD_VARLINK_DEFINE_ERROR(AlreadyReloading); static SD_VARLINK_DEFINE_ERROR(StorageReadOnly); SD_VARLINK_DEFINE_INTERFACE( @@ -604,6 +614,7 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_GetLLDPNeighbors, &vl_method_SetPersistentStorage, &vl_type_Address, + &vl_type_BitRates, &vl_type_DHCPLease, &vl_type_DHCPServer, &vl_type_DHCPServerLease, @@ -631,4 +642,5 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_Route, &vl_type_RoutingPolicyRule, &vl_type_SIP, + &vl_error_AlreadyReloading, &vl_error_StorageReadOnly); diff --git a/src/shared/varlink-io.systemd.Network.h b/src/shared/varlink-io.systemd.Network.h index 6d727cd9ee8c7..25d3bc9600f66 100644 --- a/src/shared/varlink-io.systemd.Network.h +++ b/src/shared/varlink-io.systemd.Network.h @@ -7,3 +7,27 @@ extern const sd_varlink_interface vl_interface_io_systemd_Network; extern const sd_varlink_symbol vl_type_LinkAddressState; extern const sd_varlink_symbol vl_type_LinkOnlineState; extern const sd_varlink_symbol vl_type_LinkRequiredAddressFamily; +extern const sd_varlink_symbol vl_type_LinkState; +extern const sd_varlink_symbol vl_type_Route; +extern const sd_varlink_symbol vl_type_NextHopGroup; +extern const sd_varlink_symbol vl_type_NextHop; +extern const sd_varlink_symbol vl_type_LLDPNeighbor; +extern const sd_varlink_symbol vl_type_DNS; +extern const sd_varlink_symbol vl_type_NTP; +extern const sd_varlink_symbol vl_type_SIP; +extern const sd_varlink_symbol vl_type_Domain; +extern const sd_varlink_symbol vl_type_DNSSECNegativeTrustAnchor; +extern const sd_varlink_symbol vl_type_DNSSetting; +extern const sd_varlink_symbol vl_type_Pref64; +extern const sd_varlink_symbol vl_type_NDisc; +extern const sd_varlink_symbol vl_type_Address; +extern const sd_varlink_symbol vl_type_Neighbor; +extern const sd_varlink_symbol vl_type_DHCPLease; +extern const sd_varlink_symbol vl_type_PrivateOption; +extern const sd_varlink_symbol vl_type_DHCPv6ClientPD; +extern const sd_varlink_symbol vl_type_DHCPv6ClientVendorOption; +extern const sd_varlink_symbol vl_type_DHCPv6Client; +extern const sd_varlink_symbol vl_type_DHCPServerLease; +extern const sd_varlink_symbol vl_type_DHCPServer; +extern const sd_varlink_symbol vl_type_BitRates; +extern const sd_varlink_symbol vl_type_Interface; diff --git a/src/shared/varlink-io.systemd.PCRExtend.c b/src/shared/varlink-io.systemd.PCRExtend.c index 87edec349ef80..d309330f405a6 100644 --- a/src/shared/varlink-io.systemd.PCRExtend.c +++ b/src/shared/varlink-io.systemd.PCRExtend.c @@ -12,7 +12,8 @@ static SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_DEFINE_ENUM_VALUE(keyslot), SD_VARLINK_DEFINE_ENUM_VALUE(nvpcr_init), SD_VARLINK_DEFINE_ENUM_VALUE(nvpcr_separator), - SD_VARLINK_DEFINE_ENUM_VALUE(dm_verity)); + SD_VARLINK_DEFINE_ENUM_VALUE(dm_verity), + SD_VARLINK_DEFINE_ENUM_VALUE(imds_userdata)); static SD_VARLINK_DEFINE_METHOD( Extend, diff --git a/src/shared/varlink-io.systemd.Repart.c b/src/shared/varlink-io.systemd.Repart.c index 8d50454595117..dbfb8d0360d2f 100644 --- a/src/shared/varlink-io.systemd.Repart.c +++ b/src/shared/varlink-io.systemd.Repart.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-varlink-idl.h" - #include "varlink-io.systemd.Repart.h" static SD_VARLINK_DEFINE_ENUM_TYPE( diff --git a/src/shared/varlink-io.systemd.Shutdown.c b/src/shared/varlink-io.systemd.Shutdown.c new file mode 100644 index 0000000000000..55728b7463e2c --- /dev/null +++ b/src/shared/varlink-io.systemd.Shutdown.c @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bus-polkit.h" +#include "varlink-io.systemd.Shutdown.h" + +static SD_VARLINK_DEFINE_METHOD( + PowerOff, + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"), + SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + Reboot, + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"), + SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + Halt, + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"), + SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + KExec, + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"), + SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + SoftReboot, + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"), + SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_ERROR(AlreadyInProgress); +static SD_VARLINK_DEFINE_ERROR( + BlockedByInhibitor, + SD_VARLINK_FIELD_COMMENT("Who is holding the inhibitor"), + SD_VARLINK_DEFINE_FIELD(who, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Why the inhibitor is held"), + SD_VARLINK_DEFINE_FIELD(why, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_Shutdown, + "io.systemd.Shutdown", + SD_VARLINK_INTERFACE_COMMENT("APIs for shutting down or rebooting the system."), + SD_VARLINK_SYMBOL_COMMENT("Power off the system"), + &vl_method_PowerOff, + SD_VARLINK_SYMBOL_COMMENT("Reboot the system"), + &vl_method_Reboot, + SD_VARLINK_SYMBOL_COMMENT("Halt the system"), + &vl_method_Halt, + SD_VARLINK_SYMBOL_COMMENT("Reboot the system via kexec"), + &vl_method_KExec, + SD_VARLINK_SYMBOL_COMMENT("Reboot userspace only"), + &vl_method_SoftReboot, + SD_VARLINK_SYMBOL_COMMENT("Another shutdown or sleep operation is already in progress"), + &vl_error_AlreadyInProgress, + SD_VARLINK_SYMBOL_COMMENT("Operation denied due to active block inhibitor"), + &vl_error_BlockedByInhibitor); diff --git a/src/shared/varlink-io.systemd.Shutdown.h b/src/shared/varlink-io.systemd.Shutdown.h new file mode 100644 index 0000000000000..e97853b0bc799 --- /dev/null +++ b/src/shared/varlink-io.systemd.Shutdown.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_interface vl_interface_io_systemd_Shutdown; diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index a008b506e9b1d..422aeb6952b30 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -1,6 +1,183 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "varlink-idl-common.h" +#include "varlink-io.systemd.Unit.h" + +SD_VARLINK_DEFINE_ENUM_TYPE( + ExecInputType, + SD_VARLINK_DEFINE_ENUM_VALUE(null), + SD_VARLINK_DEFINE_ENUM_VALUE(tty), + SD_VARLINK_DEFINE_ENUM_VALUE(tty_force), + SD_VARLINK_DEFINE_ENUM_VALUE(tty_fail), + SD_VARLINK_DEFINE_ENUM_VALUE(socket), + SD_VARLINK_DEFINE_ENUM_VALUE(fd), + SD_VARLINK_DEFINE_ENUM_VALUE(data), + SD_VARLINK_DEFINE_ENUM_VALUE(file)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ExecUtmpMode, + SD_VARLINK_DEFINE_ENUM_VALUE(init), + SD_VARLINK_DEFINE_ENUM_VALUE(login), + SD_VARLINK_DEFINE_ENUM_VALUE(user)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ExecPreserveMode, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes), + SD_VARLINK_DEFINE_ENUM_VALUE(restart)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ExecKeyringMode, + SD_VARLINK_DEFINE_ENUM_VALUE(inherit), + SD_VARLINK_DEFINE_ENUM_VALUE(private), + SD_VARLINK_DEFINE_ENUM_VALUE(shared)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + MemoryTHP, + SD_VARLINK_DEFINE_ENUM_VALUE(inherit), + SD_VARLINK_DEFINE_ENUM_VALUE(disable), + SD_VARLINK_DEFINE_ENUM_VALUE(madvise), + SD_VARLINK_DEFINE_ENUM_VALUE(system)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ProtectProc, + SD_VARLINK_DEFINE_ENUM_VALUE(default), + SD_VARLINK_DEFINE_ENUM_VALUE(noaccess), + SD_VARLINK_DEFINE_ENUM_VALUE(invisible), + SD_VARLINK_DEFINE_ENUM_VALUE(ptraceable)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ProcSubset, + SD_VARLINK_DEFINE_ENUM_VALUE(all), + SD_VARLINK_DEFINE_ENUM_VALUE(pid)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ProtectSystem, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes), + SD_VARLINK_DEFINE_ENUM_VALUE(full), + SD_VARLINK_DEFINE_ENUM_VALUE(strict)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ProtectHome, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes), + SD_VARLINK_DEFINE_ENUM_VALUE(read_only), + SD_VARLINK_DEFINE_ENUM_VALUE(tmpfs)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + PrivateTmp, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(connected), + SD_VARLINK_DEFINE_ENUM_VALUE(disconnected)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + PrivateUsers, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(self), + SD_VARLINK_DEFINE_ENUM_VALUE(identity), + SD_VARLINK_DEFINE_ENUM_VALUE(full), + SD_VARLINK_DEFINE_ENUM_VALUE(managed)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ProtectHostname, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes), + SD_VARLINK_DEFINE_ENUM_VALUE(private)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ProtectControlGroups, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes), + SD_VARLINK_DEFINE_ENUM_VALUE(private), + SD_VARLINK_DEFINE_ENUM_VALUE(strict)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + PrivatePIDs, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + CGroupDevicePolicy, + SD_VARLINK_DEFINE_ENUM_VALUE(auto), + SD_VARLINK_DEFINE_ENUM_VALUE(closed), + SD_VARLINK_DEFINE_ENUM_VALUE(strict)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ManagedOOMPreference, + SD_VARLINK_DEFINE_ENUM_VALUE(none), + SD_VARLINK_DEFINE_ENUM_VALUE(avoid), + SD_VARLINK_DEFINE_ENUM_VALUE(omit)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + CollectMode, + SD_VARLINK_DEFINE_ENUM_VALUE(inactive), + SD_VARLINK_DEFINE_ENUM_VALUE(inactive_or_failed)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + JobMode, + SD_VARLINK_DEFINE_ENUM_VALUE(fail), + SD_VARLINK_DEFINE_ENUM_VALUE(lenient), + SD_VARLINK_DEFINE_ENUM_VALUE(replace), + SD_VARLINK_DEFINE_ENUM_VALUE(replace_irreversibly), + SD_VARLINK_DEFINE_ENUM_VALUE(isolate), + SD_VARLINK_DEFINE_ENUM_VALUE(flush), + SD_VARLINK_DEFINE_ENUM_VALUE(ignore_dependencies), + SD_VARLINK_DEFINE_ENUM_VALUE(ignore_requirements), + SD_VARLINK_DEFINE_ENUM_VALUE(triggering), + SD_VARLINK_DEFINE_ENUM_VALUE(restart_dependencies)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + CGroupController, + SD_VARLINK_DEFINE_ENUM_VALUE(cpu), + SD_VARLINK_DEFINE_ENUM_VALUE(cpuacct), + SD_VARLINK_DEFINE_ENUM_VALUE(cpuset), + SD_VARLINK_DEFINE_ENUM_VALUE(io), + SD_VARLINK_DEFINE_ENUM_VALUE(blkio), + SD_VARLINK_DEFINE_ENUM_VALUE(memory), + SD_VARLINK_DEFINE_ENUM_VALUE(devices), + SD_VARLINK_DEFINE_ENUM_VALUE(pids), + SD_VARLINK_DEFINE_ENUM_VALUE(bpf_firewall), + SD_VARLINK_DEFINE_ENUM_VALUE(bpf_devices), + SD_VARLINK_DEFINE_ENUM_VALUE(bpf_foreign), + SD_VARLINK_DEFINE_ENUM_VALUE(bpf_socket_bind), + SD_VARLINK_DEFINE_ENUM_VALUE(bpf_restrict_network_interfaces), + SD_VARLINK_DEFINE_ENUM_VALUE(bpf_bind_network_interface)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + PrivateBPF, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + CPUSchedulingPolicy, + SD_VARLINK_DEFINE_ENUM_VALUE(other), + SD_VARLINK_DEFINE_ENUM_VALUE(batch), + SD_VARLINK_DEFINE_ENUM_VALUE(idle), + SD_VARLINK_DEFINE_ENUM_VALUE(fifo), + SD_VARLINK_DEFINE_ENUM_VALUE(ext), + SD_VARLINK_DEFINE_ENUM_VALUE(rr)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + IOSchedulingClass, + SD_VARLINK_DEFINE_ENUM_VALUE(none), + SD_VARLINK_DEFINE_ENUM_VALUE(realtime), + SD_VARLINK_DEFINE_ENUM_VALUE(best_effort), + SD_VARLINK_DEFINE_ENUM_VALUE(idle)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + NUMAPolicy, + SD_VARLINK_DEFINE_ENUM_VALUE(default), + SD_VARLINK_DEFINE_ENUM_VALUE(preferred), + SD_VARLINK_DEFINE_ENUM_VALUE(bind), + SD_VARLINK_DEFINE_ENUM_VALUE(interleave), + SD_VARLINK_DEFINE_ENUM_VALUE(local)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + MountPropagationFlag, + SD_VARLINK_DEFINE_ENUM_VALUE(shared), + SD_VARLINK_DEFINE_ENUM_VALUE(slave), + SD_VARLINK_DEFINE_ENUM_VALUE(private)); /* CGroupContext */ static SD_VARLINK_DEFINE_STRUCT_TYPE( @@ -198,7 +375,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#DeviceAllow="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(DeviceAllow, CGroupDeviceAllow, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#DevicePolicy=auto%7Cclosed%7Cstrict"), - SD_VARLINK_DEFINE_FIELD(DevicePolicy, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DevicePolicy, CGroupDevicePolicy, 0), /* Control Group Management * https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#Control%20Group%20Management */ @@ -207,26 +384,34 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#DelegateSubgroup="), SD_VARLINK_DEFINE_FIELD(DelegateSubgroup, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#DisableControllers="), - SD_VARLINK_DEFINE_FIELD(DelegateControllers, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DelegateControllers, CGroupController, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#DisableControllers="), - SD_VARLINK_DEFINE_FIELD(DisableControllers, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DisableControllers, CGroupController, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), /* Memory Pressure Control * https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#Memory%20Pressure%20Control */ SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#ManagedOOMSwap=auto%7Ckill"), - SD_VARLINK_DEFINE_FIELD(ManagedOOMSwap, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ManagedOOMSwap, ManagedOOMMode, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#ManagedOOMSwap=auto%7Ckill"), - SD_VARLINK_DEFINE_FIELD(ManagedOOMMemoryPressure, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ManagedOOMMemoryPressure, ManagedOOMMode, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#ManagedOOMMemoryPressureLimit="), SD_VARLINK_DEFINE_FIELD(ManagedOOMMemoryPressureLimit, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#ManagedOOMMemoryPressureDurationSec="), SD_VARLINK_DEFINE_FIELD(ManagedOOMMemoryPressureDurationUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#ManagedOOMPreference=none%7Cavoid%7Comit"), - SD_VARLINK_DEFINE_FIELD(ManagedOOMPreference, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ManagedOOMPreference, ManagedOOMPreference, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#MemoryPressureWatch="), - SD_VARLINK_DEFINE_FIELD(MemoryPressureWatch, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(MemoryPressureWatch, CGroupPressureWatch, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#MemoryPressureThresholdSec="), SD_VARLINK_DEFINE_FIELD(MemoryPressureThresholdUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#CPUPressureWatch="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CPUPressureWatch, CGroupPressureWatch, 0), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#CPUPressureThresholdSec="), + SD_VARLINK_DEFINE_FIELD(CPUPressureThresholdUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#IOPressureWatch="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(IOPressureWatch, CGroupPressureWatch, 0), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#IOPressureThresholdSec="), + SD_VARLINK_DEFINE_FIELD(IOPressureThresholdUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), /* Others */ SD_VARLINK_FIELD_COMMENT("Reflects whether to forward coredumps for processes that crash within this cgroup"), @@ -436,9 +621,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#BindLogSockets="), SD_VARLINK_DEFINE_FIELD(BindLogSockets, SD_VARLINK_BOOL, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectProc="), - SD_VARLINK_DEFINE_FIELD(ProtectProc, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectProc, ProtectProc, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProcSubset="), - SD_VARLINK_DEFINE_FIELD(ProcSubset, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProcSubset, ProcSubset, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#BindPaths="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(BindPaths, BindPath, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#BindPaths="), @@ -497,7 +682,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#CoredumpFilter="), SD_VARLINK_DEFINE_FIELD(CoredumpFilter, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#KeyringMode="), - SD_VARLINK_DEFINE_FIELD(KeyringMode, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(KeyringMode, ExecKeyringMode, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#OOMScoreAdjust="), SD_VARLINK_DEFINE_FIELD(OOMScoreAdjust, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#TimerSlackNSec="), @@ -512,7 +697,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#Nice="), SD_VARLINK_DEFINE_FIELD(Nice, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#CPUSchedulingPolicy="), - SD_VARLINK_DEFINE_FIELD(CPUSchedulingPolicy, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CPUSchedulingPolicy, CPUSchedulingPolicy, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#CPUSchedulingPriority="), SD_VARLINK_DEFINE_FIELD(CPUSchedulingPriority, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#CPUSchedulingResetOnFork="), @@ -520,25 +705,25 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#CPUAffinity="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(CPUAffinity, CPUAffinity, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#NUMAPolicy="), - SD_VARLINK_DEFINE_FIELD(NUMAPolicy, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(NUMAPolicy, NUMAPolicy, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#NUMAMask="), SD_VARLINK_DEFINE_FIELD(NUMAMask, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#IOSchedulingClass="), - SD_VARLINK_DEFINE_FIELD(IOSchedulingClass, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(IOSchedulingClass, IOSchedulingClass, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#IOSchedulingPriority="), SD_VARLINK_DEFINE_FIELD(IOSchedulingPriority, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#MemoryKSM="), SD_VARLINK_DEFINE_FIELD(MemoryKSM, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#MemoryTHP="), - SD_VARLINK_DEFINE_FIELD(MemoryTHP, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(MemoryTHP, MemoryTHP, 0), /* Sandboxing * https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#Sandboxing */ SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectSystem="), - SD_VARLINK_DEFINE_FIELD(ProtectSystem, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectSystem, ProtectSystem, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectHome="), - SD_VARLINK_DEFINE_FIELD(ProtectHome, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectHome, ProtectHome, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RuntimeDirectory="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(RuntimeDirectory, ExecDirectory, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RuntimeDirectory="), @@ -550,7 +735,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RuntimeDirectory="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(ConfigurationDirectory, ExecDirectory, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RuntimeDirectoryPreserve="), - SD_VARLINK_DEFINE_FIELD(RuntimeDirectoryPreserve, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(RuntimeDirectoryPreserve, ExecPreserveMode, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#TimeoutCleanSec="), SD_VARLINK_DEFINE_FIELD(TimeoutCleanUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ReadWritePaths="), @@ -566,7 +751,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#TemporaryFileSystem="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(TemporaryFileSystem, TemporaryFilesystem, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateTmp="), - SD_VARLINK_DEFINE_FIELD(PrivateTmp, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PrivateTmp, PrivateTmp, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateDevices="), SD_VARLINK_DEFINE_FIELD(PrivateDevices, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateNetwork="), @@ -578,13 +763,13 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#IPCNamespacePath="), SD_VARLINK_DEFINE_FIELD(IPCNamespacePath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivatePIDs="), - SD_VARLINK_DEFINE_FIELD(PrivatePIDs, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PrivatePIDs, PrivatePIDs, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateUsers="), - SD_VARLINK_DEFINE_FIELD(PrivateUsers, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PrivateUsers, PrivateUsers, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#UserNamespacePath="), SD_VARLINK_DEFINE_FIELD(UserNamespacePath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectHostname="), - SD_VARLINK_DEFINE_FIELD(ProtectHostname, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectHostname, ProtectHostname, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectClock="), SD_VARLINK_DEFINE_FIELD(ProtectClock, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectKernelTunables="), @@ -594,7 +779,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectKernelLogs="), SD_VARLINK_DEFINE_FIELD(ProtectKernelLogs, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectControlGroups="), - SD_VARLINK_DEFINE_FIELD(ProtectControlGroups, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectControlGroups, ProtectControlGroups, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RestrictAddressFamilies="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(RestrictAddressFamilies, AddressFamilyList, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RestrictFileSystems="), @@ -604,7 +789,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#DelegateNamespaces="), SD_VARLINK_DEFINE_FIELD(DelegateNamespaces, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivatePBF="), - SD_VARLINK_DEFINE_FIELD(PrivatePBF, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PrivatePBF, PrivateBPF, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#BPFDelegateCommands="), SD_VARLINK_DEFINE_FIELD(BPFDelegateCommands, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#BPFDelegateMaps="), @@ -626,7 +811,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateMounts="), SD_VARLINK_DEFINE_FIELD(PrivateMounts, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#MountFlags="), - SD_VARLINK_DEFINE_FIELD(MountFlags, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(MountFlags, MountPropagationFlag, SD_VARLINK_NULLABLE), /* System Call Filtering * https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#System%20Call%20Filtering */ @@ -653,11 +838,11 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( /* Logging and Standard Input/Output * https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#Logging%20and%20Standard%20Input/Output */ SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#StandardInput="), - SD_VARLINK_DEFINE_FIELD(StandardInput, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(StandardInput, ExecInputType, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#StandardOutput="), - SD_VARLINK_DEFINE_FIELD(StandardOutput, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(StandardOutput, ExecOutputType, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#StandardError="), - SD_VARLINK_DEFINE_FIELD(StandardError, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(StandardError, ExecOutputType, 0), SD_VARLINK_FIELD_COMMENT("The file descriptor name to connect standard input to"), SD_VARLINK_DEFINE_FIELD(StandardInputFileDescriptorName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The file descriptor name to connect standard output to"), @@ -715,7 +900,77 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#UtmpIdentifier="), SD_VARLINK_DEFINE_FIELD(UtmpIdentifier, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#UtmpMode="), - SD_VARLINK_DEFINE_FIELD(UtmpMode, SD_VARLINK_STRING, 0)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(UtmpMode, ExecUtmpMode, 0)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + KillMode, + SD_VARLINK_DEFINE_ENUM_VALUE(control_group), + SD_VARLINK_DEFINE_ENUM_VALUE(process), + SD_VARLINK_DEFINE_ENUM_VALUE(mixed), + SD_VARLINK_DEFINE_ENUM_VALUE(none)); + +/* KillContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html */ +static SD_VARLINK_DEFINE_STRUCT_TYPE( + KillContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.kill.html#KillMode="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(KillMode, KillMode, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.kill.html#KillSignal="), + SD_VARLINK_DEFINE_FIELD(KillSignal, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.kill.html#RestartKillSignal="), + SD_VARLINK_DEFINE_FIELD(RestartKillSignal, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.kill.html#SendSIGHUP="), + SD_VARLINK_DEFINE_FIELD(SendSIGHUP, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.kill.html#SendSIGKILL="), + SD_VARLINK_DEFINE_FIELD(SendSIGKILL, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.kill.html#FinalKillSignal="), + SD_VARLINK_DEFINE_FIELD(FinalKillSignal, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.kill.html#WatchdogSignal="), + SD_VARLINK_DEFINE_FIELD(WatchdogSignal, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +/* AutomountContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.automount.html */ +static SD_VARLINK_DEFINE_STRUCT_TYPE( + AutomountContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.automount.html#Where="), + SD_VARLINK_DEFINE_FIELD(Where, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.automount.html#ExtraOptions="), + SD_VARLINK_DEFINE_FIELD(ExtraOptions, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.automount.html#DirectoryMode="), + SD_VARLINK_DEFINE_FIELD(DirectoryMode, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.automount.html#TimeoutIdleSec="), + SD_VARLINK_DEFINE_FIELD(TimeoutIdleUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +/* MountContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.mount.html */ +static SD_VARLINK_DEFINE_STRUCT_TYPE( + MountContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#What="), + SD_VARLINK_DEFINE_FIELD(What, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#Where="), + SD_VARLINK_DEFINE_FIELD(Where, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#Type="), + SD_VARLINK_DEFINE_FIELD(Type, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#Options="), + SD_VARLINK_DEFINE_FIELD(Options, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#SloppyOptions="), + SD_VARLINK_DEFINE_FIELD(SloppyOptions, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#LazyUnmount="), + SD_VARLINK_DEFINE_FIELD(LazyUnmount, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#ReadWriteOnly="), + SD_VARLINK_DEFINE_FIELD(ReadWriteOnly, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#ForceUnmount="), + SD_VARLINK_DEFINE_FIELD(ForceUnmount, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#DirectoryMode="), + SD_VARLINK_DEFINE_FIELD(DirectoryMode, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#TimeoutSec="), + SD_VARLINK_DEFINE_FIELD(TimeoutUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Mount command"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecMount, ExecCommand, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Unmount command"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecUnmount, ExecCommand, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Remount command"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecRemount, ExecCommand, SD_VARLINK_NULLABLE)); /* UnitContext */ static SD_VARLINK_DEFINE_STRUCT_TYPE( @@ -799,9 +1054,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#WantsMountsFor="), SD_VARLINK_DEFINE_FIELD(WantsMountsFor, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#OnSuccessJobMode="), - SD_VARLINK_DEFINE_FIELD(OnSuccessJobMode, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(OnSuccessJobMode, JobMode, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#OnSuccessJobMode="), - SD_VARLINK_DEFINE_FIELD(OnFailureJobMode, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(OnFailureJobMode, JobMode, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#IgnoreOnIsolate="), SD_VARLINK_DEFINE_FIELD(IgnoreOnIsolate, SD_VARLINK_BOOL, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#StopWhenUnneeded="), @@ -817,11 +1072,11 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#SurviveFinalKillSignal="), SD_VARLINK_DEFINE_FIELD(SurviveFinalKillSignal, SD_VARLINK_BOOL, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#CollectMode="), - SD_VARLINK_DEFINE_FIELD(CollectMode, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CollectMode, CollectMode, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#FailureAction="), - SD_VARLINK_DEFINE_FIELD(FailureAction, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(FailureAction, EmergencyAction, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#FailureAction="), - SD_VARLINK_DEFINE_FIELD(SuccessAction, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(SuccessAction, EmergencyAction, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#FailureActionExitStatus="), SD_VARLINK_DEFINE_FIELD(FailureActionExitStatus, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#FailureActionExitStatus="), @@ -831,13 +1086,13 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#JobTimeoutSec="), SD_VARLINK_DEFINE_FIELD(JobRunningTimeoutUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#JobTimeoutAction="), - SD_VARLINK_DEFINE_FIELD(JobTimeoutAction, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(JobTimeoutAction, EmergencyAction, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#JobTimeoutAction="), SD_VARLINK_DEFINE_FIELD(JobTimeoutRebootArgument, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#StartLimitIntervalSec=interval"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(StartLimit, RateLimit, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#StartLimitIntervalSec=interval"), - SD_VARLINK_DEFINE_FIELD(StartLimitAction, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(StartLimitAction, EmergencyAction, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#RebootArgument="), SD_VARLINK_DEFINE_FIELD(RebootArgument, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#SourcePath="), @@ -874,7 +1129,13 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The cgroup context of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(CGroup, CGroupContext, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The exec context of the unit"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(Exec, ExecContext, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Exec, ExecContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The kill context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Kill, KillContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The automount context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Automount, AutomountContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The mount context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Mount, MountContext, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_STRUCT_TYPE( ActivationDetails, @@ -948,6 +1209,45 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The number of processes of this unit killed by systemd-oomd"), SD_VARLINK_DEFINE_FIELD(ManagedOOMKills, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); +SD_VARLINK_DEFINE_ENUM_TYPE( + AutomountResult, + SD_VARLINK_DEFINE_ENUM_VALUE(success), + SD_VARLINK_DEFINE_ENUM_VALUE(resources), + SD_VARLINK_DEFINE_ENUM_VALUE(start_limit_hit), + SD_VARLINK_DEFINE_ENUM_VALUE(mount_start_limit_hit), + SD_VARLINK_DEFINE_ENUM_VALUE(unmounted)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + AutomountRuntime, + SD_VARLINK_FIELD_COMMENT("Result of automount operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, AutomountResult, 0)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + MountResult, + SD_VARLINK_DEFINE_ENUM_VALUE(success), + SD_VARLINK_DEFINE_ENUM_VALUE(resources), + SD_VARLINK_DEFINE_ENUM_VALUE(timeout), + SD_VARLINK_DEFINE_ENUM_VALUE(exit_code), + SD_VARLINK_DEFINE_ENUM_VALUE(signal), + SD_VARLINK_DEFINE_ENUM_VALUE(core_dump), + SD_VARLINK_DEFINE_ENUM_VALUE(start_limit_hit), + SD_VARLINK_DEFINE_ENUM_VALUE(protocol)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + MountRuntime, + SD_VARLINK_FIELD_COMMENT("PID of the current mount/remount/etc process running"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ControlPID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Result of mount operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, MountResult, 0), + SD_VARLINK_FIELD_COMMENT("Result of remount operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ReloadResult, MountResult, 0), + SD_VARLINK_FIELD_COMMENT("Result of cleaning operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CleanResult, MountResult, 0), + SD_VARLINK_FIELD_COMMENT("Reference UID"), + SD_VARLINK_DEFINE_FIELD(UID, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Reference GID"), + SD_VARLINK_DEFINE_FIELD(GID, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + static SD_VARLINK_DEFINE_STRUCT_TYPE( UnitRuntime, SD_VARLINK_FIELD_COMMENT("If not empty, the field contains the name of another unit that this unit follows in state"), @@ -1005,7 +1305,11 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Provides details about why a unit was activated"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(ActivationDetails, ActivationDetails, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The cgroup runtime of the unit"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(CGroup, CGroupRuntime, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CGroup, CGroupRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The automount runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Automount, AutomountRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The mount runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Mount, MountRuntime, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD_FULL( List, @@ -1066,6 +1370,10 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_ProcessId, /* CGroupContext */ + &vl_type_CGroupDevicePolicy, + &vl_type_ManagedOOMMode, + &vl_type_ManagedOOMPreference, + &vl_type_CGroupPressureWatch, &vl_type_CGroupTasksMax, &vl_type_CGroupIODeviceWeight, &vl_type_CGroupIODeviceLimit, @@ -1075,6 +1383,7 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_CGroupRestrictNetworkInterfaces, &vl_type_CGroupNFTSet, &vl_type_CGroupBPFProgram, + &vl_type_CGroupController, &vl_type_CGroupDeviceAllow, SD_VARLINK_SYMBOL_COMMENT("CGroup context of a unit"), &vl_type_CGroupContext, @@ -1082,6 +1391,26 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_CGroupRuntime, /* ExecContext */ + &vl_type_ExecInputType, + &vl_type_ExecOutputType, + &vl_type_ExecUtmpMode, + &vl_type_ExecPreserveMode, + &vl_type_ExecKeyringMode, + &vl_type_MemoryTHP, + &vl_type_ProtectProc, + &vl_type_ProcSubset, + &vl_type_ProtectSystem, + &vl_type_ProtectHome, + &vl_type_PrivateTmp, + &vl_type_PrivateUsers, + &vl_type_ProtectHostname, + &vl_type_ProtectControlGroups, + &vl_type_PrivatePIDs, + &vl_type_PrivateBPF, + &vl_type_CPUSchedulingPolicy, + &vl_type_IOSchedulingClass, + &vl_type_NUMAPolicy, + &vl_type_MountPropagationFlag, &vl_type_WorkingDirectory, &vl_type_PartitionMountOptions, &vl_type_BindPath, @@ -1108,6 +1437,22 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_SYMBOL_COMMENT("Exec context of a unit"), &vl_type_ExecContext, + /* other contexts */ + &vl_type_KillMode, + &vl_type_KillContext, + &vl_type_AutomountContext, + &vl_type_AutomountResult, + &vl_type_AutomountRuntime, + &vl_type_ExecCommand, + &vl_type_MountContext, + &vl_type_MountResult, + &vl_type_MountRuntime, + + /* UnitContext enums */ + &vl_type_CollectMode, + &vl_type_EmergencyAction, + &vl_type_JobMode, + /* Errors */ SD_VARLINK_SYMBOL_COMMENT("No matching unit found"), &vl_error_NoSuchUnit, diff --git a/src/shared/varlink-io.systemd.Unit.h b/src/shared/varlink-io.systemd.Unit.h index 25433294534f9..a39407133844c 100644 --- a/src/shared/varlink-io.systemd.Unit.h +++ b/src/shared/varlink-io.systemd.Unit.h @@ -4,3 +4,31 @@ #include "sd-varlink-idl.h" extern const sd_varlink_interface vl_interface_io_systemd_Unit; + +extern const sd_varlink_symbol vl_type_ExecInputType; +extern const sd_varlink_symbol vl_type_ExecUtmpMode; +extern const sd_varlink_symbol vl_type_ExecPreserveMode; +extern const sd_varlink_symbol vl_type_ExecKeyringMode; +extern const sd_varlink_symbol vl_type_MemoryTHP; +extern const sd_varlink_symbol vl_type_ProtectProc; +extern const sd_varlink_symbol vl_type_ProcSubset; +extern const sd_varlink_symbol vl_type_ProtectSystem; +extern const sd_varlink_symbol vl_type_ProtectHome; +extern const sd_varlink_symbol vl_type_PrivateTmp; +extern const sd_varlink_symbol vl_type_PrivateUsers; +extern const sd_varlink_symbol vl_type_ProtectHostname; +extern const sd_varlink_symbol vl_type_ProtectControlGroups; +extern const sd_varlink_symbol vl_type_PrivatePIDs; +extern const sd_varlink_symbol vl_type_PrivateBPF; +extern const sd_varlink_symbol vl_type_CGroupDevicePolicy; +extern const sd_varlink_symbol vl_type_ManagedOOMPreference; +extern const sd_varlink_symbol vl_type_CGroupController; +extern const sd_varlink_symbol vl_type_CPUSchedulingPolicy; +extern const sd_varlink_symbol vl_type_IOSchedulingClass; +extern const sd_varlink_symbol vl_type_NUMAPolicy; +extern const sd_varlink_symbol vl_type_MountPropagationFlag; +extern const sd_varlink_symbol vl_type_KillMode; +extern const sd_varlink_symbol vl_type_AutomountResult; +extern const sd_varlink_symbol vl_type_MountResult; +extern const sd_varlink_symbol vl_type_CollectMode; +extern const sd_varlink_symbol vl_type_JobMode; diff --git a/src/shared/varlink-io.systemd.VirtualMachineInstance.c b/src/shared/varlink-io.systemd.VirtualMachineInstance.c new file mode 100644 index 0000000000000..f491418a2c6a6 --- /dev/null +++ b/src/shared/varlink-io.systemd.VirtualMachineInstance.c @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.VirtualMachineInstance.h" + +/* VM-specific control interface. Currently empty — reserved for methods that apply to virtual + * machines generically but not to containers (e.g. snapshot, migration, device hotplug). */ +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_VirtualMachineInstance, + "io.systemd.VirtualMachineInstance"); diff --git a/src/shared/varlink-io.systemd.VirtualMachineInstance.h b/src/shared/varlink-io.systemd.VirtualMachineInstance.h new file mode 100644 index 0000000000000..b6d0600f34aee --- /dev/null +++ b/src/shared/varlink-io.systemd.VirtualMachineInstance.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_interface vl_interface_io_systemd_VirtualMachineInstance; diff --git a/src/shared/varlink-io.systemd.oom.c b/src/shared/varlink-io.systemd.oom.c index 350b933d03d79..80fa50a73a92c 100644 --- a/src/shared/varlink-io.systemd.oom.c +++ b/src/shared/varlink-io.systemd.oom.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "varlink-idl-common.h" #include "varlink-io.systemd.oom.h" /* This is oomd's Varlink service, where oomd is server and systemd --user is the client. @@ -9,7 +10,7 @@ SD_VARLINK_DEFINE_STRUCT_TYPE( ControlGroup, - SD_VARLINK_DEFINE_FIELD(mode, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(mode, ManagedOOMMode, 0), SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, 0), SD_VARLINK_DEFINE_FIELD(property, SD_VARLINK_STRING, 0), SD_VARLINK_DEFINE_FIELD(limit, SD_VARLINK_INT, SD_VARLINK_NULLABLE), @@ -22,5 +23,6 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_DEFINE_INTERFACE( io_systemd_oom, "io.systemd.oom", + &vl_type_ManagedOOMMode, &vl_method_ReportManagedOOMCGroups, &vl_type_ControlGroup); diff --git a/src/shared/vconsole-util.c b/src/shared/vconsole-util.c index cd623bebdbbb6..6e8c17561e8a7 100644 --- a/src/shared/vconsole-util.c +++ b/src/shared/vconsole-util.c @@ -87,10 +87,10 @@ bool x11_context_is_safe(const X11Context *xc) { assert(xc); return - (!xc->layout || string_is_safe(xc->layout)) && - (!xc->model || string_is_safe(xc->model)) && - (!xc->variant || string_is_safe(xc->variant)) && - (!xc->options || string_is_safe(xc->options)); + (!xc->layout || string_is_safe(xc->layout, /* flags= */ 0)) && + (!xc->model || string_is_safe(xc->model, /* flags= */ 0)) && + (!xc->variant || string_is_safe(xc->variant, /* flags= */ 0)) && + (!xc->options || string_is_safe(xc->options, /* flags= */ 0)); } bool x11_context_equal(const X11Context *a, const X11Context *b) { diff --git a/src/shared/verbs.c b/src/shared/verbs.c index b7b78619fdede..dfecf048612b7 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -3,6 +3,7 @@ #include #include "env-util.h" +#include "format-table.h" #include "log.h" #include "string-util.h" #include "strv.h" @@ -57,40 +58,46 @@ bool should_bypass(const char *env_prefix) { return true; } -const Verb* verbs_find_verb(const char *name, const Verb verbs[]) { +static bool verb_is_metadata(const Verb *verb) { + /* A metadata entry that is not a real verb, like the group marker */ + return FLAGS_SET(ASSERT_PTR(verb)->flags, VERB_GROUP_MARKER); +} + +const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]) { assert(verbs); - for (size_t i = 0; verbs[i].dispatch; i++) - if (name ? streq(name, verbs[i].verb) : FLAGS_SET(verbs[i].flags, VERB_DEFAULT)) - return verbs + i; + for (const Verb *verb = verbs; verb < verbs_end; verb++) { + if (verb_is_metadata(verb)) + continue; + + if (name ? streq(name, verb->verb) : FLAGS_SET(verb->flags, VERB_DEFAULT)) + return verb; + } /* At the end of the list? */ return NULL; } -int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { - const Verb *verb; - const char *name; - int r, left; +int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_end[], void *userdata) { + int r; assert(verbs); + assert(verbs_end > verbs); assert(verbs[0].dispatch); assert(verbs[0].verb); - assert(argc >= 0); - assert(argv); - assert(argc >= optind); - left = argc - optind; - argv += optind; - optind = 0; - name = argv[0]; + const char *name = args ? args[0] : NULL; + size_t left = strv_length(args); - verb = verbs_find_verb(name, verbs); + const Verb *verb = verbs_find_verb(name, verbs, verbs_end); if (!verb) { _cleanup_strv_free_ char **verb_strv = NULL; - for (size_t i = 0; verbs[i].dispatch; i++) { - r = strv_extend(&verb_strv, verbs[i].verb); + for (verb = verbs; verb < verbs_end; verb++) { + if (verb_is_metadata(verb)) + continue; + + r = strv_extend(&verb_strv, verb->verb); if (r < 0) return log_oom(); } @@ -120,12 +127,10 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { if (!name) left = 1; - if (verb->min_args != VERB_ANY && - (unsigned) left < verb->min_args) + if (verb->min_args != VERB_ANY && left < verb->min_args) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments."); - if (verb->max_args != VERB_ANY && - (unsigned) left > verb->max_args) + if (verb->max_args != VERB_ANY && left > verb->max_args) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments."); if ((verb->flags & VERB_ONLINE_ONLY) && running_in_chroot_or_offline()) { @@ -134,7 +139,79 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { } if (!name) - return verb->dispatch(1, STRV_MAKE(verb->verb), userdata); + return verb->dispatch(1, STRV_MAKE(verb->verb), verb->data, userdata); + + assert(left < INT_MAX); /* args are derived from argc+argv, so their size must fit in an int. */ + return verb->dispatch(left, args, verb->data, userdata); +} + +int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { + /* getopt wrapper for _dispatch_verb_with_args. + * TBD: remove this function when all programs with verbs have been converted. */ + + assert(argc >= 0); + assert(argv); + assert(argc >= optind); + + size_t n = 0; + while (verbs[n].verb) + n++; + + return _dispatch_verb_with_args(strv_skip(argv, optind), verbs, verbs + n, userdata); +} + +int _verbs_get_help_table( + const Verb verbs[], + const Verb verbs_end[], + const char *group, + Table **ret) { + int r; + + assert(ret); + + _cleanup_(table_unrefp) Table *table = table_new("verb", "help"); + if (!table) + return log_oom(); + + bool in_group = group == NULL; /* Are we currently in the section on the array that forms + * group ? The first part is the default group, so + * if the group was not specified, we are in. */ + + for (const Verb *verb = verbs; verb < verbs_end; verb++) { + assert(verb->verb); + + bool group_marker = FLAGS_SET(verb->flags, VERB_GROUP_MARKER); + if (!in_group) { + in_group = group_marker && streq(group, verb->verb); + continue; + } + if (group_marker) + break; /* End of group */ + + if (!verb->help) + /* No help string — we do not show the verb */ + continue; + + /* We indent the option string by two spaces. We could set the minimum cell width and + * right-align for a similar result, but that'd be more work. This is only used for + * display. */ + r = table_add_cell_stringf(table, NULL, " %s%s%s", + verb->verb, + verb->argspec ? " " : "", + strempty(verb->argspec)); + if (r < 0) + return table_log_add_error(r); + + _cleanup_strv_free_ char **s = strv_split(verb->help, /* separators= */ NULL); + if (!s) + return log_oom(); + + r = table_add_many(table, TABLE_STRV_WRAPPED, s); + if (r < 0) + return table_log_add_error(r); + } - return verb->dispatch(left, argv, userdata); + table_set_header(table, false); + *ret = TAKE_PTR(table); + return 0; } diff --git a/src/shared/verbs.h b/src/shared/verbs.h index 0fb6621af600d..4fb0486f7567d 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -8,18 +8,100 @@ typedef enum VerbFlags { VERB_DEFAULT = 1 << 0, /* The verb to run if no verb is specified */ VERB_ONLINE_ONLY = 1 << 1, /* Just do nothing when running in chroot or offline */ + VERB_GROUP_MARKER = 1 << 2, /* Fake verb entry to separate groups */ } VerbFlags; typedef struct { const char *verb; unsigned min_args, max_args; VerbFlags flags; - int (* const dispatch)(int argc, char *argv[], void *userdata); + int (* const dispatch)(int argc, char *argv[], uintptr_t data, void *userdata); + uintptr_t data; + const char *argspec; + const char *help; } Verb; +#define _VERB_DATA(d, v, a, amin, amax, f, dat, h) \ + _section_("SYSTEMD_VERBS") \ + _alignptr_ \ + _used_ \ + _retain_ \ + _no_reorder_ \ + _variable_no_sanitize_address_ \ + static const Verb CONCATENATE(verb_data_, __COUNTER__) = { \ + .verb = v, \ + .min_args = amin, \ + .max_args = amax, \ + .flags = f, \ + .dispatch = d, \ + .data = dat, \ + .argspec = a, \ + .help = h, \ + } + +/* Forward-define function d. scope specifies the scope, e.g. static. */ +#define VERB_SCOPE_FULL(scope, d, v, a, amin, amax, f, dat, h) \ + DISABLE_WARNING_REDUNDANT_DECLS \ + scope int d(int, char**, uintptr_t, void*); \ + REENABLE_WARNING \ + _VERB_DATA(d, v, a, amin, amax, f, dat, h) +/* The same as VERB_SCOPE_FULL with scope hardwired to 'static'. */ +#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \ + VERB_SCOPE_FULL(static, d, v, a, amin, amax, f, dat, h) + +/* The same as VERB_SCOPE_FULL/VERB_FULL, but without the data argument. */ +#define VERB_SCOPE(scope, d, v, a, amin, amax, f, h) \ + VERB_SCOPE_FULL(scope, d, v, a, amin, amax, f, /* dat= */ 0, h) +#define VERB(d, v, a, amin, amax, f, h) \ + VERB_SCOPE(static, d, v, a, amin, amax, f, h) + +/* Simplified VERB_SCOPE/VERB for verbs that take no argument. */ +#define VERB_SCOPE_NOARG(scope, d, v, h) \ + VERB_SCOPE(scope, d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ 0, h) +#define VERB_NOARG(d, v, h) \ + VERB_SCOPE_NOARG(static, d, v, h) + +/* Magic entry in the table (which will not be returned) that designates the start of the group . + * The macro works as a separator between groups and must be between other VERB* stanzas. */ +#define VERB_GROUP(gr) \ + _VERB_DATA(/* d= */ NULL, /* v= */ gr, /* a= */ NULL, /* amin= */ 0, /* amax= */ 0, \ + /* f= */ VERB_GROUP_MARKER, /* dat= */ 0, /* h= */ NULL) + +/* This is magically mapped to the beginning and end of the section */ +extern const Verb __start_SYSTEMD_VERBS[]; +extern const Verb __stop_SYSTEMD_VERBS[]; + bool running_in_chroot_or_offline(void); bool should_bypass(const char *env_prefix); -const Verb* verbs_find_verb(const char *name, const Verb verbs[]); +const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]); + +int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_end[], void *userdata); +#define dispatch_verb_with_args(args, userdata) \ + _dispatch_verb_with_args(args, ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, userdata) + int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata); + +int _verbs_get_help_table( + const Verb verbs[], + const Verb verbs_end[], + const char *group, + Table **ret); +#define verbs_get_help_table_group(group, ret) \ + _verbs_get_help_table(ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, group, ret) +#define verbs_get_help_table(ret) \ + verbs_get_help_table_group(/* group= */ NULL, ret) + +#define _VERB_COMMON_HELP_IMPL(impl) \ + static int verb_help(int argc, char **argv, uintptr_t data, void *userdata) { \ + return impl(); \ + } + +#define VERB_COMMON_HELP(impl) \ + VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, "Show this help"); \ + _VERB_COMMON_HELP_IMPL(impl) + +#define VERB_COMMON_HELP_HIDDEN(impl) \ + VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, NULL); \ + _VERB_COMMON_HELP_IMPL(impl) diff --git a/src/shared/vlan-util.c b/src/shared/vlan-util.c index 2343acb346c77..cf931dfae7efa 100644 --- a/src/shared/vlan-util.c +++ b/src/shared/vlan-util.c @@ -26,6 +26,9 @@ int parse_vid_range(const char *p, uint16_t *vid, uint16_t *vid_end) { unsigned lower, upper; int r; + assert(vid); + assert(vid_end); + r = parse_range(p, &lower, &upper); if (r < 0) return r; diff --git a/src/shared/volatile-util.c b/src/shared/volatile-util.c index eaf53ac4ad18a..de498b7b885c2 100644 --- a/src/shared/volatile-util.c +++ b/src/shared/volatile-util.c @@ -9,6 +9,8 @@ int query_volatile_mode(VolatileMode *ret) { _cleanup_free_ char *mode = NULL; int r; + assert(ret); + r = proc_cmdline_get_key("systemd.volatile", PROC_CMDLINE_VALUE_OPTIONAL, &mode); if (r < 0) return r; diff --git a/src/shared/vpick.c b/src/shared/vpick.c index 38ceb225cd5ae..b68991cda19b6 100644 --- a/src/shared/vpick.c +++ b/src/shared/vpick.c @@ -197,8 +197,9 @@ static int pin_choice( if (inode_fd < 0 || FLAGS_SET(flags, PICK_RESOLVE)) { r = chaseat(toplevel_fd, + toplevel_fd, inode_path, - CHASE_AT_RESOLVE_IN_ROOT, + /* flags= */ 0, FLAGS_SET(flags, PICK_RESOLVE) ? &resolved_path : NULL, inode_fd < 0 ? &inode_fd : NULL); if (r < 0) @@ -327,7 +328,7 @@ static int make_choice( assert(ret); if (inode_fd < 0) { - r = chaseat(toplevel_fd, inode_path, CHASE_AT_RESOLVE_IN_ROOT, NULL, &inode_fd); + r = chaseat(toplevel_fd, toplevel_fd, inode_path, /* flags= */ 0, NULL, &inode_fd); if (r < 0) return r; } @@ -344,7 +345,7 @@ static int make_choice( return log_oom_debug(); _cleanup_close_ int object_fd = -EBADF; - r = chaseat(toplevel_fd, p, CHASE_AT_RESOLVE_IN_ROOT, &object_path, &object_fd); + r = chaseat(toplevel_fd, toplevel_fd, p, /* flags= */ 0, &object_path, &object_fd); if (r == -ENOENT) { *ret = PICK_RESULT_NULL; return 0; diff --git a/src/shared/watchdog.c b/src/shared/watchdog.c index 5b113013950f7..1c95a66f46999 100644 --- a/src/shared/watchdog.c +++ b/src/shared/watchdog.c @@ -17,6 +17,7 @@ #include "log.h" #include "path-util.h" #include "ratelimit.h" +#include "stat-util.h" #include "string-util.h" #include "strv.h" #include "time-util.h" @@ -55,6 +56,7 @@ static int saturated_usec_to_sec(usec_t val) { static int watchdog_get_sysfs_path(const char *filename, char **ret_path) { struct stat st; + int r; if (watchdog_fd < 0) return -EBADF; @@ -62,8 +64,9 @@ static int watchdog_get_sysfs_path(const char *filename, char **ret_path) { if (fstat(watchdog_fd, &st)) return -errno; - if (!S_ISCHR(st.st_mode)) - return -EBADF; + r = stat_verify_char(&st); + if (r < 0) + return r; if (asprintf(ret_path, "/sys/dev/char/"DEVNUM_FORMAT_STR"/%s", DEVNUM_FORMAT_VAL(st.st_rdev), filename) < 0) return -ENOMEM; @@ -75,6 +78,8 @@ static int watchdog_get_pretimeout_governor(char **ret_gov) { _cleanup_free_ char *sys_fn = NULL; int r; + assert(ret_gov); + r = watchdog_get_sysfs_path("pretimeout_governor", &sys_fn); if (r < 0) return r; diff --git a/src/shared/web-util.c b/src/shared/web-util.c index 628f0805bd3f8..bfcea982a9dfc 100644 --- a/src/shared/web-util.c +++ b/src/shared/web-util.c @@ -5,19 +5,6 @@ #include "utf8.h" #include "web-util.h" -bool http_etag_is_valid(const char *etag) { - if (isempty(etag)) - return false; - - if (!endswith(etag, "\"")) - return false; - - if (!STARTSWITH_SET(etag, "\"", "W/\"")) - return false; - - return true; -} - bool http_url_is_valid(const char *url) { const char *p; @@ -62,3 +49,23 @@ bool documentation_url_is_valid(const char *url) { return ascii_is_valid(p); } + +bool http_header_valid(const char *header) { + return header && + ascii_is_valid(header) && + !string_has_cc(header, /* ok= */ NULL) && + strchr(header, ':'); +} + +bool http_etag_is_valid(const char *etag) { + if (isempty(etag)) + return false; + + if (!endswith(etag, "\"")) + return false; + + if (!STARTSWITH_SET(etag, "\"", "W/\"")) + return false; + + return true; +} diff --git a/src/shared/web-util.h b/src/shared/web-util.h index 68f868ab0a24e..ec154c107aebc 100644 --- a/src/shared/web-util.h +++ b/src/shared/web-util.h @@ -5,7 +5,6 @@ bool http_url_is_valid(const char *url) _pure_; bool file_url_is_valid(const char *url) _pure_; - bool documentation_url_is_valid(const char *url) _pure_; - -bool http_etag_is_valid(const char *etag); +bool http_header_valid(const char *header) _pure_; +bool http_etag_is_valid(const char *etag) _pure_; diff --git a/src/shutdown/detach-swap.c b/src/shutdown/detach-swap.c index f064473ef77a2..04efbfeb36d54 100644 --- a/src/shutdown/detach-swap.c +++ b/src/shutdown/detach-swap.c @@ -36,9 +36,9 @@ int swap_list_get(const char *swaps, SwapDevice **head) { assert(head); - r = dlopen_libmount(); + r = dlopen_libmount(LOG_ERR); if (r < 0) - return log_error_errno(r, "Cannot enumerate swap partitions, no libmount support."); + return r; t = sym_mnt_new_table(); i = sym_mnt_new_iter(MNT_ITER_FORWARD); diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index fc6df238bed9e..5e6ff9f68f591 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -3,7 +3,6 @@ Copyright © 2010 ProFUSION embedded systems ***/ -#include #include #include #include @@ -29,10 +28,10 @@ #include "fd-util.h" #include "fileio.h" #include "format-util.h" -#include "getopt-defs.h" #include "initrd-util.h" #include "killall.h" #include "log.h" +#include "options.h" #include "parse-util.h" #include "pidref.h" #include "printk-util.h" @@ -51,109 +50,95 @@ #define SYNC_PROGRESS_ATTEMPTS 3 #define SYNC_TIMEOUT_USEC (10*USEC_PER_SEC) +#define DEFAULT_MINIMUM_UPTIME_USEC (15U * USEC_PER_SEC) static const char *arg_verb = NULL; static uint8_t arg_exit_code = 0; static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC; static int parse_argv(int argc, char *argv[]) { - enum { - COMMON_GETOPT_ARGS, - SHUTDOWN_GETOPT_ARGS, - }; - - static const struct option options[] = { - COMMON_GETOPT_OPTIONS, - SHUTDOWN_GETOPT_OPTIONS, - {} - }; - - int c, r; - assert(argc >= 1); assert(argv); - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; + /* The interface is: the verb must stay in argv[1]. Any extra positional arguments + * are warned about and ignored. See 4b5d8d0f22ae61ceb45a25391354ba53b43ee992. */ - /* "-" prevents getopt from permuting argv[] and moving the verb away - * from argv[1]. Our interface to initrd promises it'll be there. */ - while ((c = getopt_long(argc, argv, "-", options, NULL)) >= 0) + OptionParser state = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + const char *arg; + int r; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case ARG_LOG_LEVEL: - r = log_set_max_level_from_string(optarg); + OPTION_COMMON_LOG_LEVEL: + r = log_set_max_level_from_string(arg); if (r < 0) - log_warning_errno(r, "Failed to parse log level %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse log level %s, ignoring: %m", arg); break; - case ARG_LOG_TARGET: - r = log_set_target_from_string(optarg); + OPTION_COMMON_LOG_TARGET: + r = log_set_target_from_string(arg); if (r < 0) - log_warning_errno(r, "Failed to parse log target %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse log target %s, ignoring: %m", arg); break; - case ARG_LOG_COLOR: - - if (optarg) { - r = log_show_color_from_string(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "log-color", "BOOL", + "Highlight important messages"): + if (arg) { + r = log_show_color_from_string(arg); if (r < 0) - log_warning_errno(r, "Failed to parse log color setting %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse log color setting %s, ignoring: %m", arg); } else log_show_color(true); break; - case ARG_LOG_LOCATION: - if (optarg) { - r = log_show_location_from_string(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "log-location", "BOOL", + "Include code location in messages"): + if (arg) { + r = log_show_location_from_string(arg); if (r < 0) - log_warning_errno(r, "Failed to parse log location setting %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse log location setting %s, ignoring: %m", arg); } else log_show_location(true); break; - case ARG_LOG_TIME: - - if (optarg) { - r = log_show_time_from_string(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "log-time", "BOOL", + "Prefix messages with current time"): + if (arg) { + r = log_show_time_from_string(arg); if (r < 0) - log_warning_errno(r, "Failed to parse log time setting %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse log time setting %s, ignoring: %m", arg); } else log_show_time(true); break; - case ARG_EXIT_CODE: - r = safe_atou8(optarg, &arg_exit_code); + OPTION_LONG("exit-code", "N", + "Exit code for reboot/kexec"): + r = safe_atou8(arg, &arg_exit_code); if (r < 0) - log_warning_errno(r, "Failed to parse exit code %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse exit code %s, ignoring: %m", arg); break; - case ARG_TIMEOUT: - r = parse_sec(optarg, &arg_timeout); + OPTION_LONG("timeout", "TIME", + "Overall shutdown timeout"): + r = parse_sec(arg, &arg_timeout); if (r < 0) - log_warning_errno(r, "Failed to parse shutdown timeout %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse shutdown timeout %s, ignoring: %m", arg); break; - case '\001': + OPTION_POSITIONAL: if (!arg_verb) - arg_verb = optarg; + arg_verb = arg; else - log_warning("Got extraneous arguments, ignoring."); + log_warning("Got extraneous argument, ignoring."); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_verb) @@ -286,14 +271,30 @@ static void init_watchdog(void) { const char *s; int r; - s = getenv("WATCHDOG_DEVICE"); + /* NB: we do not insist on $WATCHDOG_PID being set because old systemd versions didn't set it at all, + * and we want to retain some basic compatibility between an old service manager and a new shutdown + * binary. If it *is* set we'll insist on it being set to 1 however. */ + s = secure_getenv("WATCHDOG_PID"); + if (s) { + pid_t pid; + + r = parse_pid(s, &pid); + if (r < 0) + log_warning_errno(r, "Failed to parse $WATCHDOG_PID, ignoring: %s", s); + else if (pid != getpid_cached()) { + log_warning("$WATCHDOG_PID set, but not to " PID_FMT ", skipping watchdog logic.", getpid_cached()); + return; + } + } + + s = secure_getenv("WATCHDOG_DEVICE"); if (s) { r = watchdog_set_device(s); if (r < 0) - log_warning_errno(r, "Failed to set watchdog device to %s, ignoring: %m", s); + log_warning_errno(r, "Failed to set watchdog device to '%s', ignoring: %m", s); } - s = getenv("WATCHDOG_USEC"); + s = secure_getenv("WATCHDOG_USEC"); if (s) { usec_t usec; @@ -327,6 +328,35 @@ static void notify_supervisor(void) { arg_exit_code, arg_verb); } +static void sleep_until_minimum_uptime(void) { + uint64_t minimum_uptime_usec = DEFAULT_MINIMUM_UPTIME_USEC; + int r; + + const char *e = secure_getenv("MINIMUM_UPTIME_USEC"); + if (e) { + r = safe_atou64(e, &minimum_uptime_usec); + if (r < 0) + log_warning_errno(r, "Failed to parse $MINIMUM_UPTIME_USEC, ignoring: %s", e); + } + + if (minimum_uptime_usec <= 0) /* turned off? */ + return; + + for (;;) { + usec_t n = now(CLOCK_BOOTTIME); + if (n >= minimum_uptime_usec) + break; + + usec_t m = minimum_uptime_usec - n; + log_notice("Delaying shutdown for %s, in order to reach minimum uptime of %s.", + FORMAT_TIMESPAN(m, USEC_PER_SEC), + FORMAT_TIMESPAN(minimum_uptime_usec, USEC_PER_SEC)); + + /* Sleep for up to 3s, then show message again, as a progress indicator. */ + usleep_safe(MIN(m, 3 * USEC_PER_SEC)); + } +} + int main(int argc, char *argv[]) { static const char* const dirs[] = { SYSTEM_SHUTDOWN_PATH, @@ -595,6 +625,12 @@ int main(int argc, char *argv[]) { notify_supervisor(); + /* Enforce the minimum uptime, but don't bother with it in containers, since – unlike on bare metal + * and VMs – the screen output isn't flushed out immediately when we reboot (as OVMF or real PC + * firmwares do) */ + if (!in_container) + sleep_until_minimum_uptime(); + if (streq(arg_verb, "exit")) { if (in_container) { log_info("Exiting container."); @@ -609,23 +645,9 @@ int main(int argc, char *argv[]) { case LINUX_REBOOT_CMD_KEXEC: if (!in_container) { - /* We cheat and exec kexec to avoid doing all its work */ log_info("Rebooting with kexec."); - r = pidref_safe_fork( - "(sd-kexec)", - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, - /* ret= */ NULL); - if (r == 0) { - /* Child */ - - (void) execl(KEXEC, KEXEC, "-e", NULL); - log_debug_errno(errno, "Failed to execute '" KEXEC "' binary, proceeding with reboot(RB_KEXEC): %m"); - - /* execv failed (kexec binary missing?), so try simply reboot(RB_KEXEC) */ - (void) reboot(cmd); - _exit(EXIT_FAILURE); - } + (void) kexec(); /* If we are still running, then the kexec can't have worked, let's fall through */ } diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index 43aaede5b023a..38b38c62f8da5 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -5,7 +5,6 @@ ***/ #include -#include #include #include #include @@ -33,21 +32,22 @@ #include "exec-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "hibernate-util.h" #include "io-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "os-util.h" #include "pretty-print.h" #include "sleep-config.h" #include "special.h" #include "strv.h" #include "time-util.h" +#include "verbs.h" #define DEFAULT_HIBERNATE_DELAY_USEC_NO_BATTERY (2 * USEC_PER_HOUR) -static SleepOperation arg_operation = _SLEEP_OPERATION_INVALID; - #if ENABLE_EFI static int determine_auto_swap(sd_device *device) { _cleanup_(sd_device_unrefp) sd_device *origin = NULL; @@ -235,15 +235,16 @@ static int lock_all_homes(void) { static int execute( const SleepConfig *sleep_config, + SleepOperation main_operation, SleepOperation operation, const char *action) { const char *arguments[] = { NULL, "pre", - /* NB: we use 'arg_operation' instead of 'operation' here, as we want to communicate the overall - * operation here, not the specific one, in case of s2h. */ - sleep_operation_to_string(arg_operation), + /* NB: we use 'main_operation' instead of 'operation' here, as we want to communicate + * the overall operation here, not the specific one, in case of s2h. */ + sleep_operation_to_string(main_operation), NULL }; static const char* const dirs[] = { @@ -325,19 +326,19 @@ static int execute( log_struct(LOG_INFO, LOG_MESSAGE_ID(SD_MESSAGE_SLEEP_START_STR), LOG_MESSAGE("Performing sleep operation '%s'...", sleep_operation_to_string(operation)), - LOG_ITEM("SLEEP=%s", sleep_operation_to_string(arg_operation))); + LOG_ITEM("SLEEP=%s", sleep_operation_to_string(main_operation))); r = write_state(state_fd, sleep_config->states[operation]); if (r < 0) log_struct_errno(LOG_ERR, r, LOG_MESSAGE_ID(SD_MESSAGE_SLEEP_STOP_STR), LOG_MESSAGE("Failed to put system to sleep. System resumed again: %m"), - LOG_ITEM("SLEEP=%s", sleep_operation_to_string(arg_operation))); + LOG_ITEM("SLEEP=%s", sleep_operation_to_string(main_operation))); else log_struct(LOG_INFO, LOG_MESSAGE_ID(SD_MESSAGE_SLEEP_STOP_STR), - LOG_MESSAGE("System returned from sleep operation '%s'.", sleep_operation_to_string(arg_operation)), - LOG_ITEM("SLEEP=%s", sleep_operation_to_string(arg_operation))); + LOG_MESSAGE("System returned from sleep operation '%s'.", sleep_operation_to_string(main_operation)), + LOG_ITEM("SLEEP=%s", sleep_operation_to_string(main_operation))); arguments[1] = "post"; (void) execute_directories( @@ -396,7 +397,7 @@ static int check_wakeup_type(void) { return false; } -static int custom_timer_suspend(const SleepConfig *sleep_config) { +static int custom_timer_suspend(const SleepConfig *sleep_config, SleepOperation main_operation) { usec_t hibernate_timestamp; int r; @@ -456,7 +457,7 @@ static int custom_timer_suspend(const SleepConfig *sleep_config) { if (timerfd_settime(tfd, 0, &ts, NULL) < 0) return log_error_errno(errno, "Error setting battery estimate timer: %m"); - r = execute(sleep_config, SLEEP_SUSPEND, NULL); + r = execute(sleep_config, main_operation, SLEEP_SUSPEND, NULL); if (r < 0) return r; @@ -506,7 +507,7 @@ static int custom_timer_suspend(const SleepConfig *sleep_config) { return 1; } -static int execute_s2h(const SleepConfig *sleep_config) { +static int execute_s2h(const SleepConfig *sleep_config, SleepOperation main_operation) { _cleanup_close_ int tfd = -EBADF; usec_t hibernate_timestamp = 0; int r; @@ -559,7 +560,7 @@ static int execute_s2h(const SleepConfig *sleep_config) { } log_debug("Attempting to suspend..."); - r = execute(sleep_config, SLEEP_SUSPEND, NULL); + r = execute(sleep_config, main_operation, SLEEP_SUSPEND, NULL); if (r < 0) return r; @@ -592,7 +593,7 @@ static int execute_s2h(const SleepConfig *sleep_config) { return 0; } } else { - r = custom_timer_suspend(sleep_config); + r = custom_timer_suspend(sleep_config, main_operation); if (r < 0) return log_debug_errno(r, "Suspend cycle with manual battery discharge rate estimation failed: %m"); if (r == 0) @@ -602,11 +603,11 @@ static int execute_s2h(const SleepConfig *sleep_config) { /* For above custom timer, if 1 is returned, system will directly hibernate */ log_debug("Attempting to hibernate"); - r = execute(sleep_config, SLEEP_HIBERNATE, NULL); + r = execute(sleep_config, main_operation, SLEEP_HIBERNATE, NULL); if (r < 0) { log_notice("Couldn't hibernate, will try to suspend again."); - r = execute(sleep_config, SLEEP_SUSPEND, "suspend-after-failed-hibernate"); + r = execute(sleep_config, main_operation, SLEEP_SUSPEND, "suspend-after-failed-hibernate"); if (r < 0) return r; } @@ -616,95 +617,63 @@ static int execute_s2h(const SleepConfig *sleep_config) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-suspend.service", "8", &link); if (r < 0) return log_oom(); - printf("%s COMMAND\n\n" - "Suspend the system, hibernate the system, or both.\n\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - "\nCommands:\n" - " suspend Suspend the system\n" - " hibernate Hibernate the system\n" - " hybrid-sleep Both hibernate and suspend the system\n" - " suspend-then-hibernate Initially suspend and then hibernate\n" - " the system after a fixed period of time or\n" - " when battery is low\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - switch (c) { - - case 'h': - return help(); - - case ARG_VERSION: - return version(); + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; - case '?': - return -EINVAL; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; - default: - assert_not_reached(); + (void) table_sync_column_widths(0, verbs, options); - } + printf("%s [OPTIONS…] COMMAND\n" + "\n%sSuspend the system, hibernate the system, or both.%s\n" + "\nCommands:\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal()); - if (argc - optind != 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Usage: %s COMMAND", - program_invocation_short_name); + r = table_print_or_warn(verbs); + if (r < 0) + return r; - arg_operation = sleep_operation_from_string(argv[optind]); - if (arg_operation < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown command '%s'.", argv[optind]); + printf("\nOptions:\n"); + r = table_print_or_warn(options); + if (r < 0) + return r; - return 1 /* work to do */; + printf("\nSee the %s for details.\n", link); + return 0; } -static int run(int argc, char *argv[]) { +VERB_FULL(verb_operate, "suspend", NULL, VERB_ANY, 1, 0, SLEEP_SUSPEND, + "Suspend the system"); +VERB_FULL(verb_operate, "hibernate", NULL, VERB_ANY, 1, 0, SLEEP_HIBERNATE, + "Hibernate the system"); +VERB_FULL(verb_operate, "hybrid-sleep", NULL, VERB_ANY, 1, 0, SLEEP_HYBRID_SLEEP, + "Both hibernate and suspend the system"); +VERB_FULL(verb_operate, "suspend-then-hibernate", NULL, VERB_ANY, 1, 0, SLEEP_SUSPEND_THEN_HIBERNATE, + "Initially suspend and then hibernate the system after a fixed period of time or when battery is low"); +static int verb_operate(int argc, char *argv[], uintptr_t data, void *userdata) { _cleanup_(unit_freezer_freep) UnitFreezer *user_slice_freezer = NULL; - _cleanup_(sleep_config_freep) SleepConfig *sleep_config = NULL; + SleepOperation operation = data; + const SleepConfig *sleep_config = ASSERT_PTR(userdata); int r; - log_setup(); + assert(0 <= operation && operation < _SLEEP_OPERATION_MAX); - r = parse_argv(argc, argv); - if (r <= 0) - return r; - - r = parse_sleep_config(&sleep_config); - if (r < 0) - return r; - - if (!sleep_config->allow[arg_operation]) + if (!sleep_config->allow[operation]) return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Sleep operation \"%s\" is disabled by configuration, refusing.", - sleep_operation_to_string(arg_operation)); + sleep_operation_to_string(operation)); /* Freeze the user sessions */ r = getenv_bool("SYSTEMD_SLEEP_FREEZE_USER_SESSIONS"); @@ -721,14 +690,14 @@ static int run(int argc, char *argv[]) { "This is not recommended, and might result in unexpected behavior, particularly\n" "in suspend-then-hibernate operations or setups with encrypted home directories."); - switch (arg_operation) { + switch (operation) { case SLEEP_SUSPEND_THEN_HIBERNATE: - r = execute_s2h(sleep_config); + r = execute_s2h(sleep_config, operation); break; case SLEEP_HYBRID_SLEEP: - r = execute(sleep_config, SLEEP_HYBRID_SLEEP, NULL); + r = execute(sleep_config, operation, SLEEP_HYBRID_SLEEP, NULL); if (r < 0) { /* If we can't hybrid sleep, then let's try to suspend at least. After all, the user * asked us to do both: suspend + hibernate, and it's almost certainly the @@ -736,14 +705,13 @@ static int run(int argc, char *argv[]) { log_notice_errno(r, "Couldn't hybrid sleep, will try to suspend instead: %m"); - r = execute(sleep_config, SLEEP_SUSPEND, "suspend-after-failed-hybrid-sleep"); + r = execute(sleep_config, operation, SLEEP_SUSPEND, "suspend-after-failed-hybrid-sleep"); } - break; case SLEEP_SUSPEND: case SLEEP_HIBERNATE: - r = execute(sleep_config, arg_operation, NULL); + r = execute(sleep_config, operation, operation, NULL); break; default: @@ -756,4 +724,43 @@ static int run(int argc, char *argv[]) { return r; } +static int parse_argv(int argc, char *argv[], char ***ret_args) { + assert(argc >= 0); + assert(argv); + assert(ret_args); + + OptionParser state = { argc, argv }; + + FOREACH_OPTION(&state, c, /* arg= */ NULL, /* on_error= */ return c) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + } + + *ret_args = option_parser_get_args(&state); + return 1; +} + +static int run(int argc, char *argv[]) { + _cleanup_(sleep_config_freep) SleepConfig *sleep_config = NULL; + char **args = NULL; + int r; + + log_setup(); + + r = parse_argv(argc, argv, &args); + if (r <= 0) + return r; + + r = parse_sleep_config(&sleep_config); + if (r < 0) + return r; + + return dispatch_verb_with_args(args, sleep_config); +} + DEFINE_MAIN_FUNCTION(run); diff --git a/src/socket-activate/socket-activate.c b/src/socket-activate/socket-activate.c index e7102b62a1aee..2f0c81a1111d0 100644 --- a/src/socket-activate/socket-activate.c +++ b/src/socket-activate/socket-activate.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -14,9 +13,11 @@ #include "errno-util.h" #include "escape.h" #include "fd-util.h" +#include "format-table.h" #include "format-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pidfd-util.h" #include "pidref.h" #include "pretty-print.h" @@ -320,83 +321,63 @@ static int install_chld_handler(void) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-socket-activate", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] COMMAND ...\n" "\n%sListen on sockets and launch child on connection.%s\n" - "\nOptions:\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - " -l --listen=ADDR Listen for raw connections at ADDR\n" - " -d --datagram Listen on datagram instead of stream socket\n" - " --seqpacket Listen on SOCK_SEQPACKET instead of stream socket\n" - " -a --accept Spawn separate child for each connection\n" - " -E --setenv=NAME[=VALUE] Pass an environment variable to children\n" - " --fdname=NAME[:NAME...] Specify names for file descriptors\n" - " --inetd Enable inetd file descriptor passing protocol\n" - " --now Start instantly instead of waiting for connection\n" - "\nNote: file descriptors from sd_listen_fds() will be passed through.\n" - "\nSee the %s for details.\n", + "\n%sOptions:%s\n", program_invocation_short_name, ansi_highlight(), ansi_normal(), - link); + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nNote: file descriptors from sd_listen_fds() will be passed through.\n" + "\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_FDNAME, - ARG_SEQPACKET, - ARG_INETD, - ARG_NOW, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "datagram", no_argument, NULL, 'd' }, - { "seqpacket", no_argument, NULL, ARG_SEQPACKET }, - { "listen", required_argument, NULL, 'l' }, - { "accept", no_argument, NULL, 'a' }, - { "setenv", required_argument, NULL, 'E' }, - { "environment", required_argument, NULL, 'E' }, /* legacy alias */ - { "fdname", required_argument, NULL, ARG_FDNAME }, - { "inetd", no_argument, NULL, ARG_INETD }, - { "now", no_argument, NULL, ARG_NOW }, - {} - }; - - int c, r; - +static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); + assert(remaining_args); + + OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + const char *arg; + int r; - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; - while ((c = getopt_long(argc, argv, "+hl:aE:d", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'l': - r = strv_extend(&arg_listen, optarg); + OPTION('l', "listen", "ADDR", + "Listen for raw connections at ADDR"): + r = strv_extend(&arg_listen, arg); if (r < 0) return log_oom(); break; - case 'd': + OPTION('d', "datagram", NULL, + "Listen on datagram instead of stream socket"): if (arg_socket_type == SOCK_SEQPACKET) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--datagram may not be combined with --seqpacket."); @@ -404,7 +385,8 @@ static int parse_argv(int argc, char *argv[]) { arg_socket_type = SOCK_DGRAM; break; - case ARG_SEQPACKET: + OPTION_LONG("seqpacket", NULL, + "Listen on SOCK_SEQPACKET instead of stream socket"): if (arg_socket_type == SOCK_DGRAM) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--seqpacket may not be combined with --datagram."); @@ -412,20 +394,24 @@ static int parse_argv(int argc, char *argv[]) { arg_socket_type = SOCK_SEQPACKET; break; - case 'a': + OPTION('a', "accept", NULL, + "Spawn separate child for each connection"): arg_accept = true; break; - case 'E': - r = strv_env_replace_strdup_passthrough(&arg_setenv, optarg); + OPTION('E', "setenv", "NAME[=VALUE]", + "Pass an environment variable to children"): {} + OPTION_LONG("environment", "NAME[=VALUE]", /* help= */ NULL): /* legacy alias */ + r = strv_env_replace_strdup_passthrough(&arg_setenv, arg); if (r < 0) - return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); + return log_error_errno(r, "Cannot assign environment variable %s: %m", arg); break; - case ARG_FDNAME: { + OPTION_LONG("fdname", "NAME[:NAME...]", + "Specify names for file descriptors"): { _cleanup_strv_free_ char **names = NULL; - names = strv_split(optarg, ":"); + names = strv_split(arg, ":"); if (!names) return log_oom(); @@ -446,22 +432,19 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_INETD: + OPTION_LONG("inetd", NULL, + "Enable inetd file descriptor passing protocol"): arg_inetd = true; break; - case ARG_NOW: + OPTION_LONG("now", NULL, + "Start instantly instead of waiting for connection"): arg_now = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind == argc) + *remaining_args = option_parser_get_args(&state); + if (strv_isempty(*remaining_args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s: command to execute is missing.", program_invocation_short_name); @@ -488,11 +471,12 @@ static int run(int argc, char **argv) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - exec_argv = strv_copy(argv + optind); + exec_argv = strv_copy(args); if (!exec_argv) return log_oom(); diff --git a/src/socket-proxy/socket-proxyd.c b/src/socket-proxy/socket-proxyd.c index 71172326da125..82250a0b06ff1 100644 --- a/src/socket-proxy/socket-proxyd.c +++ b/src/socket-proxy/socket-proxyd.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include -#include #include #include #include @@ -16,18 +14,20 @@ #include "errno-util.h" #include "event-util.h" #include "fd-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-util.h" #include "pretty-print.h" #include "resolve-private.h" #include "set.h" +#include "socket-forward.h" #include "socket-util.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" -#define BUFFER_SIZE (256 * 1024) - static unsigned arg_connections_max = 256; static const char *arg_remote_host = NULL; static usec_t arg_exit_idle_time = USEC_INFINITY; @@ -45,13 +45,10 @@ typedef struct Connection { Context *context; int server_fd, client_fd; - int server_to_client_buffer[2]; /* a pipe */ - int client_to_server_buffer[2]; /* a pipe */ - size_t server_to_client_buffer_full, client_to_server_buffer_full; - size_t server_to_client_buffer_size, client_to_server_buffer_size; + sd_event_source *connect_event_source; - sd_event_source *server_event_source, *client_event_source; + SocketForward *forward; sd_resolve_query *resolve_query; } Connection; @@ -63,15 +60,12 @@ static Connection* connection_free(Connection *c) { if (c->context) set_remove(c->context->connections, c); - sd_event_source_unref(c->server_event_source); - sd_event_source_unref(c->client_event_source); + sd_event_source_unref(c->connect_event_source); + socket_forward_free(c->forward); safe_close(c->server_fd); safe_close(c->client_fd); - safe_close_pair(c->server_to_client_buffer); - safe_close_pair(c->client_to_server_buffer); - sd_resolve_query_unref(c->resolve_query); return mfree(c); @@ -134,185 +128,29 @@ static void connection_release(Connection *c) { context_reset_timer(context); } -static int connection_create_pipes(Connection *c, int buffer[static 2], size_t *sz) { - int r; - - assert(c); - assert(buffer); - assert(sz); - - if (buffer[0] >= 0) - return 0; - - r = pipe2(buffer, O_CLOEXEC|O_NONBLOCK); - if (r < 0) - return log_error_errno(errno, "Failed to allocate pipe buffer: %m"); - - (void) fcntl(buffer[0], F_SETPIPE_SZ, BUFFER_SIZE); - - r = fcntl(buffer[0], F_GETPIPE_SZ); - if (r < 0) - return log_error_errno(errno, "Failed to get pipe buffer size: %m"); - - assert(r > 0); - *sz = r; - - return 0; -} - -static int connection_shovel( - Connection *c, - int *from, int buffer[2], int *to, - size_t *full, size_t *sz, - sd_event_source **from_source, sd_event_source **to_source) { - - bool shoveled; - - assert(c); - assert(from); - assert(buffer); - assert(buffer[0] >= 0); - assert(buffer[1] >= 0); - assert(to); - assert(full); - assert(sz); - assert(from_source); - assert(to_source); - - do { - ssize_t z; - - shoveled = false; - - if (*full < *sz && *from >= 0 && *to >= 0) { - z = splice(*from, NULL, buffer[1], NULL, *sz - *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK); - if (z > 0) { - *full += z; - shoveled = true; - } else if (z == 0 || ERRNO_IS_DISCONNECT(errno)) { - *from_source = sd_event_source_unref(*from_source); - *from = safe_close(*from); - } else if (!ERRNO_IS_TRANSIENT(errno)) - return log_error_errno(errno, "Failed to splice: %m"); - } - - if (*full > 0 && *to >= 0) { - z = splice(buffer[0], NULL, *to, NULL, *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK); - if (z > 0) { - *full -= z; - shoveled = true; - } else if (z == 0 || ERRNO_IS_DISCONNECT(errno)) { - *to_source = sd_event_source_unref(*to_source); - *to = safe_close(*to); - } else if (!ERRNO_IS_TRANSIENT(errno)) - return log_error_errno(errno, "Failed to splice: %m"); - } - } while (shoveled); - - return 0; -} - -static int connection_enable_event_sources(Connection *c); - -static int traffic_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) { +static int connection_forward_done(SocketForward *sf, int error, void *userdata) { Connection *c = ASSERT_PTR(userdata); - int r; - - assert(s); - assert(fd >= 0); - r = connection_shovel(c, - &c->server_fd, c->server_to_client_buffer, &c->client_fd, - &c->server_to_client_buffer_full, &c->server_to_client_buffer_size, - &c->server_event_source, &c->client_event_source); - if (r < 0) - goto quit; - - r = connection_shovel(c, - &c->client_fd, c->client_to_server_buffer, &c->server_fd, - &c->client_to_server_buffer_full, &c->client_to_server_buffer_size, - &c->client_event_source, &c->server_event_source); - if (r < 0) - goto quit; - - /* EOF on both sides? */ - if (c->server_fd < 0 && c->client_fd < 0) - goto quit; - - /* Server closed, and all data written to client? */ - if (c->server_fd < 0 && c->server_to_client_buffer_full <= 0) - goto quit; - - /* Client closed, and all data written to server? */ - if (c->client_fd < 0 && c->client_to_server_buffer_full <= 0) - goto quit; - - r = connection_enable_event_sources(c); - if (r < 0) - goto quit; - - return 1; + if (error < 0) + log_error_errno(error, "Forwarding failed: %m"); -quit: connection_release(c); return 0; /* ignore errors, continue serving */ } -static int connection_enable_event_sources(Connection *c) { - uint32_t a = 0, b = 0; - int r; - - assert(c); - - if (c->server_to_client_buffer_full > 0) - b |= EPOLLOUT; - if (c->server_to_client_buffer_full < c->server_to_client_buffer_size) - a |= EPOLLIN; - - if (c->client_to_server_buffer_full > 0) - a |= EPOLLOUT; - if (c->client_to_server_buffer_full < c->client_to_server_buffer_size) - b |= EPOLLIN; - - if (c->server_event_source) - r = sd_event_source_set_io_events(c->server_event_source, a); - else if (c->server_fd >= 0) - r = sd_event_add_io(c->context->event, &c->server_event_source, c->server_fd, a, traffic_cb, c); - else - r = 0; - - if (r < 0) - return log_error_errno(r, "Failed to set up server event source: %m"); - - if (c->client_event_source) - r = sd_event_source_set_io_events(c->client_event_source, b); - else if (c->client_fd >= 0) - r = sd_event_add_io(c->context->event, &c->client_event_source, c->client_fd, b, traffic_cb, c); - else - r = 0; - - if (r < 0) - return log_error_errno(r, "Failed to set up client event source: %m"); - - return 0; -} - static int connection_complete(Connection *c) { int r; assert(c); - r = connection_create_pipes(c, c->server_to_client_buffer, &c->server_to_client_buffer_size); - if (r < 0) - return r; - - r = connection_create_pipes(c, c->client_to_server_buffer, &c->client_to_server_buffer_size); + r = socket_forward_new( + c->context->event, + TAKE_FD(c->server_fd), + TAKE_FD(c->client_fd), + connection_forward_done, c, + &c->forward); if (r < 0) - return r; - - r = connection_enable_event_sources(c); - if (r < 0) - return r; + return log_error_errno(r, "Failed to set up forwarding: %m"); return 0; } @@ -336,7 +174,7 @@ static int connect_cb(sd_event_source *s, int fd, uint32_t revents, void *userda goto fail; } - c->client_event_source = sd_event_source_unref(c->client_event_source); + c->connect_event_source = sd_event_source_unref(c->connect_event_source); if (connection_complete(c) < 0) goto fail; @@ -364,11 +202,11 @@ static int connection_start(Connection *c, struct sockaddr *sa, socklen_t salen) if (errno != EINPROGRESS) return log_error_errno(errno, "Failed to connect to remote host: %m"); - r = sd_event_add_io(c->context->event, &c->client_event_source, c->client_fd, EPOLLOUT, connect_cb, c); + r = sd_event_add_io(c->context->event, &c->connect_event_source, c->client_fd, EPOLLOUT, connect_cb, c); if (r < 0) return log_error_errno(r, "Failed to add connection socket: %m"); - r = sd_event_source_set_enabled(c->client_event_source, SD_EVENT_ONESHOT); + r = sd_event_source_set_enabled(c->connect_event_source, SD_EVENT_ONESHOT); if (r < 0) return log_error_errno(r, "Failed to enable oneshot event source: %m"); @@ -472,8 +310,6 @@ static int context_add_connection(Context *context, int fd) { *c = (Connection) { .server_fd = TAKE_FD(nfd), .client_fd = -EBADF, - .server_to_client_buffer = EBADF_PAIR, - .client_to_server_buffer = EBADF_PAIR, }; r = set_ensure_put(&context->connections, &connection_hash_ops, c); @@ -548,8 +384,8 @@ static int add_listen_socket(Context *context, int fd) { } static int help(void) { - _cleanup_free_ char *link = NULL; - _cleanup_free_ char *time_link = NULL; + _cleanup_free_ char *link = NULL, *time_link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-socket-proxyd", "8", &link); @@ -559,61 +395,49 @@ static int help(void) { if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%1$s [HOST:PORT]\n" - "%1$s [SOCKET]\n\n" - "%2$sBidirectionally proxy local sockets to another (possibly remote) socket.%3$s\n\n" - " -c --connections-max= Set the maximum number of connections to be accepted\n" - " --exit-idle-time= Exit when without a connection for this duration. See\n" - " the %4$s for time span format\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\nSee the %5$s for details.\n", + "%1$s [SOCKET]\n" + "\n%2$sBidirectionally proxy local sockets to another (possibly remote) socket.%3$s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - time_link, - link); + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee %s for --exit-idle-time= time span format.\n" + "See the %s for details.\n", + time_link, link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_EXIT_IDLE, - ARG_IGNORE_ENV - }; - - static const struct option options[] = { - { "connections-max", required_argument, NULL, 'c' }, - { "exit-idle-time", required_argument, NULL, ARG_EXIT_IDLE }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c, r; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "c:h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'c': - r = safe_atou(optarg, &arg_connections_max); - if (r < 0) { - log_error("Failed to parse --connections-max= argument: %s", optarg); - return r; - } + OPTION('c', "connections-max", "NUMBER", + "Set the maximum number of connections to be accepted"): + r = safe_atou(arg, &arg_connections_max); + if (r < 0) + return log_error_errno(r, "Failed to parse --connections-max= argument: %s", arg); if (arg_connections_max < 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -621,28 +445,22 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_EXIT_IDLE: - r = parse_sec(optarg, &arg_exit_idle_time); + OPTION_LONG("exit-idle-time", "TIME", + "Exit when without a connection for this duration"): + r = parse_sec(arg, &arg_exit_idle_time); if (r < 0) - return log_error_errno(r, "Failed to parse --exit-idle-time= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --exit-idle-time= argument: %s", arg); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind >= argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Not enough parameters."); - - if (argc != optind+1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Too many parameters."); + char **args = option_parser_get_args(&state); + size_t n = strv_length(args); + if (n < 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not enough parameters."); + if (n > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many parameters."); - arg_remote_host = argv[optind]; + arg_remote_host = args[0]; return 1; } diff --git a/src/ssh-generator/ssh-generator.c b/src/ssh-generator/ssh-generator.c index 3923710b5cca0..ae062fc58da47 100644 --- a/src/ssh-generator/ssh-generator.c +++ b/src/ssh-generator/ssh-generator.c @@ -238,8 +238,8 @@ static int add_vsock_socket( "sshd-vsock.socket", "vsock::22", "AF_VSOCK", - "ExecStartPost=-/usr/lib/systemd/systemd-ssh-issue --make-vsock\n" - "ExecStopPre=-/usr/lib/systemd/systemd-ssh-issue --rm-vsock\n", + "ExecStartPost=-/usr/lib/systemd/systemd-ssh-issue make-vsock\n" + "ExecStopPre=-/usr/lib/systemd/systemd-ssh-issue rm-vsock\n", /* with_ssh_access_target_dependency= */ true); if (r < 0) return r; @@ -338,7 +338,7 @@ static int add_export_unix_socket( return r; log_debug("Binding SSH to AF_UNIX socket /run/host/unix-export/ssh\n" - "→ connect via 'ssh unix/run/systemd/nspawn/unix-export/\?\?\?/ssh' from host"); + "→ connect via 'ssh unix/run/systemd/nspawn/\?\?\?/unix-export/ssh' from host"); return 0; } diff --git a/src/ssh-generator/ssh-issue.c b/src/ssh-generator/ssh-issue.c index 852dabdb852d1..f1000d60684a9 100644 --- a/src/ssh-generator/ssh-issue.c +++ b/src/ssh-generator/ssh-issue.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -8,107 +7,192 @@ #include "ansi-color.h" #include "build.h" #include "fd-util.h" +#include "format-table.h" #include "fs-util.h" #include "log.h" #include "main-func.h" #include "mkdir.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "ssh-util.h" #include "string-util.h" +#include "strv.h" #include "tmpfile-util.h" +#include "verbs.h" #include "virt.h" -static enum { - ACTION_MAKE_VSOCK, - ACTION_RM_VSOCK, -} arg_action = ACTION_MAKE_VSOCK; - static char *arg_issue_path = NULL; static bool arg_issue_stdout = false; STATIC_DESTRUCTOR_REGISTER(arg_issue_path, freep); +static int acquire_cid(unsigned *ret_cid) { + int r; + + assert(ret_cid); + + Virtualization v = detect_virtualization(); + if (v < 0) + return log_error_errno(v, "Failed to detect if we run in a VM: %m"); + if (!VIRTUALIZATION_IS_VM(v)) { + /* NB: if we are running in a container inside a VM, then we'll *not* do AF_VSOCK stuff */ + log_debug("Not running in a VM, not creating issue file."); + *ret_cid = 0; + return 0; + } + + r = vsock_open_or_warn(/* ret= */ NULL); + if (r <= 0) + return r; + + return vsock_get_local_cid_or_warn(ret_cid); +} + +VERB_NOARG(verb_make_vsock, "make-vsock", + "Generate the issue file"); +static int verb_make_vsock(int argc, char *argv[], uintptr_t _data, void *_userdata) { + unsigned cid; + int r; + + r = acquire_cid(&cid); + if (r < 0) + return r; + if (r == 0) { + log_debug("Not running in a VSOCK enabled VM, skipping."); + return 0; + } + + _cleanup_(unlink_and_freep) char *t = NULL; + _cleanup_(fclosep) FILE *f = NULL; + FILE *out; + + if (arg_issue_path) { + r = mkdir_parents(arg_issue_path, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create parent directories of '%s': %m", arg_issue_path); + + r = fopen_tmpfile_linkable(arg_issue_path, O_WRONLY|O_CLOEXEC, &t, &f); + if (r < 0) + return log_error_errno(r, "Failed to create '%s': %m", arg_issue_path); + + out = f; + } else + out = stdout; + + fprintf(out, + "Try contacting this VM's SSH server via 'ssh vsock%%%u' from host.\n" + "\n", cid); + + if (f) { + if (fchmod(fileno(f), 0644) < 0) + return log_error_errno(errno, "Failed to adjust access mode of '%s': %m", arg_issue_path); + + r = flink_tmpfile(f, t, arg_issue_path, LINK_TMPFILE_REPLACE); + if (r < 0) + return log_error_errno(r, "Failed to move '%s' into place: %m", arg_issue_path); + } + + return 0; +} + +VERB_NOARG(verb_rm_vsock, "rm-vsock", + "Remove the issue file"); +static int verb_rm_vsock(int argc, char *argv[], uintptr_t _data, void *_userdata) { + if (arg_issue_path) { + if (unlink(arg_issue_path) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to remove '%s': %m", arg_issue_path); + + log_debug_errno(errno, "File '%s' does not exist, no operation executed.", arg_issue_path); + } else + log_debug("Successfully removed '%s'.", arg_issue_path); + } else + log_notice("STDOUT selected for issue file, not removing."); + + return 0; +} + static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-ssh-issue", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] --make-vsock\n" - "%s [OPTIONS...] --rm-vsock\n" - "\n%sCreate ssh /run/issue.d/ file reporting VSOCK address.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --issue-path=PATH Change path to /run/issue.d/50-ssh-vsock.issue\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; - return 0; -} + r = option_parser_get_help_table(&options); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { + (void) table_sync_column_widths(0, verbs, options); - enum { - ARG_MAKE_VSOCK = 0x100, - ARG_RM_VSOCK, - ARG_ISSUE_PATH, - ARG_VERSION, - }; + printf("%s [OPTIONS...] COMMAND\n" + "\n%sCreate/remove ssh /run/issue.d/ file reporting VSOCK address.%s\n" + "\n%sCommands:%s\n", + program_invocation_short_name, + ansi_highlight(), ansi_normal(), + ansi_underline(), ansi_normal()); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "make-vsock", no_argument, NULL, ARG_MAKE_VSOCK }, - { "rm-vsock", no_argument, NULL, ARG_RM_VSOCK }, - { "issue-path", required_argument, NULL, ARG_ISSUE_PATH }, - {} - }; + r = table_print_or_warn(verbs); + if (r < 0) + return r; - int c, r; + printf("\n%sOptions:%s\n", + ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); + return 0; +} + +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg, *verb = NULL; + int r; + FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_MAKE_VSOCK: - arg_action = ACTION_MAKE_VSOCK; + OPTION_LONG("make-vsock", NULL, /* help= */ NULL): {} + OPTION_LONG("rm-vsock", NULL, /* help= */ NULL): + verb = opt->long_code; break; - case ARG_RM_VSOCK: - arg_action = ACTION_RM_VSOCK; - break; - - case ARG_ISSUE_PATH: - if (isempty(optarg) || streq(optarg, "-")) { + OPTION_LONG("issue-path", "PATH", + "Change path to /run/issue.d/50-ssh-vsock.issue"): + if (empty_or_dash(arg)) { arg_issue_path = mfree(arg_issue_path); arg_issue_stdout = true; break; } - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_issue_path); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_issue_path); if (r < 0) return r; arg_issue_stdout = false; break; } - } if (!arg_issue_path && !arg_issue_stdout) { arg_issue_path = strdup("/run/issue.d/50-ssh-vsock.issue"); @@ -116,104 +200,32 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); } - return 1; -} - -static int acquire_cid(unsigned *ret_cid) { - int r; - - assert(ret_cid); - - Virtualization v = detect_virtualization(); - if (v < 0) - return log_error_errno(v, "Failed to detect if we run in a VM: %m"); - if (!VIRTUALIZATION_IS_VM(v)) { - /* NB: if we are running in a container inside a VM, then we'll *not* do AF_VSOCK stuff */ - log_debug("Not running in a VM, not creating issue file."); - *ret_cid = 0; - return 0; - } - - r = vsock_open_or_warn(/* ret= */ NULL); - if (r <= 0) - return r; + char **args; + if (verb) { + if (option_parser_get_n_args(&state) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid use of compat option --make-vsock/--rm-vsock."); + log_warning("Options --make-vsock/--rm-vsock have been replaced by make-vsock/rm-vsock verbs."); + args = strv_new(verb); + } else + args = strv_copy(option_parser_get_args(&state)); + if (!args) + return log_oom(); - return vsock_get_local_cid_or_warn(ret_cid); + *ret_args = args; + return 1; } static int run(int argc, char* argv[]) { + _cleanup_strv_free_ char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - switch (arg_action) { - case ACTION_MAKE_VSOCK: { - unsigned cid; - - r = acquire_cid(&cid); - if (r < 0) - return r; - if (r == 0) { - log_debug("Not running in a VSOCK enabled VM, skipping."); - break; - } - - _cleanup_(unlink_and_freep) char *t = NULL; - _cleanup_(fclosep) FILE *f = NULL; - FILE *out; - - if (arg_issue_path) { - r = mkdir_parents(arg_issue_path, 0755); - if (r < 0) - return log_error_errno(r, "Failed to create parent directories of '%s': %m", arg_issue_path); - - r = fopen_tmpfile_linkable(arg_issue_path, O_WRONLY|O_CLOEXEC, &t, &f); - if (r < 0) - return log_error_errno(r, "Failed to create '%s': %m", arg_issue_path); - - out = f; - } else - out = stdout; - - fprintf(out, - "Try contacting this VM's SSH server via 'ssh vsock%%%u' from host.\n" - "\n", cid); - - if (f) { - if (fchmod(fileno(f), 0644) < 0) - return log_error_errno(errno, "Failed to adjust access mode of '%s': %m", arg_issue_path); - - r = flink_tmpfile(f, t, arg_issue_path, LINK_TMPFILE_REPLACE); - if (r < 0) - return log_error_errno(r, "Failed to move '%s' into place: %m", arg_issue_path); - } - - break; - } - - case ACTION_RM_VSOCK: - if (arg_issue_path) { - if (unlink(arg_issue_path) < 0) { - if (errno != ENOENT) - return log_error_errno(errno, "Failed to remove '%s': %m", arg_issue_path); - - log_debug_errno(errno, "File '%s' does not exist, no operation executed.", arg_issue_path); - } else - log_debug("Successfully removed '%s'.", arg_issue_path); - } else - log_notice("STDOUT selected for issue file, not removing."); - - break; - - default: - assert_not_reached(); - } - - return 0; + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/ssh-generator/ssh-proxy.c b/src/ssh-generator/ssh-proxy.c index 337153787ec1d..f253036522796 100644 --- a/src/ssh-generator/ssh-proxy.c +++ b/src/ssh-generator/ssh-proxy.c @@ -62,6 +62,9 @@ static int process_vsock_string(const char *host, const char *port) { if (r < 0) return log_error_errno(r, "Failed to parse vsock cid: %s", host); + if (cid == VMADDR_CID_ANY) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use VMADDR_CID_ANY to connect to a remote host."); + return process_vsock_cid(cid, port); } @@ -279,7 +282,7 @@ static int fetch_machine(const char *machine, RuntimeScope scope, sd_json_varian if (r < 0) return log_error_errno(r, "Failed to connect to machined on %s: %m", addr); - _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; + sd_json_variant *result = NULL; const char *error_id; r = sd_varlink_callbo( vl, @@ -300,7 +303,9 @@ static int fetch_machine(const char *machine, RuntimeScope scope, sd_json_varian return log_error_errno(r, "Failed to issue io.systemd.Machine.List() varlink call: %s", error_id); } - *ret = TAKE_PTR(result); + /* result is a borrowed reference into the varlink connection's receive buffer. Take a real ref so + * that it survives the cleanup of vl below. */ + *ret = sd_json_variant_ref(result); return 0; } @@ -332,6 +337,7 @@ static int process_machine(const char *machine, const char *port) { { "vSockCid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint32, voffsetof(p, cid), 0 }, { "class", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, class), SD_JSON_MANDATORY }, { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, service), 0 }, + {} }; r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); @@ -344,11 +350,11 @@ static int process_machine(const char *machine, const char *port) { if (!streq_ptr(p.service, "systemd-nspawn")) return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Don't know how to SSH into '%s' container %s.", p.service, machine); - r = runtime_directory_generic(scope, "systemd/nspawn/unix-export", &path); + r = runtime_directory_generic(scope, "systemd/nspawn", &path); if (r < 0) return log_error_errno(r, "Failed to determine runtime directory: %m"); - if (!path_extend(&path, machine, "ssh")) + if (!path_extend(&path, machine, "unix-export", "ssh")) return log_oom(); r = is_socket(path); diff --git a/src/stdio-bridge/stdio-bridge.c b/src/stdio-bridge/stdio-bridge.c index 52c87559a4933..42f365ad3ec18 100644 --- a/src/stdio-bridge/stdio-bridge.c +++ b/src/stdio-bridge/stdio-bridge.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -11,9 +10,11 @@ #include "bus-internal.h" #include "bus-util.h" #include "errno-util.h" +#include "format-table.h" #include "io-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "time-util.h" @@ -23,84 +24,66 @@ static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; static bool arg_quiet = false; static int help(void) { - printf("%s [OPTIONS...]\n\n" - "Forward messages between a pipe or socket and a D-Bus bus.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -p --bus-path=PATH Path to the bus address (default: %s)\n" - " --system Connect to system bus\n" - " --user Connect to user bus\n" - " -M --machine=CONTAINER Name of local container to connect to\n" - " -q --quiet Fail silently instead of logging errors\n", - program_invocation_short_name, DEFAULT_SYSTEM_BUS_ADDRESS); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\nForward messages between a pipe or socket and a D-Bus bus.\n\n", + program_invocation_short_name); + + r = table_print_or_warn(options); + if (r < 0) + return r; return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_USER, - ARG_SYSTEM, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "bus-path", required_argument, NULL, 'p' }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "machine", required_argument, NULL, 'M' }, - { "quiet", no_argument, NULL, 'q' }, - {}, - }; - - int c, r; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hp:M:", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_USER: + OPTION_LONG("user", NULL, "Connect to user bus"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Connect to system bus"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case 'p': - arg_bus_path = optarg; + OPTION('p', "bus-path", "PATH", + "Path to the bus address (default: " DEFAULT_SYSTEM_BUS_ADDRESS ")"): + arg_bus_path = arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_bus_path, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(arg, &arg_bus_path, &arg_transport); if (r < 0) return r; break; - case 'q': + OPTION('q', "quiet", NULL, "Fail silently instead of logging errors"): arg_quiet = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (argc > optind) + if (option_parser_get_n_args(&state) > 0) return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(EINVAL), "%s takes no arguments.", program_invocation_short_name); diff --git a/src/storagetm/storagetm.c b/src/storagetm/storagetm.c index c6caaa1260ba3..2b0ed742c1a16 100644 --- a/src/storagetm/storagetm.c +++ b/src/storagetm/storagetm.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -17,12 +16,14 @@ #include "device-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "hashmap.h" #include "id128-util.h" #include "local-addresses.h" #include "main-func.h" #include "mountpoint-util.h" +#include "options.h" #include "os-util.h" #include "path-util.h" #include "plymouth-util.h" @@ -47,99 +48,86 @@ STATIC_DESTRUCTOR_REGISTER(arg_nqn, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-storagetm", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] [DEVICE...]\n" - "\n%sExpose a block device or regular file as NVMe-TCP volume.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --nqn=STRING Select NQN (NVMe Qualified Name)\n" - " -a --all Expose all devices\n" - " --list-devices List candidate block devices to operate on\n" - "\nSee the %s for details.\n", + "\n%sExpose a block device or regular file as NVMe-TCP volume.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_NQN = 0x100, - ARG_VERSION, - ARG_LIST_DEVICES, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "nqn", required_argument, NULL, ARG_NQN }, - { "all", no_argument, NULL, 'a' }, - { "list-devices", no_argument, NULL, ARG_LIST_DEVICES }, - {} - }; - - int r, c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "ha", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NQN: - if (!filename_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NQN invalid: %s", optarg); + OPTION_LONG("nqn", "STRING", + "Select NQN (NVMe Qualified Name)"): + if (!filename_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NQN invalid: %s", arg); - if (free_and_strdup(&arg_nqn, optarg) < 0) + if (free_and_strdup(&arg_nqn, arg) < 0) return log_oom(); break; - case 'a': + OPTION('a', "all", NULL, "Expose all devices"): arg_all++; break; - case ARG_LIST_DEVICES: + OPTION_LONG("list-devices", NULL, + "List candidate block devices to operate on"): r = blockdev_list(BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_IGNORE_ZRAM, /* ret_devices= */ NULL, /* ret_n_devices= */ NULL); if (r < 0) return r; return 0; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + char **args = option_parser_get_args(&state); if (arg_all > 0) { - if (argc > optind) + if (!strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expects no further arguments if --all/-a is specified."); } else { - if (optind >= argc) + if (strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expecting device name or --all/-a."); - for (int i = optind; i < argc; i++) - if (!path_is_valid(argv[i])) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path: %s", argv[i]); + STRV_FOREACH(a, args) + if (!path_is_valid(*a)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path: %s", *a); - arg_devices = strv_copy(argv + optind); + arg_devices = strv_copy(args); + if (!arg_devices) + return log_oom(); } if (!arg_nqn) { @@ -746,6 +734,8 @@ static int plymouth_notify_port(NvmePort *port, struct local_address *a) { } static int nvme_port_report(NvmePort *port, bool *plymouth_done) { + POINTER_MAY_BE_NULL(plymouth_done); + if (!port) return 0; diff --git a/src/sysctl/sysctl.c b/src/sysctl/sysctl.c index cf7dea24f58d4..39041ea384502 100644 --- a/src/sysctl/sysctl.c +++ b/src/sysctl/sysctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -10,10 +9,12 @@ #include "constants.h" #include "creds-util.h" #include "errno-util.h" +#include "format-table.h" #include "glob-util.h" #include "hashmap.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "path-util.h" #include "pretty-print.h" @@ -29,13 +30,13 @@ static PagerFlags arg_pager_flags = 0; STATIC_DESTRUCTOR_REGISTER(arg_prefixes, strv_freep); -typedef struct Option { +typedef struct SysctlOption { char *key; char *value; bool ignore_failure; -} Option; +} SysctlOption; -static Option* option_free(Option *o) { +static SysctlOption* sysctl_option_free(SysctlOption *o) { if (!o) return NULL; @@ -45,11 +46,11 @@ static Option* option_free(Option *o) { return mfree(o); } -DEFINE_TRIVIAL_CLEANUP_FUNC(Option*, option_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(SysctlOption*, sysctl_option_free); DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( - option_hash_ops, + sysctl_option_hash_ops, char, string_hash_func, string_compare_func, - Option, option_free); + SysctlOption, sysctl_option_free); static bool test_prefix(const char *p) { if (strv_isempty(arg_prefixes)) @@ -58,20 +59,20 @@ static bool test_prefix(const char *p) { return path_startswith_strv(p, arg_prefixes); } -static Option* option_new( +static SysctlOption* sysctl_option_new( const char *key, const char *value, bool ignore_failure) { - _cleanup_(option_freep) Option *o = NULL; + _cleanup_(sysctl_option_freep) SysctlOption *o = NULL; assert(key); - o = new(Option, 1); + o = new(SysctlOption, 1); if (!o) return NULL; - *o = (Option) { + *o = (SysctlOption) { .key = strdup(key), .value = value ? strdup(value) : NULL, .ignore_failure = ignore_failure, @@ -108,7 +109,7 @@ static int sysctl_write_or_warn(const char *key, const char *value, bool ignore_ return 0; } -static int apply_glob_option_with_prefix(OrderedHashmap *sysctl_options, Option *option, const char *prefix) { +static int apply_glob_option_with_prefix(OrderedHashmap *sysctl_options, SysctlOption *option, const char *prefix) { _cleanup_strv_free_ char **paths = NULL; _cleanup_free_ char *pattern = NULL; int r; @@ -179,7 +180,7 @@ static int apply_glob_option_with_prefix(OrderedHashmap *sysctl_options, Option return r; } -static int apply_glob_option(OrderedHashmap *sysctl_options, Option *option) { +static int apply_glob_option(OrderedHashmap *sysctl_options, SysctlOption *option) { int r = 0; if (strv_isempty(arg_prefixes)) @@ -191,7 +192,7 @@ static int apply_glob_option(OrderedHashmap *sysctl_options, Option *option) { } static int apply_all(OrderedHashmap *sysctl_options) { - Option *option; + SysctlOption *option; int r = 0; ORDERED_HASHMAP_FOREACH(option, sysctl_options) { @@ -253,7 +254,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool if (!string_is_glob(key) && !test_prefix(key)) return 0; - Option *existing = ordered_hashmap_get(*sysctl_options, key); + SysctlOption *existing = ordered_hashmap_get(*sysctl_options, key); if (existing) { if (streq_ptr(value, existing->value)) { existing->ignore_failure = existing->ignore_failure || ignore_failure; @@ -262,14 +263,14 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool log_syntax(NULL, LOG_DEBUG, fname, line, 0, "Overwriting earlier assignment of '%s'.", key); - option_free(ordered_hashmap_remove(*sysctl_options, key)); + sysctl_option_free(ordered_hashmap_remove(*sysctl_options, key)); } - _cleanup_(option_freep) Option *option = option_new(key, value, ignore_failure); + _cleanup_(sysctl_option_freep) SysctlOption *option = sysctl_option_new(key, value, ignore_failure); if (!option) return log_oom(); - r = ordered_hashmap_ensure_put(sysctl_options, &option_hash_ops, option->key, option); + r = ordered_hashmap_ensure_put(sysctl_options, &sysctl_option_hash_ops, option->key, option); if (r < 0) return log_error_errno(r, "Failed to add sysctl variable '%s' to hashmap: %m", key); @@ -314,122 +315,110 @@ static int cat_config(char **files) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *commands = NULL, *options = NULL; int r; r = terminal_urlify_man("systemd-sysctl.service", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [CONFIGURATION FILE...]\n" - "\n%2$sApplies kernel sysctl settings.%4$s\n" - "\n%3$sCommands:%4$s\n" - " --cat-config Show configuration files\n" - " --tldr Show non-comment parts of configuration\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sOptions:%4$s\n" - " --prefix=PATH Only apply rules with the specified prefix\n" - " --no-pager Do not pipe output into a pager\n" - " --strict Fail on any kind of failures\n" - " --inline Treat arguments as configuration lines\n" - "\nSee the %5$s for details.\n", + r = option_parser_get_help_table(&commands); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("Options", &options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, commands, options); + + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n" + "\n%sApplies kernel sysctl settings.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, ansi_highlight(), - ansi_underline(), ansi_normal(), - link); + ansi_underline(), + ansi_normal()); - return 0; -} + r = table_print_or_warn(commands); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { + printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal()); - enum { - ARG_VERSION = 0x100, - ARG_CAT_CONFIG, - ARG_TLDR, - ARG_PREFIX, - ARG_NO_PAGER, - ARG_STRICT, - ARG_INLINE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "cat-config", no_argument, NULL, ARG_CAT_CONFIG }, - { "tldr", no_argument, NULL, ARG_TLDR }, - { "prefix", required_argument, NULL, ARG_PREFIX }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "strict", no_argument, NULL, ARG_STRICT }, - { "inline", no_argument, NULL, ARG_INLINE }, - {} - }; + r = table_print_or_warn(options); + if (r < 0) + return r; - int c; + printf("\nSee the %s for details.\n", link); + return 0; +} +static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_CAT_CONFIG: + OPTION_COMMON_CAT_CONFIG: arg_cat_flags = CAT_CONFIG_ON; break; - case ARG_TLDR: + OPTION_COMMON_TLDR: arg_cat_flags = CAT_TLDR; break; - case ARG_PREFIX: { - const char *s; - char *p; + OPTION_GROUP("Options"): {} + + OPTION_LONG("prefix", "PATH", + "Only apply rules with the specified prefix"): { + _cleanup_free_ char *normalized = strdup(arg); + if (!normalized) + return log_oom(); + sysctl_normalize(normalized); /* We used to require people to specify absolute paths * in /proc/sys in the past. This is kinda useless, but * we need to keep compatibility. We now support any * sysctl name available. */ - sysctl_normalize(optarg); - - s = path_startswith(optarg, "/proc/sys"); - p = strdup(s ?: optarg); - if (!p) - return log_oom(); + const char *s = path_startswith(normalized, "/proc/sys"); - if (strv_consume(&arg_prefixes, p) < 0) + if (strv_extend(&arg_prefixes, s ?: normalized) < 0) return log_oom(); break; } - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_STRICT: + OPTION_LONG("strict", NULL, + "Fail on any kind of failures"): arg_strict = true; break; - case ARG_INLINE: + OPTION_LONG("inline", NULL, + "Treat arguments as configuration lines"): arg_inline = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (arg_cat_flags != CAT_CONFIG_OFF && argc > optind) + *remaining_args = option_parser_get_args(&state); + + if (arg_cat_flags != CAT_CONFIG_OFF && !strv_isempty(*remaining_args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Positional arguments are not allowed with --cat-config/--tldr."); @@ -440,7 +429,8 @@ static int run(int argc, char *argv[]) { _cleanup_ordered_hashmap_free_ OrderedHashmap *sysctl_options = NULL; int r; - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -448,10 +438,10 @@ static int run(int argc, char *argv[]) { umask(0022); - if (argc > optind) { + if (!strv_isempty(args)) { unsigned pos = 0; - STRV_FOREACH(arg, strv_skip(argv, optind)) { + STRV_FOREACH(arg, args) { if (arg_inline) /* Use (argument):n, where n==1 for the first positional arg */ RET_GATHER(r, parse_line("(argument)", ++pos, *arg, /* invalid_config= */ NULL, &sysctl_options)); diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 3661457d26f73..5f97f43ffb4c2 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -51,6 +51,7 @@ #include "path-util.h" #include "pidref.h" #include "pretty-print.h" +#include "proc-cmdline.h" #include "process-util.h" #include "rm-rf.h" #include "runtime-scope.h" @@ -564,7 +565,7 @@ static int unmerge( bool need_to_reload; int r; - (void) dlopen_libmount(); + (void) dlopen_libmount(LOG_DEBUG); r = need_reload(image_class, hierarchies, no_reload); if (r < 0) @@ -597,7 +598,7 @@ static int unmerge( return 0; } -static int verb_unmerge(int argc, char **argv, void *userdata) { +static int verb_unmerge(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = have_effective_cap(CAP_SYS_ADMIN); @@ -690,7 +691,7 @@ static int vl_method_unmerge(sd_varlink *link, sd_json_variant *parameters, sd_v return sd_varlink_reply(link, NULL); } -static int verb_status(int argc, char **argv, void *userdata) { +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *t = NULL; int r, ret = 0; @@ -1055,7 +1056,7 @@ static int resolve_mutable_directory( /* This also creates, e.g., /var/lib/extensions.mutable/usr if needed and all parent * directories plus it also works when the last part is a symlink to the real /usr but we * can't use chase_and_open here because it does not behave the same. */ - r = chase(path, root, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT, /* ret_path */ NULL, &path_fd); + r = chase(path, root, CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT, /* ret_path */ NULL, &path_fd); if (r < 0) return log_error_errno(r, "Failed to chase/create base directory '%s/%s': %m", strempty(root), skip_leading_slash(path)); @@ -1785,6 +1786,9 @@ static int merge_hierarchy( } static int strverscmp_improvedp(char *const* a, char *const* b) { + assert(a); + assert(b); + /* usable in qsort() for sorting a string array with strverscmp_improved() */ return strverscmp_improved(*a, *b); } @@ -2369,9 +2373,9 @@ static int merge(ImageClass image_class, int r; - (void) dlopen_cryptsetup(); - (void) dlopen_libblkid(); - (void) dlopen_libmount(); + (void) dlopen_cryptsetup(LOG_DEBUG); + (void) dlopen_libblkid(LOG_DEBUG); + (void) dlopen_libmount(LOG_DEBUG); _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; r = pidref_safe_fork("(sd-merge)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_NEW_MOUNTNS, &pidref); @@ -2434,8 +2438,7 @@ static int image_discover_and_read_metadata(ImageClass image_class, Hashmap **re return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name); } - if (ret_images) - *ret_images = TAKE_PTR(images); + *ret_images = TAKE_PTR(images); return 0; } @@ -2474,7 +2477,7 @@ static int look_for_merged_hierarchies( return 0; } -static int verb_merge(int argc, char **argv, void *userdata) { +static int verb_merge(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_hashmap_free_ Hashmap *images = NULL; const char *which; int r; @@ -2637,7 +2640,7 @@ static int refresh( return r; } -static int verb_refresh(int argc, char **argv, void *userdata) { +static int verb_refresh(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = have_effective_cap(CAP_SYS_ADMIN); @@ -2703,7 +2706,7 @@ static int vl_method_refresh(sd_varlink *link, sd_json_variant *parameters, sd_v return sd_varlink_reply(link, NULL); } -static int verb_list(int argc, char **argv, void *userdata) { +static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_hashmap_free_ Hashmap *images = NULL; _cleanup_(table_unrefp) Table *t = NULL; Image *img; @@ -2775,7 +2778,7 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl if (r < 0) return r; - r = varlink_set_sentinel(link, "io.systemd.sysext.NoImagesFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.sysext.NoImagesFound"); if (r < 0) return r; @@ -2794,7 +2797,7 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -2839,8 +2842,11 @@ static int verb_help(int argc, char **argv, void *userdata) { return 0; } -static int parse_argv(int argc, char *argv[]) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} +static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_NO_PAGER, @@ -2881,7 +2887,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return verb_help(argc, argv, NULL); + return help(); case ARG_VERSION: return version(); @@ -3033,6 +3039,22 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + /* PROC_CMDLINE_STRIP_RD_PREFIX cannot be used here as we need to be able to distinguish between + * rd.systemd.{sysext,confext} and systemd.{sysext,confext} in the initrd where they are both used + * and have different meaning. */ + const char *string_class = image_class_to_string(arg_image_class); + const char *cmdline_opt = strjoina(in_initrd() && !arg_root ? "rd." : "", "systemd.", string_class); + + bool enabled; + r = proc_cmdline_get_bool(cmdline_opt, PROC_CMDLINE_TRUE_WHEN_MISSING, &enabled); + if (r < 0) + log_debug_errno(r, "Failed to check '%s=' kernel command line option, proceeding: %m", cmdline_opt); + else if (!enabled && invoked_by_systemd()) { + /* Kernel command line option should not affect manual invocation. */ + log_notice("Disabled by the kernel command line option '%s=', skipping execution.", cmdline_opt); + return 0; + } + /* Parse configuration file after argv because it needs --root=. * The config entries will not overwrite values set already by * env/argv because we track initialization. */ diff --git a/src/systemctl/systemctl-add-dependency.c b/src/systemctl/systemctl-add-dependency.c index bc1a1f00f69e5..2972c4c63b839 100644 --- a/src/systemctl/systemctl-add-dependency.c +++ b/src/systemctl/systemctl-add-dependency.c @@ -17,7 +17,7 @@ #include "systemctl-util.h" #include "unit-name.h" -int verb_add_dependency(int argc, char *argv[], void *userdata) { +int verb_add_dependency(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **names = NULL; _cleanup_free_ char *target = NULL; const char *verb = argv[0]; diff --git a/src/systemctl/systemctl-add-dependency.h b/src/systemctl/systemctl-add-dependency.h index 11e5c82cc99a6..799301170ba6a 100644 --- a/src/systemctl/systemctl-add-dependency.h +++ b/src/systemctl/systemctl-add-dependency.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_add_dependency(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_add_dependency(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-cancel-job.c b/src/systemctl/systemctl-cancel-job.c index 63459820f9698..ed829fb1382ad 100644 --- a/src/systemctl/systemctl-cancel-job.c +++ b/src/systemctl/systemctl-cancel-job.c @@ -12,12 +12,12 @@ #include "systemctl-trivial-method.h" #include "systemctl-util.h" -int verb_cancel(int argc, char *argv[], void *userdata) { +int verb_cancel(int argc, char *argv[], uintptr_t data, void *userdata) { sd_bus *bus; int r; if (argc <= 1) /* Shortcut to trivial_method() if no argument is given */ - return verb_trivial_method(argc, argv, userdata); + return verb_trivial_method(argc, argv, data, userdata); r = acquire_bus(BUS_MANAGER, &bus); if (r < 0) diff --git a/src/systemctl/systemctl-cancel-job.h b/src/systemctl/systemctl-cancel-job.h index 397e5155f3eb2..0c27cb19e5b83 100644 --- a/src/systemctl/systemctl-cancel-job.h +++ b/src/systemctl/systemctl-cancel-job.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_cancel(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_cancel(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-clean-or-freeze.c b/src/systemctl/systemctl-clean-or-freeze.c index 4870074e8c014..dfaff2adbcf35 100644 --- a/src/systemctl/systemctl-clean-or-freeze.c +++ b/src/systemctl/systemctl-clean-or-freeze.c @@ -13,7 +13,7 @@ #include "systemctl-clean-or-freeze.h" #include "systemctl-util.h" -int verb_clean_or_freeze(int argc, char *argv[], void *userdata) { +int verb_clean_or_freeze(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *w = NULL; _cleanup_strv_free_ char **names = NULL; int r, ret = EXIT_SUCCESS; diff --git a/src/systemctl/systemctl-clean-or-freeze.h b/src/systemctl/systemctl-clean-or-freeze.h index 5f2bca4a4e56a..aadf15c2975ae 100644 --- a/src/systemctl/systemctl-clean-or-freeze.h +++ b/src/systemctl/systemctl-clean-or-freeze.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_clean_or_freeze(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_clean_or_freeze(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-compat-halt.c b/src/systemctl/systemctl-compat-halt.c index 614216fd08559..cebdfb2413f16 100644 --- a/src/systemctl/systemctl-compat-halt.c +++ b/src/systemctl/systemctl-compat-halt.c @@ -170,7 +170,7 @@ int halt_main(void) { arg_no_block = true; if (!arg_dry_run) - return verb_start(0, NULL, NULL); + return verb_start(0, NULL, /* data= */ 0, NULL); } r = must_be_root(); diff --git a/src/systemctl/systemctl-compat-shutdown.c b/src/systemctl/systemctl-compat-shutdown.c index 877ea8378b375..cfc917073ae0e 100644 --- a/src/systemctl/systemctl-compat-shutdown.c +++ b/src/systemctl/systemctl-compat-shutdown.c @@ -108,14 +108,13 @@ static int parse_shutdown_time_spec(const char *t, usec_t *ret) { if (r < 0) return r; - if (tm.tm_hour != requested_hour || tm.tm_min != requested_min) { + if (tm.tm_hour != requested_hour || tm.tm_min != requested_min) log_warning("Requested shutdown time %02d:%02d does not exist. " "Rescheduling to %02d:%02d.", requested_hour, requested_min, tm.tm_hour, tm.tm_min); - } } *ret = s; diff --git a/src/systemctl/systemctl-daemon-reload.c b/src/systemctl/systemctl-daemon-reload.c index 48606dd77f0cb..fd513e69d47b4 100644 --- a/src/systemctl/systemctl-daemon-reload.c +++ b/src/systemctl/systemctl-daemon-reload.c @@ -62,7 +62,7 @@ int daemon_reload(enum action action, bool graceful) { return 1; } -int verb_daemon_reload(int argc, char *argv[], void *userdata) { +int verb_daemon_reload(int argc, char *argv[], uintptr_t _data, void *userdata) { enum action a; int r; diff --git a/src/systemctl/systemctl-daemon-reload.h b/src/systemctl/systemctl-daemon-reload.h index 0265a313f2a33..3a9785a9aaf37 100644 --- a/src/systemctl/systemctl-daemon-reload.h +++ b/src/systemctl/systemctl-daemon-reload.h @@ -5,4 +5,4 @@ int daemon_reload(enum action action, bool graceful); -int verb_daemon_reload(int argc, char *argv[], void *userdata); +int verb_daemon_reload(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-edit.c b/src/systemctl/systemctl-edit.c index a28180922a02d..ea0ca2f2f657e 100644 --- a/src/systemctl/systemctl-edit.c +++ b/src/systemctl/systemctl-edit.c @@ -20,7 +20,7 @@ #include "terminal-util.h" #include "unit-name.h" -int verb_cat(int argc, char *argv[], void *userdata) { +int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_hashmap_free_ Hashmap *cached_id_map = NULL, *cached_name_map = NULL; _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_strv_free_ char **names = NULL; @@ -324,7 +324,7 @@ static int find_paths_to_edit( return 0; } -int verb_edit(int argc, char *argv[], void *userdata) { +int verb_edit(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(edit_file_context_done) EditFileContext context = { .marker_start = DROPIN_MARKER_START, .marker_end = DROPIN_MARKER_END, diff --git a/src/systemctl/systemctl-edit.h b/src/systemctl/systemctl-edit.h index 10dac5cb2a19d..d847b8c42cde0 100644 --- a/src/systemctl/systemctl-edit.h +++ b/src/systemctl/systemctl-edit.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_cat(int argc, char *argv[], void *userdata); -int verb_edit(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_edit(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-enable.c b/src/systemctl/systemctl-enable.c index f6277b7287135..68c349a195c67 100644 --- a/src/systemctl/systemctl-enable.c +++ b/src/systemctl/systemctl-enable.c @@ -80,7 +80,7 @@ static int normalize_names(char **names) { return 0; } -int verb_enable(int argc, char *argv[], void *userdata) { +int verb_enable(int argc, char *argv[], uintptr_t data, void *userdata) { const char *verb = ASSERT_PTR(argv[0]); _cleanup_strv_free_ char **names = NULL; int carries_install_info = -1; @@ -402,7 +402,7 @@ int verb_enable(int argc, char *argv[], void *userdata) { return log_oom(); } - return verb_start(strv_length(new_args), new_args, userdata); + return verb_start(strv_length(new_args), new_args, data, userdata); } return 0; diff --git a/src/systemctl/systemctl-enable.h b/src/systemctl/systemctl-enable.h index f04bbcd62a2b2..ec1d911a7eb63 100644 --- a/src/systemctl/systemctl-enable.h +++ b/src/systemctl/systemctl-enable.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_enable(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_enable(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-is-active.c b/src/systemctl/systemctl-is-active.c index 19c24eaf7e577..6f853b5b59c39 100644 --- a/src/systemctl/systemctl-is-active.c +++ b/src/systemctl/systemctl-is-active.c @@ -57,7 +57,7 @@ static int check_unit_generic(int code, const UnitActiveState good_states[], siz return ok ? EXIT_SUCCESS : not_found ? EXIT_PROGRAM_OR_SERVICES_STATUS_UNKNOWN : code; } -int verb_is_active(int argc, char *argv[], void *userdata) { +int verb_is_active(int argc, char *argv[], uintptr_t _data, void *userdata) { static const UnitActiveState states[] = { UNIT_ACTIVE, @@ -69,7 +69,7 @@ int verb_is_active(int argc, char *argv[], void *userdata) { return check_unit_generic(EXIT_PROGRAM_NOT_RUNNING, states, ELEMENTSOF(states), strv_skip(argv, 1)); } -int verb_is_failed(int argc, char *argv[], void *userdata) { +int verb_is_failed(int argc, char *argv[], uintptr_t _data, void *userdata) { static const UnitActiveState states[] = { UNIT_FAILED, diff --git a/src/systemctl/systemctl-is-active.h b/src/systemctl/systemctl-is-active.h index 950f29ac55b62..771739f856478 100644 --- a/src/systemctl/systemctl-is-active.h +++ b/src/systemctl/systemctl-is-active.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_is_active(int argc, char *argv[], void *userdata); -int verb_is_failed(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_is_active(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_is_failed(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-is-enabled.c b/src/systemctl/systemctl-is-enabled.c index 77b8cac5f01eb..e42faf2724c7c 100644 --- a/src/systemctl/systemctl-is-enabled.c +++ b/src/systemctl/systemctl-is-enabled.c @@ -65,7 +65,7 @@ static int show_installation_targets(sd_bus *bus, const char *name) { return 0; } -int verb_is_enabled(int argc, char *argv[], void *userdata) { +int verb_is_enabled(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **names = NULL; bool not_found = true, enabled = false; int r; diff --git a/src/systemctl/systemctl-is-enabled.h b/src/systemctl/systemctl-is-enabled.h index 96dff95d6f33b..1ce1343327d1c 100644 --- a/src/systemctl/systemctl-is-enabled.h +++ b/src/systemctl/systemctl-is-enabled.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_is_enabled(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_is_enabled(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-is-system-running.c b/src/systemctl/systemctl-is-system-running.c index 943d4aa6d7a77..2270f5ad56fc6 100644 --- a/src/systemctl/systemctl-is-system-running.c +++ b/src/systemctl/systemctl-is-system-running.c @@ -25,7 +25,7 @@ static int match_startup_finished(sd_bus_message *m, void *userdata, sd_bus_erro return 0; } -int verb_is_system_running(int argc, char *argv[], void *userdata) { +int verb_is_system_running(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_startup_finished = NULL; _cleanup_(sd_event_unrefp) sd_event* event = NULL; diff --git a/src/systemctl/systemctl-is-system-running.h b/src/systemctl/systemctl-is-system-running.h index de86211a912da..ebe80aed9d702 100644 --- a/src/systemctl/systemctl-is-system-running.h +++ b/src/systemctl/systemctl-is-system-running.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_is_system_running(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_is_system_running(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-kill.c b/src/systemctl/systemctl-kill.c index 1452deb5b7c90..575ef1e4193e2 100644 --- a/src/systemctl/systemctl-kill.c +++ b/src/systemctl/systemctl-kill.c @@ -13,7 +13,7 @@ #include "systemctl-kill.h" #include "systemctl-util.h" -int verb_kill(int argc, char *argv[], void *userdata) { +int verb_kill(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *w = NULL; _cleanup_strv_free_ char **names = NULL; const char *kill_whom; diff --git a/src/systemctl/systemctl-kill.h b/src/systemctl/systemctl-kill.h index 88b2eae4b29b9..54e8bbe262995 100644 --- a/src/systemctl/systemctl-kill.h +++ b/src/systemctl/systemctl-kill.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_kill(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_kill(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-list-dependencies.c b/src/systemctl/systemctl-list-dependencies.c index 65f12ea473977..4e7c12e6b9e12 100644 --- a/src/systemctl/systemctl-list-dependencies.c +++ b/src/systemctl/systemctl-list-dependencies.c @@ -82,6 +82,9 @@ static int list_dependencies_print(const char *name, UnitActiveState state, int } static int list_dependencies_compare(char * const *a, char * const *b) { + assert(a); + assert(b); + if (unit_name_to_type(*a) == UNIT_TARGET && unit_name_to_type(*b) != UNIT_TARGET) return 1; if (unit_name_to_type(*a) != UNIT_TARGET && unit_name_to_type(*b) == UNIT_TARGET) @@ -167,7 +170,7 @@ static int list_dependencies_one( return 0; } -int verb_list_dependencies(int argc, char *argv[], void *userdata) { +int verb_list_dependencies(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **units = NULL, **done = NULL; char **patterns; sd_bus *bus; diff --git a/src/systemctl/systemctl-list-dependencies.h b/src/systemctl/systemctl-list-dependencies.h index 1e68a5f9f05df..53b68085acd63 100644 --- a/src/systemctl/systemctl-list-dependencies.h +++ b/src/systemctl/systemctl-list-dependencies.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_list_dependencies(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_list_dependencies(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-list-jobs.c b/src/systemctl/systemctl-list-jobs.c index 9049873bbe329..e5ad25234f628 100644 --- a/src/systemctl/systemctl-list-jobs.c +++ b/src/systemctl/systemctl-list-jobs.c @@ -115,9 +115,9 @@ static int output_jobs_list(sd_bus *bus, const struct job_info* jobs, unsigned n output_waiting_jobs(bus, table, j->id, "GetJobBefore", "\twaiting for job"); } - r = table_print(table, NULL); + r = table_print_or_warn(table); if (r < 0) - return log_error_errno(r, "Failed to print the table: %m"); + return r; if (arg_legend != 0) { on = ansi_highlight(); @@ -133,7 +133,7 @@ static bool output_show_job(struct job_info *job, char **patterns) { return strv_fnmatch_or_empty(patterns, job->name, FNM_NOESCAPE); } -int verb_list_jobs(int argc, char *argv[], void *userdata) { +int verb_list_jobs(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ struct job_info *jobs = NULL; diff --git a/src/systemctl/systemctl-list-jobs.h b/src/systemctl/systemctl-list-jobs.h index b10ec79b3ec98..f8fe9b6302c77 100644 --- a/src/systemctl/systemctl-list-jobs.h +++ b/src/systemctl/systemctl-list-jobs.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_list_jobs(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_list_jobs(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-list-machines.c b/src/systemctl/systemctl-list-machines.c index ea83f43a737fa..a5d0376c27238 100644 --- a/src/systemctl/systemctl-list-machines.c +++ b/src/systemctl/systemctl-list-machines.c @@ -231,7 +231,7 @@ static int output_machines_list(struct machine_info *machine_infos, unsigned n) return 0; } -int verb_list_machines(int argc, char *argv[], void *userdata) { +int verb_list_machines(int argc, char *argv[], uintptr_t _data, void *userdata) { struct machine_info *machine_infos = NULL; sd_bus *bus; int r, rc; diff --git a/src/systemctl/systemctl-list-machines.h b/src/systemctl/systemctl-list-machines.h index f1dd8353e1e51..fd0331604b5a0 100644 --- a/src/systemctl/systemctl-list-machines.h +++ b/src/systemctl/systemctl-list-machines.h @@ -4,7 +4,7 @@ #include "bus-map-properties.h" #include "shared-forward.h" -int verb_list_machines(int argc, char *argv[], void *userdata); +int verb_list_machines(int argc, char *argv[], uintptr_t _data, void *userdata); struct machine_info { bool is_host; diff --git a/src/systemctl/systemctl-list-unit-files.c b/src/systemctl/systemctl-list-unit-files.c index 548b2573fc4ec..e0074974eeb80 100644 --- a/src/systemctl/systemctl-list-unit-files.c +++ b/src/systemctl/systemctl-list-unit-files.c @@ -173,7 +173,7 @@ static int output_unit_file_list(const UnitFileList *units, unsigned c) { return 0; } -int verb_list_unit_files(int argc, char *argv[], void *userdata) { +int verb_list_unit_files(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_hashmap_free_ Hashmap *h = NULL; _cleanup_free_ UnitFileList *units = NULL; diff --git a/src/systemctl/systemctl-list-unit-files.h b/src/systemctl/systemctl-list-unit-files.h index 4819fbd820015..150d392f00ef4 100644 --- a/src/systemctl/systemctl-list-unit-files.h +++ b/src/systemctl/systemctl-list-unit-files.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_list_unit_files(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_list_unit_files(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-list-units.c b/src/systemctl/systemctl-list-units.c index 7e16b4377b4e1..9a8cd89295b5c 100644 --- a/src/systemctl/systemctl-list-units.c +++ b/src/systemctl/systemctl-list-units.c @@ -258,7 +258,7 @@ static int output_units_list(const UnitInfo *unit_infos, size_t c) { return 0; } -int verb_list_units(int argc, char *argv[], void *userdata) { +int verb_list_units(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ UnitInfo *unit_infos = NULL; _cleanup_set_free_ Set *replies = NULL; sd_bus *bus; @@ -490,7 +490,7 @@ static int output_sockets_list(const SocketInfo *sockets, size_t n_sockets) { return 0; } -int verb_list_sockets(int argc, char *argv[], void *userdata) { +int verb_list_sockets(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_set_free_ Set *replies = NULL; _cleanup_strv_free_ char **sockets_with_suffix = NULL; _cleanup_free_ UnitInfo *unit_infos = NULL; @@ -771,7 +771,7 @@ static int add_timer_info( return 0; } -int verb_list_timers(int argc, char *argv[], void *userdata) { +int verb_list_timers(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_set_free_ Set *replies = NULL; _cleanup_strv_free_ char **timers_with_suffix = NULL; _cleanup_free_ UnitInfo *unit_infos = NULL; @@ -970,7 +970,7 @@ static int output_automounts_list(const AutomountInfo *infos, size_t n_infos) { return 0; } -int verb_list_automounts(int argc, char *argv[], void *userdata) { +int verb_list_automounts(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_set_free_ Set *replies = NULL; _cleanup_strv_free_ char **names = NULL; _cleanup_free_ UnitInfo *unit_infos = NULL; @@ -1178,7 +1178,7 @@ static int output_paths_list(const PathInfo *paths, size_t n_paths) { return 0; } -int verb_list_paths(int argc, char *argv[], void *userdata) { +int verb_list_paths(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_set_free_ Set *replies = NULL; _cleanup_strv_free_ char **units = NULL; _cleanup_free_ UnitInfo *unit_infos = NULL; diff --git a/src/systemctl/systemctl-list-units.h b/src/systemctl/systemctl-list-units.h index 74bf9cda166a0..e3c4f24ececb1 100644 --- a/src/systemctl/systemctl-list-units.h +++ b/src/systemctl/systemctl-list-units.h @@ -3,10 +3,10 @@ #include "time-util.h" -int verb_list_units(int argc, char *argv[], void *userdata); -int verb_list_sockets(int argc, char *argv[], void *userdata); -int verb_list_timers(int argc, char *argv[], void *userdata); -int verb_list_automounts(int argc, char *argv[], void *userdata); -int verb_list_paths(int argc, char *argv[], void *userdata); +int verb_list_units(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_list_sockets(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_list_timers(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_list_automounts(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_list_paths(int argc, char *argv[], uintptr_t _data, void *userdata); usec_t calc_next_elapse(const dual_timestamp *nw, const dual_timestamp *next); diff --git a/src/systemctl/systemctl-log-setting.c b/src/systemctl/systemctl-log-setting.c index 1ea3d7abefad4..e181af4a97aff 100644 --- a/src/systemctl/systemctl-log-setting.c +++ b/src/systemctl/systemctl-log-setting.c @@ -26,7 +26,7 @@ static void give_log_control1_hint(const char *name) { " See the %s for details.", link ?: "org.freedesktop.LogControl1(5) man page"); } -int verb_log_setting(int argc, char *argv[], void *userdata) { +int verb_log_setting(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus; int r; @@ -44,6 +44,8 @@ static int service_name_to_dbus(sd_bus *bus, const char *name, char **ret_dbus_n _cleanup_free_ char *bus_name = NULL; int r; + assert(ret_dbus_name); + /* First, look for the BusName= property */ _cleanup_free_ char *dbus_path = unit_dbus_path_from_name(name); if (!dbus_path) @@ -71,7 +73,7 @@ static int service_name_to_dbus(sd_bus *bus, const char *name, char **ret_dbus_n return 0; } -int verb_service_log_setting(int argc, char *argv[], void *userdata) { +int verb_service_log_setting(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus; _cleanup_free_ char *unit = NULL, *dbus_name = NULL; int r; diff --git a/src/systemctl/systemctl-log-setting.h b/src/systemctl/systemctl-log-setting.h index 910d6c8af5c81..bf8bfc3229c12 100644 --- a/src/systemctl/systemctl-log-setting.h +++ b/src/systemctl/systemctl-log-setting.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_log_setting(int argc, char *argv[], void *userdata); -int verb_service_log_setting(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_log_setting(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_service_log_setting(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-main.c b/src/systemctl/systemctl-main.c index 3b8a5e9088e4a..4d1830071f47e 100644 --- a/src/systemctl/systemctl-main.c +++ b/src/systemctl/systemctl-main.c @@ -65,9 +65,9 @@ static int systemctl_main(int argc, char *argv[]) { { "reload", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, { "restart", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, { "try-restart", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, - { "enqueue-marked-jobs", 1, 1, VERB_ONLINE_ONLY, verb_start }, + { "enqueue-marked", 1, 1, VERB_ONLINE_ONLY, verb_start }, { "reload-or-restart", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, - { "reload-or-try-restart", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, /* For compatibility with old systemctl <= 228 */ + { "reload-or-try-restart", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, /* For compatibility with systemctl <= 228 */ { "try-reload-or-restart", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, { "force-reload", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, /* For compatibility with SysV */ { "condreload", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, /* For compatibility with ALTLinux */ @@ -135,7 +135,7 @@ static int systemctl_main(int argc, char *argv[]) { {} }; - const Verb *verb = verbs_find_verb(argv[optind], verbs); + const Verb *verb = verbs_find_verb(argv[optind], verbs, verbs + ELEMENTSOF(verbs) - 1); if (verb && (verb->flags & VERB_ONLINE_ONLY) && arg_root) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Verb '%s' cannot be used with --root= or --image=.", diff --git a/src/systemctl/systemctl-mount.c b/src/systemctl/systemctl-mount.c index bc4aa92260f83..23720d53fac4d 100644 --- a/src/systemctl/systemctl-mount.c +++ b/src/systemctl/systemctl-mount.c @@ -14,7 +14,7 @@ #include "systemctl-util.h" #include "unit-name.h" -int verb_bind(int argc, char *argv[], void *userdata) { +int verb_bind(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *n = NULL; sd_bus *bus; @@ -48,7 +48,7 @@ int verb_bind(int argc, char *argv[], void *userdata) { return 0; } -int verb_mount_image(int argc, char *argv[], void *userdata) { +int verb_mount_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; const char *unit = argv[1], *src = argv[2], *dest = argv[3]; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; diff --git a/src/systemctl/systemctl-mount.h b/src/systemctl/systemctl-mount.h index b2d075001693b..a4a9760b3447a 100644 --- a/src/systemctl/systemctl-mount.h +++ b/src/systemctl/systemctl-mount.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_bind(int argc, char *argv[], void *userdata); -int verb_mount_image(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_bind(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_mount_image(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-preset-all.c b/src/systemctl/systemctl-preset-all.c index f621d55895614..2687a841d11d2 100644 --- a/src/systemctl/systemctl-preset-all.c +++ b/src/systemctl/systemctl-preset-all.c @@ -13,7 +13,7 @@ #include "systemctl-util.h" #include "verbs.h" -int verb_preset_all(int argc, char *argv[], void *userdata) { +int verb_preset_all(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; if (should_bypass("SYSTEMD_PRESET")) diff --git a/src/systemctl/systemctl-preset-all.h b/src/systemctl/systemctl-preset-all.h index 4631e7ea311fc..178ca31291fc3 100644 --- a/src/systemctl/systemctl-preset-all.h +++ b/src/systemctl/systemctl-preset-all.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_preset_all(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_preset_all(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-reset-failed.c b/src/systemctl/systemctl-reset-failed.c index 18ca190517844..8e14174f5ed6b 100644 --- a/src/systemctl/systemctl-reset-failed.c +++ b/src/systemctl/systemctl-reset-failed.c @@ -10,13 +10,13 @@ #include "systemctl-trivial-method.h" #include "systemctl-util.h" -int verb_reset_failed(int argc, char *argv[], void *userdata) { +int verb_reset_failed(int argc, char *argv[], uintptr_t data, void *userdata) { _cleanup_strv_free_ char **names = NULL; sd_bus *bus; int r, q; if (argc <= 1) /* Shortcut to trivial_method() if no argument is given */ - return verb_trivial_method(argc, argv, userdata); + return verb_trivial_method(argc, argv, data, userdata); r = acquire_bus(BUS_MANAGER, &bus); if (r < 0) diff --git a/src/systemctl/systemctl-reset-failed.h b/src/systemctl/systemctl-reset-failed.h index 5da0659d6ec40..6ad714cf4a7eb 100644 --- a/src/systemctl/systemctl-reset-failed.h +++ b/src/systemctl/systemctl-reset-failed.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_reset_failed(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_reset_failed(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-service-watchdogs.c b/src/systemctl/systemctl-service-watchdogs.c index 632345b405654..7d563dacf5a98 100644 --- a/src/systemctl/systemctl-service-watchdogs.c +++ b/src/systemctl/systemctl-service-watchdogs.c @@ -10,7 +10,7 @@ #include "systemctl-service-watchdogs.h" #include "systemctl-util.h" -int verb_service_watchdogs(int argc, char *argv[], void *userdata) { +int verb_service_watchdogs(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int b, r; diff --git a/src/systemctl/systemctl-service-watchdogs.h b/src/systemctl/systemctl-service-watchdogs.h index 2f59f5a3f4376..e626a223c35f5 100644 --- a/src/systemctl/systemctl-service-watchdogs.h +++ b/src/systemctl/systemctl-service-watchdogs.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_service_watchdogs(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_service_watchdogs(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-set-default.c b/src/systemctl/systemctl-set-default.c index c4faa31b4c17b..ac4e39203780d 100644 --- a/src/systemctl/systemctl-set-default.c +++ b/src/systemctl/systemctl-set-default.c @@ -88,7 +88,7 @@ static int determine_default(char **ret_name) { } } -int verb_get_default(int argc, char *argv[], void *userdata) { +int verb_get_default(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *name = NULL; int r; @@ -103,7 +103,7 @@ int verb_get_default(int argc, char *argv[], void *userdata) { return 0; } -int verb_set_default(int argc, char *argv[], void *userdata) { +int verb_set_default(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *unit = NULL; int r; diff --git a/src/systemctl/systemctl-set-default.h b/src/systemctl/systemctl-set-default.h index 7873e126780a3..7df9e78c4f020 100644 --- a/src/systemctl/systemctl-set-default.h +++ b/src/systemctl/systemctl-set-default.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_get_default(int argc, char *argv[], void *userdata); -int verb_set_default(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_get_default(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_set_default(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-set-environment.c b/src/systemctl/systemctl-set-environment.c index 7e04f5a867337..008608fb8c22c 100644 --- a/src/systemctl/systemctl-set-environment.c +++ b/src/systemctl/systemctl-set-environment.c @@ -70,7 +70,7 @@ static int print_variable(const char *s) { return 0; } -int verb_show_environment(int argc, char *argv[], void *userdata) { +int verb_show_environment(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; const char *text; @@ -122,7 +122,7 @@ static void invalid_callback(const char *p, void *userdata) { log_debug("Ignoring invalid environment assignment \"%s\".", strnull(t)); } -int verb_set_environment(int argc, char *argv[], void *userdata) { +int verb_set_environment(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; const char *method; @@ -157,7 +157,7 @@ int verb_set_environment(int argc, char *argv[], void *userdata) { return 0; } -int verb_import_environment(int argc, char *argv[], void *userdata) { +int verb_import_environment(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; sd_bus *bus; diff --git a/src/systemctl/systemctl-set-environment.h b/src/systemctl/systemctl-set-environment.h index 404258aa43cb3..659afd53c5148 100644 --- a/src/systemctl/systemctl-set-environment.h +++ b/src/systemctl/systemctl-set-environment.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_show_environment(int argc, char *argv[], void *userdata); -int verb_set_environment(int argc, char *argv[], void *userdata); -int verb_import_environment(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_show_environment(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_set_environment(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_import_environment(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-set-property.c b/src/systemctl/systemctl-set-property.c index e84e3d580f0f6..da8bfe3162936 100644 --- a/src/systemctl/systemctl-set-property.c +++ b/src/systemctl/systemctl-set-property.c @@ -51,7 +51,7 @@ static int set_property_one(sd_bus *bus, const char *name, char **properties) { return 0; } -int verb_set_property(int argc, char *argv[], void *userdata) { +int verb_set_property(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus; _cleanup_strv_free_ char **names = NULL; int r; diff --git a/src/systemctl/systemctl-set-property.h b/src/systemctl/systemctl-set-property.h index 0892291d59cd8..e1dc8dc049c91 100644 --- a/src/systemctl/systemctl-set-property.h +++ b/src/systemctl/systemctl-set-property.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_set_property(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_set_property(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c index c35db87c45a88..570aab7365922 100644 --- a/src/systemctl/systemctl-show.c +++ b/src/systemctl/systemctl-show.c @@ -320,6 +320,9 @@ static void unit_status_info_done(UnitStatusInfo *info) { } static void format_active_state(const char *active_state, const char **active_on, const char **active_off) { + assert(active_on); + assert(active_off); + if (streq_ptr(active_state, "failed")) { *active_on = ansi_highlight_red(); *active_off = ansi_normal(); @@ -2482,7 +2485,7 @@ static int show_system_status(sd_bus *bus) { return 0; } -int verb_show(int argc, char *argv[], void *userdata) { +int verb_show(int argc, char *argv[], uintptr_t _data, void *userdata) { bool new_line = false, ellipsized = false; SystemctlShowMode show_mode; int r, ret = 0; diff --git a/src/systemctl/systemctl-show.h b/src/systemctl/systemctl-show.h index 5aeed51e5b4a0..4ca15bca74528 100644 --- a/src/systemctl/systemctl-show.h +++ b/src/systemctl/systemctl-show.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_show(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_show(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c index 71927846deebc..fd38c678ea69a 100644 --- a/src/systemctl/systemctl-start-special.c +++ b/src/systemctl/systemctl-start-special.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include #include "sd-bus.h" @@ -8,6 +9,7 @@ #include "bus-error.h" #include "bus-locator.h" #include "efivars.h" +#include "fd-util.h" #include "log.h" #include "parse-util.h" #include "path-util.h" @@ -23,19 +25,17 @@ #include "systemctl-util.h" static int load_kexec_kernel(void) { - _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; - _cleanup_free_ char *kernel = NULL, *initrd = NULL, *options = NULL; - const BootEntry *e; int r; if (kexec_loaded()) { + if (arg_kernel_cmdline) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--kernel-cmdline= specified but kexec kernel already loaded"); log_debug("Kexec kernel already loaded."); return 0; } - if (access(KEXEC, X_OK) < 0) - return log_error_errno(errno, KEXEC" is not available: %m"); - + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; r = boot_config_load_auto(&config, NULL, NULL); if (r == -ENOKEY) /* The call doesn't log about ENOKEY, let's do so here. */ @@ -51,7 +51,7 @@ static int load_kexec_kernel(void) { if (r < 0) return r; - e = boot_config_default_entry(&config); + const BootEntry *e = boot_config_default_entry(&config); if (!e) return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No boot loader entry suitable as default, refusing to guess."); @@ -65,29 +65,85 @@ static int load_kexec_kernel(void) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Boot entry specifies multiple initrds, which is not supported currently."); + _cleanup_free_ char *kernel = NULL; kernel = path_join(e->root, e->kernel); if (!kernel) return log_oom(); + _cleanup_free_ char *initrd = NULL; if (!strv_isempty(e->initrd)) { initrd = path_join(e->root, e->initrd[0]); if (!initrd) return log_oom(); } - options = strv_join(e->options, " "); + _cleanup_free_ char *options = strv_join(e->options, " "); if (!options) return log_oom(); + if (!isempty(arg_kernel_cmdline) && !strextend_with_separator(&options, " ", arg_kernel_cmdline)) + return log_oom(); + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, - "%s "KEXEC" --load \"%s\" --append \"%s\"%s%s%s", - arg_dry_run ? "Would run" : "Running", + "%s %s kernel=\"%s\" cmdline=\"%s\"%s%s%s", + arg_dry_run ? "Would call" : "Calling", + HAVE_KEXEC_FILE_LOAD_SYSCALL ? "kexec_file_load()" : "kexec", kernel, options, - initrd ? " --initrd \"" : NULL, strempty(initrd), initrd ? "\"" : ""); + initrd ? " initrd=\"" : "", strempty(initrd), initrd ? "\"" : ""); if (arg_dry_run) return 0; +#if HAVE_KEXEC_FILE_LOAD_SYSCALL + _cleanup_close_ int kernel_fd = open(kernel, O_RDONLY|O_CLOEXEC); + if (kernel_fd < 0) + return log_error_errno(errno, "Failed to open kernel '%s': %m", kernel); + + _cleanup_close_ int initrd_fd = -EBADF; + if (initrd) { + initrd_fd = open(initrd, O_RDONLY|O_CLOEXEC); + if (initrd_fd < 0) + return log_error_errno(errno, "Failed to open initrd '%s': %m", initrd); + } + + unsigned long flags = initrd ? 0 : KEXEC_FILE_NO_INITRAMFS; + + if (kexec_file_load(kernel_fd, initrd_fd, strlen(options) + 1, options, flags) >= 0) + return 0; + + int saved_errno = errno; + + if (saved_errno == ENOEXEC) { + /* The kernel didn't recognize the image format. Try decompressing or extracting the + * kernel (e.g. compressed Image, ZBOOT PE, or UKI) and loading again. */ + log_debug_errno(saved_errno, "Kernel rejected image, trying decompression/extraction: %m"); + + _cleanup_close_ int extracted_kernel_fd = -EBADF, extracted_initrd_fd = -EBADF; + r = kexec_maybe_decompress_kernel( + kernel, kernel_fd, &extracted_kernel_fd, + initrd_fd >= 0 ? NULL : &extracted_initrd_fd); + if (r < 0) + log_debug_errno(r, "Failed to decompress/extract kernel image, ignoring: %m"); + else if (r > 0) { + int final_initrd_fd = initrd_fd >= 0 ? initrd_fd : extracted_initrd_fd; + unsigned long final_flags = final_initrd_fd >= 0 ? 0 : KEXEC_FILE_NO_INITRAMFS; + + if (kexec_file_load(extracted_kernel_fd, final_initrd_fd, strlen(options) + 1, options, final_flags) >= 0) + return 0; + + saved_errno = errno; + } + } + + log_debug_errno(saved_errno, "kexec_file_load() failed, falling back to " KEXEC " binary: %m"); +#endif + + /* Fall back to kexec binary for architectures without kexec_file_load() or when the + * syscall fails (e.g. the kernel's kexec handler doesn't support this image format + * but kexec-tools might via the older kexec_load() code path). */ + if (access(KEXEC, X_OK) < 0) + return log_error_errno(errno, KEXEC " is not available: %m"); + r = pidref_safe_fork( "(kexec)", FORK_WAIT|FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, @@ -127,7 +183,7 @@ static int set_exit_code(uint8_t code) { return 0; } -int verb_start_special(int argc, char *argv[], void *userdata) { +int verb_start_special(int argc, char *argv[], uintptr_t data, void *userdata) { bool termination_action; /* An action that terminates the system, can be performed also by signal. */ enum action a; int r; @@ -198,7 +254,7 @@ int verb_start_special(int argc, char *argv[], void *userdata) { if (arg_force >= 1 && (termination_action || IN_SET(a, ACTION_KEXEC, ACTION_EXIT))) - r = verb_trivial_method(argc, argv, userdata); + r = verb_trivial_method(argc, argv, data, userdata); else { /* First try logind, to allow authentication with polkit */ switch (a) { @@ -255,7 +311,7 @@ int verb_start_special(int argc, char *argv[], void *userdata) { ; } - r = verb_start(argc, argv, userdata); + r = verb_start(argc, argv, data, userdata); } if (termination_action && arg_force < 2 && @@ -265,7 +321,7 @@ int verb_start_special(int argc, char *argv[], void *userdata) { return r; } -int verb_start_system_special(int argc, char *argv[], void *userdata) { +int verb_start_system_special(int argc, char *argv[], uintptr_t data, void *userdata) { /* Like start_special above, but raises an error when running in user mode */ if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM) @@ -273,5 +329,5 @@ int verb_start_system_special(int argc, char *argv[], void *userdata) { "Bad action for %s mode.", runtime_scope_cmdline_option_to_string(arg_runtime_scope)); - return verb_start_special(argc, argv, userdata); + return verb_start_special(argc, argv, data, userdata); } diff --git a/src/systemctl/systemctl-start-special.h b/src/systemctl/systemctl-start-special.h index 9396321d7064e..93df5f89b7d94 100644 --- a/src/systemctl/systemctl-start-special.h +++ b/src/systemctl/systemctl-start-special.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_start_special(int argc, char *argv[], void *userdata); -int verb_start_system_special(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_start_special(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_start_system_special(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-start-unit.c b/src/systemctl/systemctl-start-unit.c index 6a2981d9f7a21..1b6dbd8b14040 100644 --- a/src/systemctl/systemctl-start-unit.c +++ b/src/systemctl/systemctl-start-unit.c @@ -289,7 +289,7 @@ static const char** make_extra_args(const char *extra_args[static 4]) { return extra_args; } -int verb_start(int argc, char *argv[], void *userdata) { +int verb_start(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *wu = NULL; _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; const char *method, *job_type, *mode, *one_name, *suffix = NULL; @@ -332,16 +332,16 @@ int verb_start(int argc, char *argv[], void *userdata) { job_type = "start"; mode = "isolate"; suffix = ".target"; - } else if (streq(argv[0], "enqueue-marked-jobs") || arg_marked) { + } else if (streq(argv[0], "enqueue-marked") || arg_marked) { is_enqueue_marked_jobs = true; method = job_type = mode = NULL; if (arg_show_transaction) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--show-transaction is not supported for enqueue-marked-jobs."); + "--show-transaction is not supported for enqueue-marked."); if (arg_marked) - log_warning("--marked is deprecated. Please use systemctl enqueue-marked-jobs instead."); + log_warning("--marked is deprecated. Please use 'systemctl enqueue-marked' instead."); } else { /* A command in style of "systemctl start …", "systemctl stop …" and so on */ @@ -402,7 +402,7 @@ int verb_start(int argc, char *argv[], void *userdata) { ret = enqueue_marked_jobs(bus, w); else { if (arg_verbose) - (void) journal_fork(arg_runtime_scope, names, &journal_pid); + (void) journal_fork(arg_runtime_scope, names, arg_output, &journal_pid); STRV_FOREACH(name, names) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; diff --git a/src/systemctl/systemctl-start-unit.h b/src/systemctl/systemctl-start-unit.h index 28650167731ef..02434a2db6c76 100644 --- a/src/systemctl/systemctl-start-unit.h +++ b/src/systemctl/systemctl-start-unit.h @@ -3,7 +3,7 @@ #include "systemctl.h" -int verb_start(int argc, char *argv[], void *userdata); +int verb_start(int argc, char *argv[], uintptr_t _data, void *userdata); struct action_metadata { const char *target; diff --git a/src/systemctl/systemctl-switch-root.c b/src/systemctl/systemctl-switch-root.c index 62aebe886e611..27fccf7f41748 100644 --- a/src/systemctl/systemctl-switch-root.c +++ b/src/systemctl/systemctl-switch-root.c @@ -38,7 +38,7 @@ static int same_file_in_root( return stat_inode_same(&sta, &stb); } -int verb_switch_root(int argc, char *argv[], void *userdata) { +int verb_switch_root(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *cmdline_init = NULL; const char *root, *init; diff --git a/src/systemctl/systemctl-switch-root.h b/src/systemctl/systemctl-switch-root.h index e9ba12baf799f..46d336430641b 100644 --- a/src/systemctl/systemctl-switch-root.h +++ b/src/systemctl/systemctl-switch-root.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_switch_root(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_switch_root(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-trivial-method.c b/src/systemctl/systemctl-trivial-method.c index 3fa1272c665c2..1e94357f4acf2 100644 --- a/src/systemctl/systemctl-trivial-method.c +++ b/src/systemctl/systemctl-trivial-method.c @@ -12,7 +12,7 @@ /* A generic implementation for cases we just need to invoke a simple method call on the Manager object. */ -int verb_trivial_method(int argc, char *argv[], void *userdata) { +int verb_trivial_method(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; const char *method; sd_bus *bus; diff --git a/src/systemctl/systemctl-trivial-method.h b/src/systemctl/systemctl-trivial-method.h index d36b4803d4338..ed901c14a841b 100644 --- a/src/systemctl/systemctl-trivial-method.h +++ b/src/systemctl/systemctl-trivial-method.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_trivial_method(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_trivial_method(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-util.c b/src/systemctl/systemctl-util.c index ef7bce9e7f1cc..b278f784ba3ec 100644 --- a/src/systemctl/systemctl-util.c +++ b/src/systemctl/systemctl-util.c @@ -919,7 +919,7 @@ int output_table(Table *table) { if (OUTPUT_MODE_IS_JSON(arg_output)) r = table_print_json(table, NULL, output_mode_to_json_format_flags(arg_output) | SD_JSON_FORMAT_COLOR_AUTO); else - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); diff --git a/src/systemctl/systemctl-whoami.c b/src/systemctl/systemctl-whoami.c index bf38eb2236b14..77ccef8d134ea 100644 --- a/src/systemctl/systemctl-whoami.c +++ b/src/systemctl/systemctl-whoami.c @@ -11,7 +11,7 @@ #include "systemctl-util.h" #include "systemctl-whoami.h" -int verb_whoami(int argc, char *argv[], void *userdata) { +int verb_whoami(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus; int r, ret = 0; diff --git a/src/systemctl/systemctl-whoami.h b/src/systemctl/systemctl-whoami.h index abdd13b34fc1e..9bdefdb14a310 100644 --- a/src/systemctl/systemctl-whoami.h +++ b/src/systemctl/systemctl-whoami.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_whoami(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_whoami(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 0c9dd3b0afb01..4f76c5150021f 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -10,6 +10,7 @@ #include "bus-util.h" #include "capsule-util.h" #include "extract-word.h" +#include "help-util.h" #include "image-policy.h" #include "install.h" #include "output-mode.h" @@ -17,7 +18,6 @@ #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" -#include "pretty-print.h" #include "static-destruct.h" #include "string-table.h" #include "string-util.h" @@ -68,6 +68,7 @@ char *arg_image = NULL; usec_t arg_when = 0; bool arg_stdin = false; const char *arg_reboot_argument = NULL; +char *arg_kernel_cmdline = NULL; enum action arg_action = ACTION_SYSTEMCTL; BusTransport arg_transport = BUS_TRANSPORT_LOCAL; const char *arg_host = NULL; @@ -98,6 +99,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_kill_whom, unsetp); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_reboot_argument, unsetp); +STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline, freep); STATIC_DESTRUCTOR_REGISTER(arg_host, unsetp); STATIC_DESTRUCTOR_REGISTER(arg_boot_loader_entry, unsetp); STATIC_DESTRUCTOR_REGISTER(arg_clean_what, strv_freep); @@ -106,19 +108,13 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_kill_subgroup, freep); static int systemctl_help(void) { - _cleanup_free_ char *link = NULL; - int r; - pager_open(arg_pager_flags); - r = terminal_urlify_man("systemctl", "1", &link); - if (r < 0) - return log_oom(); + help_cmdline("[OPTIONS...] COMMAND ..."); + help_abstract("Query or send control commands to the system manager."); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sQuery or send control commands to the system manager.%6$s\n" - "\n%3$sUnit Commands:%4$s\n" - " list-units [PATTERN...] List units currently in memory\n" + help_section("Unit Commands:"); + printf(" list-units [PATTERN...] List units currently in memory\n" " list-automounts [PATTERN...] List automount units currently in memory,\n" " ordered by path\n" " list-paths [PATTERN...] List path units currently in memory,\n" @@ -143,7 +139,7 @@ static int systemctl_help(void) { " reload UNIT... Reload one or more units\n" " restart UNIT... Start or restart one or more units\n" " try-restart UNIT... Restart one or more units if active\n" - " enqueue-marked-jobs Enqueue all marked unit jobs\n" + " enqueue-marked Enqueue jobs for all marked units\n" " reload-or-restart UNIT... Reload one or more units if possible,\n" " otherwise start or restart\n" " try-reload-or-restart UNIT... If active, reload one or more units,\n" @@ -164,9 +160,10 @@ static int systemctl_help(void) { " reset-failed [PATTERN...] Reset failed state for all, one, or more\n" " units\n" " whoami [PID...] Return unit caller or specified PIDs are\n" - " part of\n" - "\n%3$sUnit File Commands:%4$s\n" - " list-unit-files [PATTERN...] List installed unit files\n" + " part of\n"); + + help_section("Unit File Commands:"); + printf(" list-unit-files [PATTERN...] List installed unit files\n" " enable [UNIT...|PATH...] Enable one or more unit files\n" " disable UNIT... Disable one or more unit files\n" " reenable UNIT... Reenable one or more unit files\n" @@ -187,25 +184,30 @@ static int systemctl_help(void) { " on specified one or more units\n" " edit UNIT... Edit one or more unit files\n" " get-default Get the name of the default target\n" - " set-default TARGET Set the default target\n" - "\n%3$sMachine Commands:%4$s\n" - " list-machines [PATTERN...] List local containers and host\n" - "\n%3$sJob Commands:%4$s\n" - " list-jobs [PATTERN...] List jobs\n" - " cancel [JOB...] Cancel all, one, or more jobs\n" - "\n%3$sEnvironment Commands:%4$s\n" - " show-environment Dump environment\n" + " set-default TARGET Set the default target\n"); + + help_section("Machine Commands:"); + printf(" list-machines [PATTERN...] List local containers and host\n"); + + help_section("Job Commands:"); + printf(" list-jobs [PATTERN...] List jobs\n" + " cancel [JOB...] Cancel all, one, or more jobs\n"); + + help_section("Environment Commands:"); + printf(" show-environment Dump environment\n" " set-environment VARIABLE=VALUE... Set one or more environment variables\n" " unset-environment VARIABLE... Unset one or more environment variables\n" - " import-environment VARIABLE... Import all or some environment variables\n" - "\n%3$sManager State Commands:%4$s\n" - " daemon-reload Reload systemd manager configuration\n" + " import-environment VARIABLE... Import all or some environment variables\n"); + + help_section("Manager State Commands:"); + printf(" daemon-reload Reload systemd manager configuration\n" " daemon-reexec Reexecute systemd manager\n" " log-level [LEVEL] Get/set logging threshold for manager\n" " log-target [TARGET] Get/set logging target for manager\n" - " service-watchdogs [BOOL] Get/set service watchdog state\n" - "\n%3$sSystem Commands:%4$s\n" - " is-system-running Check whether system is fully running\n" + " service-watchdogs [BOOL] Get/set service watchdog state\n"); + + help_section("System Commands:"); + printf(" is-system-running Check whether system is fully running\n" " default Enter system default mode\n" " rescue Enter system rescue mode\n" " emergency Enter system emergency mode\n" @@ -222,9 +224,10 @@ static int systemctl_help(void) { " hibernate Hibernate the system\n" " hybrid-sleep Hibernate and suspend the system\n" " suspend-then-hibernate Suspend the system, wake after a period of\n" - " time, and hibernate" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" + " time, and hibernate\n"); + + help_section("Options:"); + printf(" -h --help Show this help\n" " --version Show package version\n" " --system Connect to system manager\n" " --user Connect to user service manager\n" @@ -305,6 +308,9 @@ static int systemctl_help(void) { " Boot into a specific boot loader entry on next boot\n" " --reboot-argument=ARG\n" " Specify argument string to pass to reboot()\n" + " --kernel-cmdline=CMDLINE\n" + " Append to the kernel command line when loading the\n" + " kernel from the booted boot loader entry\n" " --plain Print unit dependencies as a list instead of a tree\n" " --timestamp=FORMAT Change format of printed timestamps (pretty, unix,\n" " us, utc, us+utc)\n" @@ -314,14 +320,9 @@ static int systemctl_help(void) { " --drop-in=NAME Edit unit files using the specified drop-in file name\n" " --when=TIME Schedule halt/power-off/reboot/kexec action after\n" " a certain timestamp\n" - " --stdin Read new contents of edited file from stdin\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + " --stdin Read new contents of edited file from stdin\n"); + + help_man_page_reference("systemctl", "1"); return 0; } @@ -434,6 +435,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { ARG_WAIT, ARG_WHAT, ARG_REBOOT_ARG, + ARG_KERNEL_CMDLINE, ARG_TIMESTAMP_STYLE, ARG_READ_ONLY, ARG_MKDIR, @@ -505,6 +507,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { { "show-transaction", no_argument, NULL, 'T' }, { "what", required_argument, NULL, ARG_WHAT }, { "reboot-argument", required_argument, NULL, ARG_REBOOT_ARG }, + { "kernel-cmdline", required_argument, NULL, ARG_KERNEL_CMDLINE }, { "timestamp", required_argument, NULL, ARG_TIMESTAMP_STYLE }, { "read-only", no_argument, NULL, ARG_READ_ONLY }, { "mkdir", no_argument, NULL, ARG_MKDIR }, @@ -960,6 +963,21 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_reboot_argument = optarg; break; + case ARG_KERNEL_CMDLINE: + if (isempty(optarg)) { + arg_kernel_cmdline = mfree(arg_kernel_cmdline); + break; + } + + if (!string_is_safe(optarg, STRING_ALLOW_GLOBS|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--kernel-cmdline= argument contains invalid characters: %s", optarg); + + r = free_and_strdup_warn(&arg_kernel_cmdline, optarg); + if (r < 0) + return r; + break; + case ARG_TIMESTAMP_STYLE: if (streq(optarg, "help")) return DUMP_STRING_TABLE(timestamp_style, TimestampStyle, _TIMESTAMP_STYLE_MAX); diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h index 8d4b1a614da28..bc52e96fe3bfd 100644 --- a/src/systemctl/systemctl.h +++ b/src/systemctl/systemctl.h @@ -76,6 +76,7 @@ extern char *arg_image; extern usec_t arg_when; extern bool arg_stdin; extern const char *arg_reboot_argument; +extern char *arg_kernel_cmdline; extern enum action arg_action; extern BusTransport arg_transport; extern const char *arg_host; diff --git a/src/systemd/meson.build b/src/systemd/meson.build index c3d2c1befb71a..d7335cee558de 100644 --- a/src/systemd/meson.build +++ b/src/systemd/meson.build @@ -6,6 +6,7 @@ _systemd_headers = [ 'sd-bus-vtable.h', 'sd-daemon.h', 'sd-device.h', + 'sd-dlopen.h', 'sd-event.h', 'sd-gpt.h', 'sd-hwdb.h', diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index b2995a961f320..378271aef885d 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -159,7 +159,7 @@ int sd_dhcp_client_start(sd_dhcp_client *client); int sd_dhcp_client_send_decline(sd_dhcp_client *client); int sd_dhcp_client_send_renew(sd_dhcp_client *client); int sd_dhcp_client_set_ipv6_connectivity(sd_dhcp_client *client, int have); -int sd_dhcp_client_interrupt_ipv6_only_mode(sd_dhcp_client *client); +int sd_dhcp_client_is_waiting_for_ipv6_connectivity(sd_dhcp_client *client); _SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_client); @@ -172,7 +172,7 @@ int sd_dhcp_client_attach_event( sd_event *event, int64_t priority); int sd_dhcp_client_detach_event(sd_dhcp_client *client); -sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client); +sd_event* sd_dhcp_client_get_event(sd_dhcp_client *client); int sd_dhcp_client_attach_device(sd_dhcp_client *client, sd_device *dev); _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_client, sd_dhcp_client_unref); diff --git a/src/systemd/sd-dhcp-protocol.h b/src/systemd/sd-dhcp-protocol.h index c25498502dac9..1d3b635c31eff 100644 --- a/src/systemd/sd-dhcp-protocol.h +++ b/src/systemd/sd-dhcp-protocol.h @@ -91,7 +91,7 @@ enum { SD_DHCP_OPTION_NIS_SERVER_ADDR = 65, /* [RFC2132] */ SD_DHCP_OPTION_BOOT_SERVER_NAME = 66, /* [RFC2132] */ SD_DHCP_OPTION_BOOT_FILENAME = 67, /* [RFC2132] */ - SD_DHCP_OPTION_HOME_AGENT_ADDRESSES = 68, /* [RFC2132] */ + SD_DHCP_OPTION_HOME_AGENT_ADDRESS = 68, /* [RFC2132] */ SD_DHCP_OPTION_SMTP_SERVER = 69, /* [RFC2132] */ SD_DHCP_OPTION_POP3_SERVER = 70, /* [RFC2132] */ SD_DHCP_OPTION_NNTP_SERVER = 71, /* [RFC2132] */ diff --git a/src/systemd/sd-dlopen.h b/src/systemd/sd-dlopen.h new file mode 100644 index 0000000000000..a58b90e0ec08c --- /dev/null +++ b/src/systemd/sd-dlopen.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: MIT-0 */ +#ifndef foosddlopenhfoo +#define foosddlopenhfoo + +/*** + Copyright © 2026 The systemd Project + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _SD_PASTE +# define _SD_PASTE_INNER(a, b) a##b +# define _SD_PASTE(a, b) _SD_PASTE_INNER(a, b) +#endif + +#ifndef _SD_UNIQ +# ifdef __COUNTER__ +# define _SD_UNIQ _SD_PASTE(_sd_uniq_, __COUNTER__) +# else +# define _SD_UNIQ _SD_PASTE(_sd_uniq_, __LINE__) +# endif +#endif + +/* ELF note macros implementing the FDO .note.dlopen standard. + * + * These macros embed metadata in an ELF binary's .note.dlopen section, + * declaring optional shared library dependencies that are loaded via + * dlopen() at runtime. Package managers and build systems can read + * these notes to discover runtime dependencies not visible in ELF + * DT_NEEDED entries. + * + * Usage: + * + * SD_ELF_NOTE_DLOPEN("myfeature", "Feature description", + * SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + * "libfoo.so.1"); + * + * See SD_ELF_NOTE_DLOPEN(3) for details. + */ + +#define SD_ELF_NOTE_DLOPEN_VENDOR "FDO" +#define SD_ELF_NOTE_DLOPEN_TYPE UINT32_C(0x407c0c0a) +#define SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED "required" +#define SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED "recommended" +#define SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED "suggested" + +#define _SD_ELF_NOTE_DLOPEN(json, variable_name) \ + __attribute__((used, section(".note.dlopen"))) _Alignas(sizeof(uint32_t)) static const struct { \ + struct { \ + uint32_t n_namesz, n_descsz, n_type; \ + } nhdr; \ + char name[sizeof(SD_ELF_NOTE_DLOPEN_VENDOR)]; \ + _Alignas(sizeof(uint32_t)) char dlopen_json[sizeof(json)]; \ + } variable_name = { \ + .nhdr = { \ + .n_namesz = sizeof(SD_ELF_NOTE_DLOPEN_VENDOR), \ + .n_descsz = sizeof(json), \ + .n_type = SD_ELF_NOTE_DLOPEN_TYPE, \ + }, \ + .name = SD_ELF_NOTE_DLOPEN_VENDOR, \ + .dlopen_json = json, \ + } + +#define _SD_SONAME_ARRAY1(a) "[\"" a "\"]" +#define _SD_SONAME_ARRAY2(a, b) "[\"" a "\",\"" b "\"]" +#define _SD_SONAME_ARRAY3(a, b, c) "[\"" a "\",\"" b "\",\"" c "\"]" +#define _SD_SONAME_ARRAY4(a, b, c, d) "[\"" a "\",\"" b "\",\"" c "\",\"" d "\"]" +#define _SD_SONAME_ARRAY5(a, b, c, d, e) "[\"" a "\",\"" b "\",\"" c "\",\"" d "\",\"" e "\"]" +#define _SD_SONAME_ARRAY_GET(_1,_2,_3,_4,_5,NAME,...) NAME +#define _SD_SONAME_ARRAY(...) _SD_SONAME_ARRAY_GET(__VA_ARGS__, _SD_SONAME_ARRAY5, _SD_SONAME_ARRAY4, _SD_SONAME_ARRAY3, _SD_SONAME_ARRAY2, _SD_SONAME_ARRAY1)(__VA_ARGS__) + +#define SD_ELF_NOTE_DLOPEN(feature, description, priority, ...) \ + _SD_ELF_NOTE_DLOPEN("[{\"feature\":\"" feature "\",\"description\":\"" description "\",\"priority\":\"" priority "\",\"soname\":" _SD_SONAME_ARRAY(__VA_ARGS__) "}]", _SD_PASTE(_sd_elf_note_dlopen_, _SD_UNIQ)) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/systemd/sd-event.h b/src/systemd/sd-event.h index f5c79acdfdabc..34bd396080dc3 100644 --- a/src/systemd/sd-event.h +++ b/src/systemd/sd-event.h @@ -97,6 +97,8 @@ int sd_event_add_defer(sd_event *e, sd_event_source **ret, sd_event_handler_t ca int sd_event_add_post(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); int sd_event_add_exit(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); int sd_event_add_memory_pressure(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); +int sd_event_add_cpu_pressure(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); +int sd_event_add_io_pressure(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); int sd_event_prepare(sd_event *e); int sd_event_wait(sd_event *e, uint64_t timeout); @@ -162,6 +164,10 @@ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret); int sd_event_source_get_inotify_path(sd_event_source *s, const char **ret); int sd_event_source_set_memory_pressure_type(sd_event_source *s, const char *ty); int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec); +int sd_event_source_set_cpu_pressure_type(sd_event_source *s, const char *ty); +int sd_event_source_set_cpu_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec); +int sd_event_source_set_io_pressure_type(sd_event_source *s, const char *ty); +int sd_event_source_set_io_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec); int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback); int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret); int sd_event_source_get_floating(sd_event_source *s); diff --git a/src/systemd/sd-json.h b/src/systemd/sd-json.h index 359149ef9e0e2..6a1977549098f 100644 --- a/src/systemd/sd-json.h +++ b/src/systemd/sd-json.h @@ -181,7 +181,9 @@ int sd_json_variant_sort(sd_json_variant **v); int sd_json_variant_normalize(sd_json_variant **v); __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_json_parse_flags_t) { - SD_JSON_PARSE_SENSITIVE = 1 << 0, /* mark variant as "sensitive", i.e. something containing secret key material or such */ + SD_JSON_PARSE_SENSITIVE = 1 << 0, /* mark variant as "sensitive", i.e. something containing secret key material or such */ + SD_JSON_PARSE_MUST_BE_OBJECT = 1 << 1, /* refuse parsing if top-level is not an object */ + SD_JSON_PARSE_MUST_BE_ARRAY = 1 << 2, /* refuse parsing if top-level is not an array */ _SD_ENUM_FORCE_S64(JSON_PARSE_FLAGS) } sd_json_parse_flags_t; diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h index 7bc4199408f13..6aabe88196b98 100644 --- a/src/systemd/sd-messages.h +++ b/src/systemd/sd-messages.h @@ -306,8 +306,11 @@ _SD_BEGIN_DECLARATIONS; #define SD_MESSAGE_SYSTEM_ACCOUNT_REQUIRED SD_ID128_MAKE(34,05,20,5d,36,8e,49,fe,b5,ab,39,25,fe,e1,38,74) #define SD_MESSAGE_SYSTEM_ACCOUNT_REQUIRED_STR SD_ID128_MAKE_STR(34,05,20,5d,36,8e,49,fe,b5,ab,39,25,fe,e1,38,74) -#define SD_MESSAGE_TPM_INVINDEX_EXHAUSTED SD_ID128_MAKE(ab,98,4e,a0,08,96,4f,b8,8d,6e,38,9f,b5,13,fb,94) -#define SD_MESSAGE_TPM_INVINDEX_EXHAUSTED_STR SD_ID128_MAKE_STR(ab,98,4e,a0,08,96,4f,b8,8d,6e,38,9f,b5,13,fb,94) +#define SD_MESSAGE_TPM_NVINDEX_EXHAUSTED SD_ID128_MAKE(ab,98,4e,a0,08,96,4f,b8,8d,6e,38,9f,b5,13,fb,94) +#define SD_MESSAGE_TPM_NVINDEX_EXHAUSTED_STR SD_ID128_MAKE_STR(ab,98,4e,a0,08,96,4f,b8,8d,6e,38,9f,b5,13,fb,94) + +#define SD_MESSAGE_TPM_NVPCR_UNSUPPORTED SD_ID128_MAKE(8f,07,a5,b8,14,ca,47,62,b8,9f,cc,30,82,e4,8a,ed) +#define SD_MESSAGE_TPM_NVPCR_UNSUPPORTED_STR SD_ID128_MAKE_STR(8f,07,a5,b8,14,ca,47,62,b8,9f,cc,30,82,e4,8a,ed) _SD_END_DECLARATIONS; diff --git a/src/systemd/sd-path.h b/src/systemd/sd-path.h index 2718cf8266475..299fb20adea72 100644 --- a/src/systemd/sd-path.h +++ b/src/systemd/sd-path.h @@ -132,6 +132,8 @@ __extension__ enum { SD_PATH_USER_CREDENTIAL_STORE_ENCRYPTED, SD_PATH_USER_SEARCH_CREDENTIAL_STORE_ENCRYPTED, + SD_PATH_USER_PROJECTS, + _SD_PATH_MAX, _SD_PATH_INVALID = UINT64_MAX }; diff --git a/src/systemd/sd-varlink-idl.h b/src/systemd/sd-varlink-idl.h index c9e0bfca49234..1122e31324206 100644 --- a/src/systemd/sd-varlink-idl.h +++ b/src/systemd/sd-varlink-idl.h @@ -52,7 +52,9 @@ __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_symbol_type_t) { __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_symbol_flags_t) { SD_VARLINK_SUPPORTS_MORE = 1 << 0, /* Call supports "more" flag */ SD_VARLINK_REQUIRES_MORE = 1 << 1, /* Call requires "more" flag */ - _SD_VARLINK_SYMBOL_FLAGS_MAX = (1 << 2) - 1, + SD_VARLINK_SUPPORTS_UPGRADE = 1 << 2, /* Call supports "upgrade" flag */ + SD_VARLINK_REQUIRES_UPGRADE = 1 << 3, /* Call requires "upgrade" flag */ + _SD_VARLINK_SYMBOL_FLAGS_MAX = (1 << 4) - 1, _SD_VARLINK_SYMBOL_FLAGS_INVALID = -EINVAL, _SD_ENUM_FORCE_S64(SD_VARLINK_SYMBOL_FLAGS) } sd_varlink_symbol_flags_t; @@ -67,9 +69,9 @@ __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_field_type_t) { SD_VARLINK_FLOAT, SD_VARLINK_STRING, SD_VARLINK_OBJECT, - SD_VARLINK_ANY, SD_VARLINK_ENUM_VALUE, _SD_VARLINK_FIELD_COMMENT, /* Not really a field, just a comment about a field */ + SD_VARLINK_ANY, _SD_VARLINK_FIELD_TYPE_MAX, _SD_VARLINK_FIELD_TYPE_INVALID = -EINVAL, _SD_ENUM_FORCE_S64(SD_VARLINK_FIELD) diff --git a/src/systemd/sd-varlink.h b/src/systemd/sd-varlink.h index fff6ea8a36fd8..3be82a7ddbc34 100644 --- a/src/systemd/sd-varlink.h +++ b/src/systemd/sd-varlink.h @@ -55,8 +55,9 @@ __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_reply_flags_t) { } sd_varlink_reply_flags_t; __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_method_flags_t) { - SD_VARLINK_METHOD_ONEWAY = 1 << 0, - SD_VARLINK_METHOD_MORE = 1 << 1, + SD_VARLINK_METHOD_ONEWAY = 1 << 0, + SD_VARLINK_METHOD_MORE = 1 << 1, + SD_VARLINK_METHOD_UPGRADE = 1 << 2, _SD_ENUM_FORCE_S64(SD_VARLINK_METHOD) } sd_varlink_method_flags_t; @@ -71,6 +72,7 @@ __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_server_flags_t) { SD_VARLINK_SERVER_FD_PASSING_INPUT_STRICT = 1 << 7, /* Reject input messages with fds if fd passing is disabled (needs kernel v6.16+) */ SD_VARLINK_SERVER_HANDLE_SIGINT = 1 << 8, /* Exit cleanly on SIGINT */ SD_VARLINK_SERVER_HANDLE_SIGTERM = 1 << 9, /* Exit cleanly on SIGTERM */ + SD_VARLINK_SERVER_UPGRADABLE = 1 << 10, /* Server has upgrade methods; avoid consuming post-upgrade data during reads */ _SD_ENUM_FORCE_S64(SD_VARLINK_SERVER) } sd_varlink_server_flags_t; @@ -135,6 +137,14 @@ int sd_varlink_callb(sd_varlink *v, const char *method, sd_json_variant **ret_pa #define sd_varlink_callbo(v, method, ret_parameters, ret_error_id, ...) \ sd_varlink_callb((v), (method), (ret_parameters), (ret_error_id), SD_JSON_BUILD_OBJECT(__VA_ARGS__)) +/* Send method call with upgrade, wait for reply, then steal the connection fds for raw I/O. + * For bidirectional sockets ret_input_fd and ret_output_fd will be separate (dupped) fds + * referring to the same underlying socket. ret_parameters and ret_error_id are borrowed + * references valid only until v is closed or unreffed. + * Returns > 0 if the connection was upgraded, 0 if a Varlink error occurred (and ret_error_id was set), + * or < 0 on local failure. */ +int sd_varlink_call_and_upgrade(sd_varlink *v, const char *method, sd_json_variant *parameters, sd_json_variant **ret_parameters, const char **ret_error_id, int *ret_input_fd, int *ret_output_fd); + /* Send method call and begin collecting all 'more' replies into an array, finishing when a final reply is sent */ int sd_varlink_collect_full(sd_varlink *v, const char *method, sd_json_variant *parameters, sd_json_variant **ret_parameters, const char **ret_error_id, sd_varlink_reply_flags_t *ret_flags); int sd_varlink_collect(sd_varlink *v, const char *method, sd_json_variant *parameters, sd_json_variant **ret_parameters, const char **ret_error_id); @@ -160,6 +170,18 @@ int sd_varlink_replyb(sd_varlink *v, ...); #define sd_varlink_replybo(v, ...) \ sd_varlink_replyb((v), SD_JSON_BUILD_OBJECT(__VA_ARGS__)) +/* Send a final reply to an upgrade request, then steal the connection fds for raw I/O. + * The fds are returned in blocking mode. The varlink connection is disconnected afterwards. + * For bidirectional sockets ret_input_fd and ret_output_fd will be separate (dupped) fds + * referring to the same underlying socket. For pipe pairs (e.g. ssh-exec transport) they + * will differ. Either ret pointer may be NULL. + * + * Note: this call synchronously blocks until the reply is flushed to the socket. This is + * usually fine as flush is fast but a misbehaving/adversary client that stops reading + * could stall the caller. So do not use in servers that multiplex many varlink + * connections. */ +int sd_varlink_reply_and_upgrade(sd_varlink *v, sd_json_variant *parameters, int *ret_input_fd, int *ret_output_fd); + /* Enqueue a (final) error */ int sd_varlink_error(sd_varlink *v, const char *error_id, sd_json_variant *parameters); int sd_varlink_errorb(sd_varlink *v, const char *error_id, ...); @@ -205,6 +227,11 @@ int sd_varlink_bind_reply(sd_varlink *v, sd_varlink_reply_t reply); void* sd_varlink_set_userdata(sd_varlink *v, void *userdata); void* sd_varlink_get_userdata(sd_varlink *v); +/* Queue a reply to be sent if no other reply was sent by a method callback. + * Useful when implementing services which send a (possibly empty) series + * of objects and terminate. */ +int sd_varlink_set_sentinel(sd_varlink *v, const char *error_id); + int sd_varlink_get_peer_uid(sd_varlink *v, uid_t *ret); int sd_varlink_get_peer_gid(sd_varlink *v, gid_t *ret); int sd_varlink_get_peer_pid(sd_varlink *v, pid_t *ret); @@ -309,6 +336,7 @@ _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_varlink_server, sd_varlink_server_unref); #define SD_VARLINK_ERROR_INVALID_PARAMETER "org.varlink.service.InvalidParameter" #define SD_VARLINK_ERROR_PERMISSION_DENIED "org.varlink.service.PermissionDenied" #define SD_VARLINK_ERROR_EXPECTED_MORE "org.varlink.service.ExpectedMore" +#define SD_VARLINK_ERROR_EXPECTED_UPGRADE "org.varlink.service.ExpectedUpgrade" _SD_END_DECLARATIONS; diff --git a/src/sysupdate/meson.build b/src/sysupdate/meson.build index 6875834e0fcba..3a1ee1a048c62 100644 --- a/src/sysupdate/meson.build +++ b/src/sysupdate/meson.build @@ -27,13 +27,9 @@ executables += [ 'conditions' : ['ENABLE_SYSUPDATE'], 'sources' : systemd_sysupdate_sources, 'extract' : systemd_sysupdate_extract_sources, - 'link_with' : [ - libshared, - libshared_fdisk, - ], 'dependencies' : [ - libfdisk, - libopenssl, + libfdisk_cflags, + libopenssl_cflags, threads, ], }, diff --git a/src/sysupdate/sysupdate-cache.c b/src/sysupdate/sysupdate-cache.c index 23cef57b088fc..222b0889159e4 100644 --- a/src/sysupdate/sysupdate-cache.c +++ b/src/sysupdate/sysupdate-cache.c @@ -42,7 +42,7 @@ int web_cache_add_item( if (item && memcmp_nn(item->data, item->size, data, size) == 0) return 0; - if (hashmap_size(*web_cache) >= (size_t) (WEB_CACHE_ENTRIES_MAX + !!hashmap_get(*web_cache, url))) + if (hashmap_size(*web_cache) >= (size_t) (WEB_CACHE_ENTRIES_MAX + hashmap_contains(*web_cache, url))) return -ENOSPC; r = hashmap_ensure_allocated(web_cache, &web_cache_hash_ops); diff --git a/src/sysupdate/sysupdate-partition.c b/src/sysupdate/sysupdate-partition.c index 94a69850af57c..0b346da62d18d 100644 --- a/src/sysupdate/sysupdate-partition.c +++ b/src/sysupdate/sysupdate-partition.c @@ -77,32 +77,32 @@ int read_partition_info( assert(t); assert(ret); - p = fdisk_table_get_partition(t, i); + p = sym_fdisk_table_get_partition(t, i); if (!p) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); - if (fdisk_partition_is_used(p) <= 0) { + if (sym_fdisk_partition_is_used(p) <= 0) { *ret = (PartitionInfo) PARTITION_INFO_NULL; return 0; /* not found! */ } - if (fdisk_partition_has_partno(p) <= 0 || - fdisk_partition_has_start(p) <= 0 || - fdisk_partition_has_size(p) <= 0) + if (sym_fdisk_partition_has_partno(p) <= 0 || + sym_fdisk_partition_has_start(p) <= 0 || + sym_fdisk_partition_has_size(p) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a number, position or size."); - partno = fdisk_partition_get_partno(p); + partno = sym_fdisk_partition_get_partno(p); - start = fdisk_partition_get_start(p); - ssz = fdisk_get_sector_size(c); + start = sym_fdisk_partition_get_start(p); + ssz = sym_fdisk_get_sector_size(c); assert(start <= UINT64_MAX / ssz); start *= ssz; - size = fdisk_partition_get_size(p); + size = sym_fdisk_partition_get_size(p); assert(size <= UINT64_MAX / ssz); size *= ssz; - label = fdisk_partition_get_name(p); + label = sym_fdisk_partition_get_name(p); if (!label) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a label."); @@ -118,7 +118,7 @@ int read_partition_info( if (r < 0) return log_error_errno(r, "Failed to get partition flags: %m"); - r = fdisk_partition_to_string(p, c, FDISK_FIELD_DEVICE, &device); + r = sym_fdisk_partition_to_string(p, c, FDISK_FIELD_DEVICE, &device); if (r != 0) return log_error_errno(r, "Failed to get partition device name: %m"); @@ -158,20 +158,25 @@ int find_suitable_partition( int r; assert(device); + POINTER_MAY_BE_NULL(partition_type); assert(ret); + r = dlopen_fdisk(LOG_DEBUG); + if (r < 0) + return r; + r = fdisk_new_context_at(AT_FDCWD, device, /* read_only= */ true, /* sector_size= */ UINT32_MAX, &c); if (r < 0) return log_error_errno(r, "Failed to create fdisk context from '%s': %m", device); - if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) + if (!sym_fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", device); - r = fdisk_get_partitions(c, &t); + r = sym_fdisk_get_partitions(c, &t); if (r < 0) return log_error_errno(r, "Failed to acquire partition table: %m"); - n_partitions = fdisk_table_get_nents(t); + n_partitions = sym_fdisk_table_get_nents(t); for (size_t i = 0; i < n_partitions; i++) { _cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL; @@ -225,11 +230,15 @@ int patch_partition( if (change == 0) /* Nothing to do */ return 0; + r = dlopen_fdisk(LOG_DEBUG); + if (r < 0) + return r; + r = fdisk_new_context_at(AT_FDCWD, device, /* read_only= */ false, /* sector_size= */ UINT32_MAX, &c); if (r < 0) return log_error_errno(r, "Failed to create fdisk context from '%s': %m", device); - assert_se((fd = fdisk_get_devfd(c)) >= 0); + assert_se((fd = sym_fdisk_get_devfd(c)) >= 0); /* Make sure udev doesn't read the device while we make changes (this lock is released automatically * by the kernel when the fd is closed, i.e. when the fdisk context is freed, hence no explicit @@ -237,21 +246,21 @@ int patch_partition( if (flock(fd, LOCK_EX) < 0) return log_error_errno(errno, "Failed to lock block device '%s': %m", device); - if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) + if (!sym_fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", device); - r = fdisk_get_partition(c, info->partno, &pa); + r = sym_fdisk_get_partition(c, info->partno, &pa); if (r < 0) return log_error_errno(r, "Failed to read partition %zu of GPT label of '%s': %m", info->partno, device); if (change & PARTITION_LABEL) { - r = fdisk_partition_set_name(pa, info->label); + r = sym_fdisk_partition_set_name(pa, info->label); if (r < 0) return log_error_errno(r, "Failed to update partition label: %m"); } if (change & PARTITION_UUID) { - r = fdisk_partition_set_uuid(pa, SD_ID128_TO_UUID_STRING(info->uuid)); + r = sym_fdisk_partition_set_uuid(pa, SD_ID128_TO_UUID_STRING(info->uuid)); if (r < 0) return log_error_errno(r, "Failed to update partition UUID: %m"); } @@ -259,15 +268,15 @@ int patch_partition( if (change & PARTITION_TYPE) { _cleanup_(fdisk_unref_parttypep) struct fdisk_parttype *pt = NULL; - pt = fdisk_new_parttype(); + pt = sym_fdisk_new_parttype(); if (!pt) return log_oom(); - r = fdisk_parttype_set_typestr(pt, SD_ID128_TO_UUID_STRING(info->type)); + r = sym_fdisk_parttype_set_typestr(pt, SD_ID128_TO_UUID_STRING(info->type)); if (r < 0) return log_error_errno(r, "Failed to initialize partition type: %m"); - r = fdisk_partition_set_type(pa, pt); + r = sym_fdisk_partition_set_type(pa, pt); if (r < 0) return log_error_errno(r, "Failed to update partition type: %m"); } @@ -327,11 +336,11 @@ int patch_partition( } } - r = fdisk_set_partition(c, info->partno, pa); + r = sym_fdisk_set_partition(c, info->partno, pa); if (r < 0) return log_error_errno(r, "Failed to update partition: %m"); - r = fdisk_write_disklabel(c); + r = sym_fdisk_write_disklabel(c); if (r < 0) return log_error_errno(r, "Failed to write updated partition table: %m"); diff --git a/src/sysupdate/sysupdate-resource.c b/src/sysupdate/sysupdate-resource.c index 3be0943e4c0a3..5cd3d0fbbb0ab 100644 --- a/src/sysupdate/sysupdate-resource.c +++ b/src/sysupdate/sysupdate-resource.c @@ -229,18 +229,22 @@ static int resource_load_from_blockdev(Resource *rr) { assert(rr); + r = dlopen_fdisk(LOG_DEBUG); + if (r < 0) + return r; + r = fdisk_new_context_at(AT_FDCWD, rr->path, /* read_only= */ true, /* sector_size= */ UINT32_MAX, &c); if (r < 0) return log_error_errno(r, "Failed to create fdisk context from '%s': %m", rr->path); - if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) + if (!sym_fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", rr->path); - r = fdisk_get_partitions(c, &t); + r = sym_fdisk_get_partitions(c, &t); if (r < 0) return log_error_errno(r, "Failed to acquire partition table: %m"); - n_partitions = fdisk_table_get_nents(t); + n_partitions = sym_fdisk_table_get_nents(t); for (size_t i = 0; i < n_partitions; i++) { _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL; _cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL; @@ -430,27 +434,13 @@ static int process_magic_file( /* Even if we ignore if people have non-empty files for this file, let's nonetheless warn about it, * so that people fix it. After all we want to retain liberty to maybe one day place some useful data * inside it */ - if (iovec_memcmp(&IOVEC_MAKE(expected_hash, sizeof(expected_hash)), hash) != 0) + if (!iovec_equal(&IOVEC_MAKE(expected_hash, sizeof(expected_hash)), hash)) log_warning("Hash of best before marker file '%s' has unexpected value, proceeding anyway.", fn); - struct tm parsed_tm = {}; - const char *n = strptime(e, "%Y-%m-%d", &parsed_tm); - if (!n || *n != 0) { - /* Doesn't parse? Then it's not a best-before date */ - log_warning("Found best before marker with an invalid date, ignoring: %s", fn); - return 0; - } - - struct tm copy_tm = parsed_tm; usec_t best_before; - r = mktime_or_timegm_usec(©_tm, /* utc= */ true, &best_before); - if (r < 0) - return log_error_errno(r, "Failed to convert best before time: %m"); - if (copy_tm.tm_mday != parsed_tm.tm_mday || - copy_tm.tm_mon != parsed_tm.tm_mon || - copy_tm.tm_year != parsed_tm.tm_year) { - /* date was not normalized? (e.g. "30th of feb") */ - log_warning("Found best before marker with a non-normalized data, ignoring: %s", fn); + r = parse_calendar_date(e, &best_before); + if (r < 0) { + log_warning_errno(r, "Found best before marker with an invalid date, ignoring: %s", fn); return 0; } @@ -491,6 +481,7 @@ static int resource_load_from_web( int r; assert(rr); + POINTER_MAY_BE_NULL(web_cache); ci = web_cache ? web_cache_get_item(*web_cache, rr->path, verify) : NULL; if (ci) { @@ -852,9 +843,9 @@ int resource_resolve_path( } else { /* boot, esp, or xbootldr */ r = 0; if (IN_SET(rr->path_relative_to, PATH_RELATIVE_TO_BOOT, PATH_RELATIVE_TO_XBOOTLDR)) - r = find_xbootldr_and_warn(root, NULL, /* unprivileged_mode= */ -1, &relative_to, NULL, NULL); + r = find_xbootldr_and_warn(root, /* path= */ NULL, /* unprivileged_mode= */ -1, &relative_to, /* ret_fd= */ NULL); if (r == -ENOKEY || rr->path_relative_to == PATH_RELATIVE_TO_ESP) - r = find_esp_and_warn(root, NULL, -1, &relative_to, NULL, NULL, NULL, NULL, NULL); + r = find_esp_and_warn(root, /* path= */ NULL, /* unprivileged_mode= */ -1, &relative_to, /* ret_fd= */ NULL); if (r < 0) return log_error_errno(r, "Failed to resolve $BOOT: %m"); log_debug("Resolved $BOOT to '%s'", relative_to); diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c index 46f3129f5d993..3ed7a2ae3a488 100644 --- a/src/sysupdate/sysupdate-transfer.c +++ b/src/sysupdate/sysupdate-transfer.c @@ -1009,6 +1009,7 @@ static int callout_context_new(const Transfer *t, const Instance *i, TransferPro assert(t); assert(i); assert(cb); + assert(ret); ctx = new(CalloutContext, 1); if (!ctx) @@ -1609,8 +1610,10 @@ int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i) { /* Does this instance already exist in the target but isn’t pending? */ existing = resource_find_instance(&t->target, i->metadata.version); - if (existing && !existing->is_pending) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to acquire '%s', instance is already in the target but is not pending.", i->path); + if (existing && !existing->is_pending) { + log_info("Resource '%s' instance is already in the target but is not pending.", i->path); + return 0; + } /* All we need to do is compute the temporary paths. We don’t need to do any of the other work in * transfer_acquire_instance(). */ diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 7693ec373caac..a4bf835108ab6 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-daemon.h" @@ -16,6 +15,7 @@ #include "loop-util.h" #include "main-func.h" #include "mount-util.h" +#include "options.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" @@ -111,11 +111,7 @@ static Context* context_new(void) { return new0(Context, 1); } -static void free_transfers(Transfer **array, size_t n) { - FOREACH_ARRAY(t, array, n) - transfer_free(*t); - free(array); -} +static DEFINE_POINTER_ARRAY_FREE_FUNC(Transfer*, transfer_free); static int read_definitions( Context *c, @@ -128,9 +124,9 @@ static int read_definitions( size_t n_files = 0, n_transfers = 0, n_disabled = 0; int r; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); - CLEANUP_ARRAY(transfers, n_transfers, free_transfers); - CLEANUP_ARRAY(disabled, n_disabled, free_transfers); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); + CLEANUP_ARRAY(transfers, n_transfers, transfer_free_array); + CLEANUP_ARRAY(disabled, n_disabled, transfer_free_array); assert(c); assert(dirs); @@ -208,7 +204,7 @@ static int context_read_definitions(Context *c, const char* node, bool requires_ ConfFile **files = NULL; size_t n_files = 0; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); r = conf_files_list_strv_full(".feature", arg_root, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN, @@ -397,13 +393,12 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags assert(flags == UPDATE_INSTALLED); match = resource_find_instance(&t->target, cursor); - if (!match && !(extra_flags & (UPDATE_PARTIAL|UPDATE_PENDING))) { + if (!match && !(extra_flags & (UPDATE_PARTIAL|UPDATE_PENDING))) /* When we're looking for installed versions, let's be robust and treat * an incomplete installation as an installation. Otherwise, there are * situations that can lead to sysupdate wiping the currently booted OS. * See https://github.com/systemd/systemd/issues/33339 */ extra_flags |= UPDATE_INCOMPLETE; - } } cursor_instances[k] = match; @@ -1207,7 +1202,9 @@ static int context_install( } (void) sd_notifyf(/* unset_environment=*/ false, - "STATUS=Installing '%s'.", us->version); + "READY=1\n" + "X_SYSUPDATE_VERSION=%s\n" + "STATUS=Installing '%s'.", us->version, us->version); for (size_t i = 0; i < c->n_transfers; i++) { Instance *inst = us->instances[i]; @@ -1278,7 +1275,9 @@ static int process_image( return 0; } -static int verb_list(int argc, char **argv, void *userdata) { +VERB(verb_list, "list", "[VERSION]", VERB_ANY, 2, VERB_DEFAULT, + "Show installed and available versions"); +static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; _cleanup_(context_freep) Context* context = NULL; @@ -1346,7 +1345,9 @@ static int verb_list(int argc, char **argv, void *userdata) { } } -static int verb_features(int argc, char **argv, void *userdata) { +VERB(verb_features, "features", "[FEATURE]", VERB_ANY, 2, 0, + "Show optional features"); +static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; _cleanup_(context_freep) Context* context = NULL; @@ -1480,7 +1481,9 @@ static int verb_features(int argc, char **argv, void *userdata) { return 0; } -static int verb_check_new(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_check_new, "check-new", + "Check if there's a new version available"); +static int verb_check_new(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; _cleanup_(context_freep) Context* context = NULL; @@ -1521,29 +1524,6 @@ static int verb_check_new(int argc, char **argv, void *userdata) { return EXIT_SUCCESS; } -static int verb_vacuum(int argc, char **argv, void *userdata) { - _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; - _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; - _cleanup_(context_freep) Context* context = NULL; - int r; - - assert(argc <= 1); - - if (arg_instances_max < 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "The --instances-max argument must be >= 1 while vacuuming"); - - r = process_image(/* ro= */ false, &mounted_dir, &loop_device); - if (r < 0) - return r; - - r = context_make_offline(&context, loop_device ? loop_device->node : NULL, /* requires_enabled_transfers= */ false); - if (r < 0) - return r; - - return context_vacuum(context, 0, NULL); -} - typedef enum { UPDATE_ACTION_ACQUIRE = 1 << 0, UPDATE_ACTION_INSTALL = 1 << 1, @@ -1616,7 +1596,9 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag return 0; } -static int verb_update(int argc, char **argv, void *userdata) { +VERB(verb_update, "update", "[VERSION]", VERB_ANY, 2, 0, + "Install new version now"); +static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) { UpdateActionFlags flags = UPDATE_ACTION_INSTALL; if (!arg_offline) @@ -1625,11 +1607,42 @@ static int verb_update(int argc, char **argv, void *userdata) { return verb_update_impl(argc, argv, flags); } -static int verb_acquire(int argc, char **argv, void *userdata) { +VERB(verb_acquire, "acquire", "[VERSION]", VERB_ANY, 2, 0, + "Acquire (download) new version now"); +static int verb_acquire(int argc, char *argv[], uintptr_t _data, void *userdata) { return verb_update_impl(argc, argv, UPDATE_ACTION_ACQUIRE); } -static int verb_pending_or_reboot(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_vacuum, "vacuum", + "Make room, by deleting old versions"); +static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; + _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; + _cleanup_(context_freep) Context* context = NULL; + int r; + + assert(argc <= 1); + + if (arg_instances_max < 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "The --instances-max argument must be >= 1 while vacuuming"); + + r = process_image(/* ro= */ false, &mounted_dir, &loop_device); + if (r < 0) + return r; + + r = context_make_offline(&context, loop_device ? loop_device->node : NULL, /* requires_enabled_transfers= */ false); + if (r < 0) + return r; + + return context_vacuum(context, 0, NULL); +} + +VERB(verb_pending_or_reboot, "pending", NULL, 1, 1, 0, + "Report whether a newer version is installed than currently booted"); +VERB(verb_pending_or_reboot, "reboot", NULL, 1, 1, 0, + "Reboot if a newer version is installed than booted"); +static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(context_freep) Context* context = NULL; _cleanup_free_ char *booted_version = NULL; int r; @@ -1703,7 +1716,9 @@ static int component_name_valid(const char *c) { return filename_is_valid(j); } -static int verb_components(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_components, "components", + "Show list of components"); +static int verb_components(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; _cleanup_set_free_ Set *names = NULL; @@ -1719,7 +1734,7 @@ static int verb_components(int argc, char **argv, void *userdata) { ConfFile **directories = NULL; size_t n_directories = 0; - CLEANUP_ARRAY(directories, n_directories, conf_file_free_many); + CLEANUP_ARRAY(directories, n_directories, conf_file_free_array); r = conf_files_list_strv_full(".d", arg_root, CONF_FILES_DIRECTORY|CONF_FILES_WARN, (const char * const *) CONF_PATHS_STRV(""), &directories, &n_directories); @@ -1793,189 +1808,151 @@ static int verb_components(int argc, char **argv, void *userdata) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *common_options = NULL, *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-sysupdate", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [VERSION]\n" - "\n%5$sUpdate OS images.%6$s\n" - "\n%3$sCommands:%4$s\n" - " list [VERSION] Show installed and available versions\n" - " features [FEATURE] Show optional features\n" - " check-new Check if there's a new version available\n" - " update [VERSION] Install new version now\n" - " acquire [VERSION] Acquire (download) new version now\n" - " vacuum Make room, by deleting old versions\n" - " pending Report whether a newer version is installed than\n" - " currently booted\n" - " reboot Reboot if a newer version is installed than booted\n" - " components Show list of components\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sOptions:%4$s\n" - " -C --component=NAME Select component to update\n" - " --definitions=DIR Find transfer definitions in specified directory\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY\n" - " Specify disk image dissection policy\n" - " -m --instances-max=INT How many instances to maintain\n" - " --sync=BOOL Controls whether to sync data to disk\n" - " --verify=BOOL Force signature verification on or off\n" - " --reboot Reboot after updating to newer version\n" - " --offline Do not fetch metadata from the network\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " --transfer-source=PATH\n" - " Specify the directory to transfer sources from\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&common_options); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("Options", &options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, common_options, options); + + printf("%s [OPTIONS...] [VERSION]\n" + "\n%sUpdate OS images.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + ansi_highlight(), ansi_normal(), + ansi_underline(), ansi_normal()); + + r = table_print_or_warn(verbs); + if (r < 0) + return r; + r = table_print_or_warn(common_options); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_SYNC, - ARG_DEFINITIONS, - ARG_JSON, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_REBOOT, - ARG_VERIFY, - ARG_OFFLINE, - ARG_TRANSFER_SOURCE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "definitions", required_argument, NULL, ARG_DEFINITIONS }, - { "instances-max", required_argument, NULL, 'm' }, - { "sync", required_argument, NULL, ARG_SYNC }, - { "json", required_argument, NULL, ARG_JSON }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "reboot", no_argument, NULL, ARG_REBOOT }, - { "component", required_argument, NULL, 'C' }, - { "verify", required_argument, NULL, ARG_VERIFY }, - { "offline", no_argument, NULL, ARG_OFFLINE }, - { "transfer-source", required_argument, NULL, ARG_TRANSFER_SOURCE }, - {} - }; - - int c, r; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "hm:C:", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return verb_help(0, NULL, NULL); + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION_GROUP("Options"): break; - case ARG_NO_LEGEND: - arg_legend = false; - break; + OPTION('C', "component", "NAME", + "Select component to update"): + if (isempty(arg)) { + arg_component = mfree(arg_component); + break; + } - case 'm': - r = safe_atou64(optarg, &arg_instances_max); + r = component_name_valid(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", optarg); - - break; + return log_error_errno(r, "Failed to determine if component name is valid: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", arg); - case ARG_SYNC: - r = parse_boolean_argument("--sync=", optarg, &arg_sync); + r = free_and_strdup_warn(&arg_component, arg); if (r < 0) return r; + break; - case ARG_DEFINITIONS: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_definitions); + OPTION_LONG("definitions", "DIR", + "Find transfer definitions in specified directory"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_definitions); if (r < 0) return r; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) + OPTION_LONG("root", "PATH", + "Operate on an alternate filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root); + if (r < 0) return r; - break; - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root); + OPTION_LONG("image", "PATH", + "Operate on disk image as filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + OPTION_LONG("image-policy", "POLICY", + "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("transfer-source", "PATH", + "Specify the directory to transfer sources from"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_transfer_source); if (r < 0) return r; - break; - case ARG_REBOOT: - arg_reboot = true; break; - case 'C': - if (isempty(optarg)) { - arg_component = mfree(arg_component); - break; - } - - r = component_name_valid(optarg); + OPTION('m', "instances-max", "INT", + "How many instances to maintain"): + r = safe_atou64(arg, &arg_instances_max); if (r < 0) - return log_error_errno(r, "Failed to determine if component name is valid: %m"); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", optarg); + return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", arg); + + break; - r = free_and_strdup_warn(&arg_component, optarg); + OPTION_LONG("sync", "BOOL", + "Controls whether to sync data to disk"): + r = parse_boolean_argument("--sync=", arg, &arg_sync); if (r < 0) return r; - break; - case ARG_VERIFY: { + OPTION_LONG("verify", "BOOL", + "Force signature verification on or off"): { bool b; - r = parse_boolean_argument("--verify=", optarg, &b); + r = parse_boolean_argument("--verify=", arg, &b); if (r < 0) return r; @@ -1983,24 +1960,31 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_OFFLINE: + OPTION_LONG("reboot", NULL, + "Reboot after updating to newer version"): + arg_reboot = true; + break; + + OPTION_LONG("offline", NULL, + "Do not fetch metadata from the network"): arg_offline = true; break; - case ARG_TRANSFER_SOURCE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_transfer_source); - if (r < 0) - return r; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + OPTION_COMMON_NO_LEGEND: + arg_legend = false; break; - case '?': - return -EINVAL; + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); + if (r <= 0) + return r; - default: - assert_not_reached(); + break; } - } if (arg_image && arg_root) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); @@ -2011,38 +1995,21 @@ static int parse_argv(int argc, char *argv[]) { if (arg_definitions && arg_component) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined."); + *remaining_args = option_parser_get_args(&state); return 1; } -static int sysupdate_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "list", VERB_ANY, 2, VERB_DEFAULT, verb_list }, - { "components", VERB_ANY, 1, 0, verb_components }, - { "features", VERB_ANY, 2, 0, verb_features }, - { "check-new", VERB_ANY, 1, 0, verb_check_new }, - { "update", VERB_ANY, 2, 0, verb_update }, - { "acquire", VERB_ANY, 2, 0, verb_acquire }, - { "vacuum", VERB_ANY, 1, 0, verb_vacuum }, - { "reboot", 1, 1, 0, verb_pending_or_reboot }, - { "pending", 1, 1, 0, verb_pending_or_reboot }, - { "help", VERB_ANY, 1, 0, verb_help }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return sysupdate_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/sysupdate/sysupdated.c b/src/sysupdate/sysupdated.c index c123842c1084f..4c7a759a4dadb 100644 --- a/src/sysupdate/sysupdated.c +++ b/src/sysupdate/sysupdated.c @@ -184,8 +184,9 @@ static Job *job_free(Job *j) { } DEFINE_TRIVIAL_CLEANUP_FUNC(Job*, job_free); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(job_hash_ops, uint64_t, uint64_hash_func, uint64_compare_func, - Job, job_free); +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(job_hash_ops, + uint64_t, uint64_hash_func, uint64_compare_func, + Job, job_free); static int job_new(JobType type, Target *t, sd_bus_message *msg, JobComplete complete_cb, Job **ret) { _cleanup_(job_freep) Job *j = NULL; @@ -728,8 +729,9 @@ static Target *target_free(Target *t) { } DEFINE_TRIVIAL_CLEANUP_FUNC(Target*, target_free); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(target_hash_ops, char, string_hash_func, string_compare_func, - Target, target_free); +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(target_hash_ops, + char, string_hash_func, string_compare_func, + Target, target_free); static int target_new(Manager *m, TargetClass class, const char *name, const char *path, Target **ret) { _cleanup_(target_freep) Target *t = NULL; @@ -975,8 +977,8 @@ static int target_method_describe(sd_bus_message *msg, void *userdata, sd_bus_er if (r < 0) return r; - if (isempty(version)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Version must be specified"); + if (!version_is_valid(version)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid version"); if ((flags & ~SD_SYSUPDATE_FLAGS_ALL) != 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags specified"); @@ -1124,8 +1126,12 @@ static int target_method_acquire(sd_bus_message *msg, void *userdata, sd_bus_err * an update anyway. */ if (isempty(version)) action = "org.freedesktop.sysupdate1.update"; - else + else { + if (!version_is_valid(version)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid version"); + action = "org.freedesktop.sysupdate1.update-to-version"; + } const char *details[] = { "class", target_class_to_string(t->class), @@ -1208,8 +1214,12 @@ static int target_method_install(sd_bus_message *msg, void *userdata, sd_bus_err * an update anyway. */ if (isempty(version)) action = "org.freedesktop.sysupdate1.update"; - else + else { + if (!version_is_valid(version)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid version"); + action = "org.freedesktop.sysupdate1.update-to-version"; + } const char *details[] = { "class", target_class_to_string(t->class), @@ -1324,7 +1334,9 @@ static int target_method_get_version(sd_bus_message *msg, void *userdata, sd_bus version_json = sd_json_variant_by_key(v, "current"); if (!version_json) - return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'current'"); + version_json = sd_json_variant_by_key(v, "current+pending"); + if (!version_json) + return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'current' or 'current+pending'"); if (sd_json_variant_is_null(version_json)) return sd_bus_reply_method_return(msg, "s", ""); @@ -1415,6 +1427,19 @@ static int target_method_list_features(sd_bus_message *msg, void *userdata, sd_b return sd_bus_message_send(reply); } +static bool feature_name_is_valid(const char *name) { + if (isempty(name)) + return false; + + if (!ascii_is_valid(name)) + return false; + + if (!filename_is_valid(strjoina(name, ".feature.d"))) + return false; + + return true; +} + static int target_method_describe_feature(sd_bus_message *msg, void *userdata, sd_bus_error *error) { Target *t = ASSERT_PTR(userdata); _cleanup_(job_freep) Job *j = NULL; @@ -1428,8 +1453,8 @@ static int target_method_describe_feature(sd_bus_message *msg, void *userdata, s if (r < 0) return r; - if (isempty(feature)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Feature must be specified"); + if (!feature_name_is_valid(feature)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid feature name"); if (flags != 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0"); @@ -1450,19 +1475,6 @@ static int target_method_describe_feature(sd_bus_message *msg, void *userdata, s return 1; } -static bool feature_name_is_valid(const char *name) { - if (isempty(name)) - return false; - - if (!ascii_is_valid(name)) - return false; - - if (!filename_is_valid(strjoina(name, ".feature.d"))) - return false; - - return true; -} - static int target_method_set_feature_enabled(sd_bus_message *msg, void *userdata, sd_bus_error *error) { _cleanup_free_ char *feature_ext = NULL; Target *t = ASSERT_PTR(userdata); @@ -1870,6 +1882,9 @@ static int manager_enumerate_image_class(Manager *m, TargetClass class) { if (image_is_host(image)) continue; /* We already enroll the host ourselves */ + if (image->type == IMAGE_MSTACK) + continue; /* systemd-sysupdate doesn't support mstack images yet */ + r = target_new(m, class, image->name, image->path, &t); if (r < 0) return r; diff --git a/src/sysupdate/updatectl.c b/src/sysupdate/updatectl.c index 60523181bcdb1..c6e5c33fdfe48 100644 --- a/src/sysupdate/updatectl.c +++ b/src/sysupdate/updatectl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" @@ -19,6 +18,7 @@ #include "hashmap.h" #include "json-util.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "polkit-agent.h" #include "pretty-print.h" @@ -75,6 +75,8 @@ typedef struct Operation { /* Only used for Acquire()/Install() operations: */ char *acquired_version; + /* The version the user requested, possibly empty */ + char *requested_version; } Operation; static Operation* operation_free(Operation *p) { @@ -90,6 +92,7 @@ static Operation* operation_free(Operation *p) { free(p->job_path); free(p->acquired_version); + free(p->requested_version); sd_event_source_disable_unref(p->job_interrupt_source); sd_bus_slot_unref(p->job_properties_slot); @@ -641,7 +644,9 @@ static int describe(sd_bus *bus, const char *target_path, const char *version) { return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend); } -static int verb_list(int argc, char **argv, void *userdata) { +VERB(verb_list, "list", "[TARGET[@VERSION]]", VERB_ANY, 2, VERB_DEFAULT|VERB_ONLINE_ONLY, + "List available targets and versions"); +static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_free_ char *target_path = NULL, *version = NULL; int r; @@ -752,7 +757,9 @@ static int check_finished(sd_bus_message *reply, void *userdata, sd_bus_error *r return 0; } -static int verb_check(int argc, char **argv, void *userdata) { +VERB(verb_check, "check", "[TARGET...]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Check for updates"); +static int verb_check(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_(table_unrefp) Table *table = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -1068,7 +1075,7 @@ static int update_acquire_finished(sd_bus_message *m, void *userdata, sd_bus_err update_install_started, op, "st", - op->acquired_version, + op->requested_version, 0LU); if (r < 0) return log_bus_error(r, NULL, op->target_id, "call Install"); @@ -1246,6 +1253,11 @@ static int do_update(sd_bus *bus, char **targets) { 0LU); if (r < 0) return log_bus_error(r, NULL, targets[i], "call Acquire"); + + op->requested_version = strdup(versions[i]); + if (!op->requested_version) + return log_oom(); + TAKE_PTR(op); remaining++; @@ -1283,7 +1295,9 @@ static int do_update(sd_bus *bus, char **targets) { return did_anything ? 1 : 0; } -static int verb_update(int argc, char **argv, void *userdata) { +VERB(verb_update, "update", "[TARGET[@VERSION]...]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Install updates"); +static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_strv_free_ char **targets = NULL; bool did_anything = false; @@ -1336,7 +1350,9 @@ static int do_vacuum(sd_bus *bus, const char *target, const char *path) { return count + disabled > 0 ? 1 : 0; } -static int verb_vacuum(int argc, char **argv, void *userdata) { +VERB(verb_vacuum, "vacuum", "[TARGET...]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Clean up old updates"); +static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_strv_free_ char **targets = NULL, **target_paths = NULL; size_t n; @@ -1480,7 +1496,9 @@ static int list_features(sd_bus *bus) { return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend); } -static int verb_features(int argc, char **argv, void *userdata) { +VERB(verb_features, "features", "[FEATURE]", VERB_ANY, 2, VERB_ONLINE_ONLY, + "List and inspect optional features on host OS"); +static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_(table_unrefp) Table *table = NULL; _cleanup_(feature_done) Feature f = {}; @@ -1529,7 +1547,11 @@ static int verb_features(int argc, char **argv, void *userdata) { return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, false); } -static int verb_enable(int argc, char **argv, void *userdata) { +VERB(verb_enable, "enable", "FEATURE...", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Enable optional feature on host OS"); +VERB(verb_enable, "disable", "FEATURE...", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Disable optional feature on host OS"); +static int verb_enable(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); bool did_anything = false, enable; char **features; @@ -1610,111 +1632,102 @@ static int verb_enable(int argc, char **argv, void *userdata) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *verbs2 = NULL, *options = NULL; int r; r = terminal_urlify_man("updatectl", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [VERSION]\n" - "\n%5$sManage system updates.%6$s\n" - "\n%3$sCommands:%4$s\n" - " list [TARGET[@VERSION]] List available targets and versions\n" - " check [TARGET...] Check for updates\n" - " update [TARGET[@VERSION]...] Install updates\n" - " vacuum [TARGET...] Clean up old updates\n" - " features [FEATURE] List and inspect optional features on host OS\n" - " enable FEATURE... Enable optional feature on host OS\n" - " disable FEATURE... Disable optional feature on host OS\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sOptions:%4$s\n" - " --reboot Reboot after updating to newer version\n" - " --offline Do not fetch metadata from the network\n" - " --now Download/delete resources immediately\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - "\nSee the %2$s for details.\n" - , program_invocation_short_name - , link - , ansi_underline(), ansi_normal() - , ansi_highlight(), ansi_normal() - ); + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; - return 0; -} + r = option_parser_get_help_table_group("Verbs", &verbs2); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { + r = option_parser_get_help_table(&options); + if (r < 0) + return r; - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_REBOOT, - ARG_OFFLINE, - ARG_NOW, - }; + (void) table_sync_column_widths(0, verbs, verbs2, options); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "host", required_argument, NULL, 'H' }, - { "reboot", no_argument, NULL, ARG_REBOOT }, - { "offline", no_argument, NULL, ARG_OFFLINE }, - { "now", no_argument, NULL, ARG_NOW }, - {} - }; + printf("%s [OPTIONS...] [VERSION]\n" + "\n%sManage system updates.%s\n" + "\n%sCommands:%s\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + ansi_underline(), + ansi_normal()); - int c; + r = table_print_or_warn(verbs); + if (r < 0) + return r; + r = table_print_or_warn(verbs2); + if (r < 0) + return r; + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); + return 0; +} + +VERB_COMMON_HELP_HIDDEN(help); + +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hH:", options, NULL)) >= 0) { - switch (c) { + OptionParser state = { argc, argv }; + const char *arg; - case 'h': - return help(); + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + switch (c) { - case ARG_VERSION: - return version(); + OPTION_LONG("reboot", NULL, "Reboot after updating to newer version"): + arg_reboot = true; + break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION_LONG("offline", NULL, "Do not fetch metadata from the network"): + arg_offline = true; break; - case ARG_NO_LEGEND: - arg_legend = false; + OPTION_LONG("now", NULL, "Download/delete resources immediately"): + arg_now = true; break; - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = arg; break; - case ARG_REBOOT: - arg_reboot = true; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case ARG_OFFLINE: - arg_offline = true; + OPTION_COMMON_NO_LEGEND: + arg_legend = false; break; - case ARG_NOW: - arg_now = true; - break; + OPTION_GROUP("Verbs"): {} - case '?': - return -EINVAL; + OPTION_COMMON_HELP: + return help(); - default: - assert_not_reached(); + OPTION_COMMON_VERSION: + return version(); } - } + *ret_args = option_parser_get_args(&state); return 1; } @@ -1722,23 +1735,13 @@ static int run(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; - static const Verb verbs[] = { - { "list", VERB_ANY, 2, VERB_DEFAULT|VERB_ONLINE_ONLY, verb_list }, - { "check", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_check }, - { "update", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_update }, - { "vacuum", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_vacuum }, - { "features", VERB_ANY, 2, VERB_ONLINE_ONLY, verb_features }, - { "enable", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_enable }, - { "disable", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_enable }, - {} - }; - setlocale(LC_ALL, ""); log_setup(); (void) signal(SIGWINCH, columns_lines_cache_reset); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -1751,7 +1754,7 @@ static int run(int argc, char *argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, true); - return dispatch_verb(argc, argv, verbs, bus); + return dispatch_verb_with_args(args, bus); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index a113928f97789..ff391cb316578 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -16,6 +15,7 @@ #include "extract-word.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "format-util.h" #include "fs-util.h" #include "hashmap.h" @@ -28,6 +28,7 @@ #include "loop-util.h" #include "main-func.h" #include "mount-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "path-util.h" @@ -479,6 +480,8 @@ static int write_temporary_passwd( int r; assert(c); + assert(ret_tmpfile); + assert(ret_tmpfile_path); if (ordered_hashmap_isempty(c->todo_uids)) goto done; @@ -610,6 +613,8 @@ static int write_temporary_shadow( int r; assert(c); + assert(ret_tmpfile); + assert(ret_tmpfile_path); if (ordered_hashmap_isempty(c->todo_uids)) goto done; @@ -745,6 +750,8 @@ static int write_temporary_group( int r; assert(c); + assert(ret_tmpfile); + assert(ret_tmpfile_path); if (ordered_hashmap_isempty(c->todo_gids) && ordered_hashmap_isempty(c->members)) goto done; @@ -862,6 +869,8 @@ static int write_temporary_gshadow( int r; assert(c); + assert(ret_tmpfile); + assert(ret_tmpfile_path); if (ordered_hashmap_isempty(c->todo_gids) && ordered_hashmap_isempty(c->members)) goto done; @@ -1053,7 +1062,7 @@ static int uid_is_ok( assert(c); /* Let's see if we already have assigned the UID a second time */ - if (ordered_hashmap_get(c->todo_uids, UID_TO_PTR(uid))) + if (ordered_hashmap_contains(c->todo_uids, UID_TO_PTR(uid))) return 0; /* Try to avoid using uids that are already used by a group @@ -1292,7 +1301,7 @@ static int gid_is_ok( assert(c); assert(groupname); - if (ordered_hashmap_get(c->todo_gids, GID_TO_PTR(gid))) + if (ordered_hashmap_contains(c->todo_gids, GID_TO_PTR(gid))) return 0; /* Avoid reusing gids that are already used by a different user */ @@ -1568,7 +1577,7 @@ static int add_implicit(Context *c) { /* Implicitly create additional users and groups, if they were listed in "m" lines */ ORDERED_HASHMAP_FOREACH_KEY(l, g, c->members) { STRV_FOREACH(m, l) - if (!ordered_hashmap_get(c->users, *m)) { + if (!ordered_hashmap_contains(c->users, *m)) { _cleanup_(item_freep) Item *j = item_new(ADD_USER, *m, /* filename= */ NULL, /* line= */ 0); if (!j) @@ -1584,8 +1593,8 @@ static int add_implicit(Context *c) { TAKE_PTR(j); } - if (!(ordered_hashmap_get(c->users, g) || - ordered_hashmap_get(c->groups, g))) { + if (!(ordered_hashmap_contains(c->users, g) || + ordered_hashmap_contains(c->groups, g))) { _cleanup_(item_freep) Item *j = item_new(ADD_GROUP, g, /* filename= */ NULL, /* line= */ 0); if (!j) @@ -2048,143 +2057,126 @@ static int cat_config(void) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *cmds = NULL, *opts = NULL; int r; r = terminal_urlify_man("systemd-sysusers.service", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [CONFIGURATION FILE...]\n" - "\n%2$sCreates system user and group accounts.%4$s\n" - "\n%3$sCommands:%4$s\n" - " --cat-config Show configuration files\n" - " --tldr Show non-comment parts of configuration\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sOptions:%4$s\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY Specify disk image dissection policy\n" - " --replace=PATH Treat arguments as replacement for PATH\n" - " --dry-run Just print what would be done\n" - " --inline Treat arguments as configuration lines\n" - " --no-pager Do not pipe output into a pager\n" - "\nSee the %5$s for details.\n", + r = option_parser_get_help_table(&cmds); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("Options", &opts); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, cmds, opts); + + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n" + "\n%sCreates system user and group accounts.%s\n" + "\nCommands:\n", program_invocation_short_name, ansi_highlight(), - ansi_underline(), - ansi_normal(), - link); + ansi_normal()); - return 0; -} + r = table_print_or_warn(cmds); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_CAT_CONFIG, - ARG_TLDR, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_REPLACE, - ARG_DRY_RUN, - ARG_INLINE, - ARG_NO_PAGER, - }; + printf("\nOptions:\n"); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "cat-config", no_argument, NULL, ARG_CAT_CONFIG }, - { "tldr", no_argument, NULL, ARG_TLDR }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "replace", required_argument, NULL, ARG_REPLACE }, - { "dry-run", no_argument, NULL, ARG_DRY_RUN }, - { "inline", no_argument, NULL, ARG_INLINE }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - {} - }; + r = table_print_or_warn(opts); + if (r < 0) + return r; - int c, r; + printf("\nSee the %s for details.\n", link); + return 0; +} + +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(); - - case ARG_VERSION: - return version(); - - case ARG_CAT_CONFIG: + OPTION_COMMON_CAT_CONFIG: arg_cat_flags = CAT_CONFIG_ON; break; - case ARG_TLDR: + OPTION_COMMON_TLDR: arg_cat_flags = CAT_TLDR; break; - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root); + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_GROUP("Options"): {} + + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_REPLACE: - if (!path_is_absolute(optarg)) + OPTION_LONG("replace", "PATH", "Treat arguments as replacement for PATH"): + if (!path_is_absolute(arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must be an absolute path."); - if (!endswith(optarg, ".conf")) + if (!endswith(arg, ".conf")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must have the extension '.conf'."); - arg_replace = optarg; + arg_replace = arg; break; - case ARG_DRY_RUN: + OPTION_LONG("dry-run", NULL, "Just print what would be done"): arg_dry_run = true; break; - case ARG_INLINE: + OPTION_LONG("inline", NULL, "Treat arguments as configuration lines"): arg_inline = true; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + char **args = option_parser_get_args(&state); + size_t n_args = option_parser_get_n_args(&state); + if (arg_replace && arg_cat_flags != CAT_CONFIG_OFF) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --replace= is not supported with --cat-config/--tldr."); - if (arg_replace && optind >= argc) + if (arg_inline && arg_cat_flags != CAT_CONFIG_OFF) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Option --inline is not supported with --cat-config/--tldr."); + + if (arg_replace && n_args == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When --replace= is given, some configuration items must be specified."); @@ -2192,6 +2184,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Use either --root= or --image=, the combination of both is not supported."); + *ret_args = args; return 1; } @@ -2277,7 +2270,8 @@ static int run(int argc, char *argv[]) { Item *i; int r; - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -2327,10 +2321,10 @@ static int run(int argc, char *argv[]) { * insert the positional arguments at the specified place. Otherwise, if command line arguments are * specified, execute just them, and finally, without --replace= or any positional arguments, just * read configuration and execute it. */ - if (arg_replace || optind >= argc) - r = read_config_files(&c, argv + optind); + if (arg_replace || strv_isempty(args)) + r = read_config_files(&c, args); else - r = parse_arguments(&c, argv + optind); + r = parse_arguments(&c, args); if (r < 0) return r; diff --git a/src/test/meson.build b/src/test/meson.build index adbcd3c0d4dcf..6f9a24eb04483 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -130,6 +130,7 @@ simple_tests += files( 'test-install-root.c', 'test-io-util.c', 'test-iovec-util.c', + 'test-iovec-wrapper.c', 'test-journal-importer.c', 'test-kbd-util.c', 'test-label.c', @@ -156,6 +157,7 @@ simple_tests += files( 'test-nsresource.c', 'test-nulstr-util.c', 'test-open-file.c', + 'test-options.c', 'test-ordered-set.c', 'test-os-util.c', 'test-osc-context.c', @@ -287,8 +289,8 @@ executables += [ 'c_args' : ['-fno-sanitize=all', '-fno-optimize-sibling-calls', '-O1'], }, test_template + { - 'sources' : files('test-cryptolib.c'), - 'dependencies' : libopenssl, + 'sources' : files('test-crypto-util.c'), + 'dependencies' : libopenssl_cflags, 'conditions' : ['HAVE_OPENSSL'], }, test_template + { @@ -299,7 +301,10 @@ executables += [ 'sources' : files('test-dlopen-so.c'), 'dependencies' : [ libblkid_cflags, + libfdisk_cflags, + libgnutls_cflags, libkmod_cflags, + libmicrohttpd_cflags, libmount_cflags, libp11kit_cflags, libseccomp_cflags, @@ -345,6 +350,10 @@ executables += [ 'sources' : files('test-json.c'), 'dependencies' : libm, }, + test_template + { + 'sources' : files('test-kexec.c'), + 'link_with' : [libshared], + }, test_template + { 'sources' : files('test-libcrypt-util.c'), 'conditions' : ['HAVE_LIBCRYPT'], @@ -367,7 +376,7 @@ executables += [ 'dependencies' : libm, }, test_template + { - 'sources' : files('test-mempress.c'), + 'sources' : files('test-pressure.c'), 'dependencies' : threads, }, test_template + { @@ -401,14 +410,9 @@ executables += [ 'dependencies' : libdl, 'conditions' : ['ENABLE_NSS'], }, - test_template + { - 'sources' : files('test-openssl.c'), - 'dependencies' : libopenssl, - 'conditions' : ['HAVE_OPENSSL'], - }, test_template + { 'sources' : files('test-pkcs7-util.c'), - 'dependencies' : libopenssl, + 'dependencies' : libopenssl_cflags, 'conditions' : ['HAVE_OPENSSL'], }, test_template + { @@ -426,6 +430,14 @@ executables += [ test_template + { 'sources' : files('test-progress-bar.c'), }, + test_template + { + 'sources' : files('test-qmp-client.c'), + }, + test_template + { + 'sources' : files('test-qmp-client-qemu.c'), + 'objects' : ['systemd-vmspawn'], + 'conditions' : ['ENABLE_VMSPAWN'], + }, test_template + { 'sources' : files('test-qrcode-util.c'), 'dependencies' : libdl, @@ -477,7 +489,7 @@ executables += [ }, test_template + { 'sources' : files('test-tpm2.c'), - 'dependencies' : libopenssl, + 'dependencies' : libopenssl_cflags, 'timeout' : 120, }, test_template + { @@ -492,6 +504,16 @@ executables += [ 'sources' : files('test-varlink-idl.c'), 'dependencies' : threads, }, + core_test_template + { + 'sources' : files('test-varlink-idl-unit.c'), + }, + core_test_template + { + 'sources' : files('test-varlink-idl-manager.c'), + }, + test_template + { + 'sources' : files('test-varlink-idl-machine.c'), + 'objects' : ['systemd-machined'], + }, test_template + { 'sources' : files('test-watchdog.c'), 'type' : 'unsafe', @@ -579,7 +601,7 @@ executables += [ 'dependencies' : common_test_dependencies, }, core_test_template + { - 'sources' : files('test-loop-block.c'), + 'sources' : files('test-loop-util.c'), 'dependencies' : [threads], 'parallel' : false, }, diff --git a/src/test/test-bpf-token.c b/src/test/test-bpf-token.c index f0ba50e715f84..7e83a048e52f9 100644 --- a/src/test/test-bpf-token.c +++ b/src/test/test-bpf-token.c @@ -4,9 +4,10 @@ #include #include "fd-util.h" -#include "tests.h" +#include "main-func.h" +#include "tests.h" /* NOLINT(misc-include-cleaner): this is needed conditionally */ -static int intro(void) { +static int run(int argc, char *argv[]) { #if defined(LIBBPF_MAJOR_VERSION) && (LIBBPF_MAJOR_VERSION > 1 || (LIBBPF_MAJOR_VERSION == 1 && LIBBPF_MINOR_VERSION >= 5)) _cleanup_close_ int bpffs_fd = open("/sys/fs/bpf", O_RDONLY); if (bpffs_fd < 0) @@ -16,10 +17,11 @@ static int intro(void) { if (token_fd < 0) return log_error_errno(errno, "Failed to create bpf token: %m"); - return EXIT_SUCCESS; + log_info("Successfully created token fd."); + return 0; #else return log_tests_skipped("libbpf is older than v1.5"); #endif } -DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/test/test-chase-manual.c b/src/test/test-chase-manual.c index 376cd12c42628..0e760aeb89676 100644 --- a/src/test/test-chase-manual.c +++ b/src/test/test-chase-manual.c @@ -1,84 +1,75 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include "chase.h" #include "fd-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" +#include "strv.h" #include "tests.h" -static char *arg_root = NULL; +static const char *arg_root = NULL; static int arg_flags = 0; static bool arg_open = false; -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_ROOT = 0x1000, - ARG_OPEN, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "open", no_argument, NULL, ARG_OPEN }, - - { "prefix-root", no_argument, NULL, CHASE_PREFIX_ROOT }, - { "nonexistent", no_argument, NULL, CHASE_NONEXISTENT }, - { "no_autofs", no_argument, NULL, CHASE_NO_AUTOFS }, - { "trigger-autofs", no_argument, NULL, CHASE_TRIGGER_AUTOFS }, - { "safe", no_argument, NULL, CHASE_SAFE }, - { "trail-slash", no_argument, NULL, CHASE_TRAIL_SLASH }, - { "step", no_argument, NULL, CHASE_STEP }, - { "nofollow", no_argument, NULL, CHASE_NOFOLLOW }, - { "warn", no_argument, NULL, CHASE_WARN }, - {} - }; - - int c; - - assert_se(argc >= 0); - assert_se(argv); - - while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] path...\n" + "\nExercise chase() function on specified paths.\n\n", + program_invocation_short_name); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + return 0; +} + +static int parse_argv(int argc, char *argv[], char ***ret_args) { + assert(argc >= 0); + assert(argv); + assert(ret_args); + + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + + FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) switch (c) { - case 'h': - printf("Syntax:\n" - " %s [OPTION...] path...\n" - "Options:\n" - , argv[0]); - FOREACH_ARRAY(option, options, ELEMENTSOF(options) - 1) - printf(" --%s\n", option->name); - return 0; - - case ARG_ROOT: - arg_root = optarg; + OPTION_COMMON_HELP: + return help(); + + OPTION_LONG("root", "PATH", "Operate below specified root directory"): + arg_root = arg; break; - case ARG_OPEN: + OPTION_LONG("open", NULL, "Open the resolved path"): arg_open = true; break; - case CHASE_PREFIX_ROOT: - case CHASE_NONEXISTENT: - case CHASE_NO_AUTOFS: - case CHASE_TRIGGER_AUTOFS: - case CHASE_SAFE: - case CHASE_TRAIL_SLASH: - case CHASE_STEP: - case CHASE_NOFOLLOW: - case CHASE_WARN: - arg_flags |= c; + OPTION_LONG_DATA("prefix-root", NULL, CHASE_PREFIX_ROOT, "Prefix path with --root"): {} + OPTION_LONG_DATA("nonexistent", NULL, CHASE_NONEXISTENT, "Allow path to not exist"): {} + OPTION_LONG_DATA("no-autofs", NULL, CHASE_NO_AUTOFS, "Return -EREMOTE if autofs mount point found"): {} + OPTION_LONG_DATA("trigger-autofs", NULL, CHASE_TRIGGER_AUTOFS, "Trigger autofs mounts"): {} + OPTION_LONG_DATA("safe", NULL, CHASE_SAFE, "Refuse privilege boundary crossings"): {} + OPTION_LONG_DATA("trail-slash", NULL, CHASE_TRAIL_SLASH, "Preserve trailing slash"): {} + OPTION_LONG_DATA("step", NULL, CHASE_STEP, "Execute a single normalization step"): {} + OPTION_LONG_DATA("nofollow", NULL, CHASE_NOFOLLOW, "Do not follow the path's right-most component"): {} + OPTION_LONG_DATA("warn", NULL, CHASE_WARN, "Emit a warning on error"): + arg_flags |= opt->data; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind == argc) + *ret_args = option_parser_get_args(&state); + if (strv_isempty(*ret_args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument is required."); return 1; @@ -89,18 +80,19 @@ static int run(int argc, char **argv) { test_setup_logging(LOG_DEBUG); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - for (int i = optind; i < argc; i++) { + STRV_FOREACH(a, args) { _cleanup_free_ char *p = NULL; _cleanup_close_ int fd = -EBADF; - printf("%s ", argv[i]); + printf("%s ", *a); fflush(stdout); - r = chase(argv[i], arg_root, arg_flags, &p, arg_open ? &fd : NULL); + r = chase(*a, arg_root, arg_flags, &p, arg_open ? &fd : NULL); if (r < 0) log_error_errno(r, "failed: %m"); else { diff --git a/src/test/test-chase.c b/src/test/test-chase.c index 129ea19b7237d..ed07ac5cef68d 100644 --- a/src/test/test-chase.c +++ b/src/test/test-chase.c @@ -27,10 +27,10 @@ static void test_chase_extract_filename_one(const char *path, const char *root, log_debug("/* %s(path=%s, root=%s) */", __func__, path, strnull(root)); - assert_se(chase(path, root, CHASE_EXTRACT_FILENAME, &ret1, NULL) > 0); + ASSERT_OK_POSITIVE(chase(path, root, CHASE_EXTRACT_FILENAME, &ret1, NULL)); ASSERT_STREQ(ret1, expected); - assert_se(chase(path, root, 0, &ret2, NULL) > 0); + ASSERT_OK_POSITIVE(chase(path, root, 0, &ret2, NULL)); ASSERT_OK(chase_extract_filename(ret2, root, &fname)); ASSERT_STREQ(fname, expected); } @@ -41,97 +41,84 @@ TEST(chase) { char *temp; const char *top, *p, *pslash, *q, *qslash; struct stat st; - int r; temp = strjoina(arg_test_dir ?: "/tmp", "/test-chase.XXXXXX"); - assert_se(mkdtemp(temp)); + ASSERT_NOT_NULL(mkdtemp(temp)); top = strjoina(temp, "/top"); ASSERT_OK(mkdir(top, 0700)); p = strjoina(top, "/dot"); if (symlink(".", p) < 0) { - assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM)); + ASSERT_TRUE(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM)); log_tests_skipped_errno(errno, "symlink() not possible"); goto cleanup; }; p = strjoina(top, "/dotdot"); - ASSERT_OK(symlink("..", p)); + ASSERT_OK_ERRNO(symlink("..", p)); p = strjoina(top, "/dotdota"); - ASSERT_OK(symlink("../a", p)); + ASSERT_OK_ERRNO(symlink("../a", p)); p = strjoina(temp, "/a"); - ASSERT_OK(symlink("b", p)); + ASSERT_OK_ERRNO(symlink("b", p)); p = strjoina(temp, "/b"); - ASSERT_OK(symlink("/usr", p)); + ASSERT_OK_ERRNO(symlink("/usr", p)); p = strjoina(temp, "/start"); - ASSERT_OK(symlink("top/dot/dotdota", p)); + ASSERT_OK_ERRNO(symlink("top/dot/dotdota", p)); /* Paths that use symlinks underneath the "root" */ - r = chase(p, NULL, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/usr")); + ASSERT_OK_POSITIVE(chase(p, NULL, 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/usr"); result = mfree(result); - r = chase(p, "/.//../../../", 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/usr")); + ASSERT_OK_POSITIVE(chase(p, "/.//../../../", 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/usr"); result = mfree(result); pslash = strjoina(p, "/"); - r = chase(pslash, NULL, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/usr/")); + ASSERT_OK_POSITIVE(chase(pslash, NULL, 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/usr/"); result = mfree(result); - r = chase(p, temp, 0, &result, NULL); - assert_se(r == -ENOENT); - - r = chase(pslash, temp, 0, &result, NULL); - assert_se(r == -ENOENT); + ASSERT_ERROR(chase(p, temp, 0, &result, NULL), ENOENT); + ASSERT_ERROR(chase(pslash, temp, 0, &result, NULL), ENOENT); q = strjoina(temp, "/usr"); - r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); - assert_se(path_equal(result, q)); + ASSERT_OK_ZERO(chase(p, temp, CHASE_NONEXISTENT, &result, NULL)); + ASSERT_PATH_EQ(result, q); result = mfree(result); qslash = strjoina(q, "/"); - r = chase(pslash, temp, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); - assert_se(path_equal(result, qslash)); + ASSERT_OK_ZERO(chase(pslash, temp, CHASE_NONEXISTENT, &result, NULL)); + ASSERT_PATH_EQ(result, qslash); result = mfree(result); - ASSERT_OK(mkdir(q, 0700)); + ASSERT_OK_ERRNO(mkdir(q, 0700)); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, q)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, q); result = mfree(result); - r = chase(pslash, temp, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, qslash)); + ASSERT_OK_POSITIVE(chase(pslash, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, qslash); result = mfree(result); p = strjoina(temp, "/slash"); - assert_se(symlink("/", p) >= 0); + ASSERT_OK_ERRNO(symlink("/", p)); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/")); + ASSERT_OK_POSITIVE(chase(p, NULL, 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/"); result = mfree(result); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, temp)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, temp); result = mfree(result); /* Tests for CHASE_EXTRACT_FILENAME and chase_extract_filename() */ @@ -150,205 +137,182 @@ TEST(chase) { /* Paths that would "escape" outside of the "root" */ p = strjoina(temp, "/6dots"); - ASSERT_OK(symlink("../../..", p)); + ASSERT_OK_ERRNO(symlink("../../..", p)); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0 && path_equal(result, temp)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, temp); result = mfree(result); p = strjoina(temp, "/6dotsusr"); - ASSERT_OK(symlink("../../../usr", p)); + ASSERT_OK_ERRNO(symlink("../../../usr", p)); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0 && path_equal(result, q)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, q); result = mfree(result); p = strjoina(temp, "/top/8dotsusr"); - ASSERT_OK(symlink("../../../../usr", p)); + ASSERT_OK_ERRNO(symlink("../../../../usr", p)); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0 && path_equal(result, q)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, q); result = mfree(result); /* Paths that contain repeated slashes */ p = strjoina(temp, "/slashslash"); - ASSERT_OK(symlink("///usr///", p)); + ASSERT_OK_ERRNO(symlink("///usr///", p)); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/usr")); + ASSERT_OK_POSITIVE(chase(p, NULL, 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/usr"); ASSERT_STREQ(result, "/usr"); /* we guarantee that we drop redundant slashes */ result = mfree(result); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, q)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, q); result = mfree(result); /* Paths underneath the "root" with different UIDs while using CHASE_SAFE */ if (geteuid() == 0 && !userns_has_single_user()) { p = strjoina(temp, "/user"); - ASSERT_OK(mkdir(p, 0755)); - ASSERT_OK(chown(p, UID_NOBODY, GID_NOBODY)); + ASSERT_OK_ERRNO(mkdir(p, 0755)); + ASSERT_OK_ERRNO(chown(p, UID_NOBODY, GID_NOBODY)); q = strjoina(temp, "/user/root"); - ASSERT_OK(mkdir(q, 0755)); + ASSERT_OK_ERRNO(mkdir(q, 0755)); p = strjoina(q, "/link"); - ASSERT_OK(symlink("/", p)); + ASSERT_OK_ERRNO(symlink("/", p)); /* Fail when user-owned directories contain root-owned subdirectories. */ - r = chase(p, temp, CHASE_SAFE, &result, NULL); - assert_se(r == -ENOLINK); + ASSERT_ERROR(chase(p, temp, CHASE_SAFE, &result, NULL), ENOLINK); result = mfree(result); /* Allow this when the user-owned directories are all in the "root". */ - r = chase(p, q, CHASE_SAFE, &result, NULL); - assert_se(r > 0); + ASSERT_OK_POSITIVE(chase(p, q, CHASE_SAFE, &result, NULL)); result = mfree(result); } /* Paths using . */ - r = chase("/etc/./.././", NULL, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/")); + ASSERT_OK_POSITIVE(chase("/etc/./.././", NULL, 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/"); result = mfree(result); - r = chase("/etc/./.././", "/etc", 0, &result, NULL); - assert_se(r > 0 && path_equal(result, "/etc")); + ASSERT_OK_POSITIVE(chase("/etc/./.././", "/etc", 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/etc"); result = mfree(result); - r = chase("/../.././//../../etc", NULL, 0, &result, NULL); - assert_se(r > 0); + ASSERT_OK_POSITIVE(chase("/../.././//../../etc", NULL, 0, &result, NULL)); ASSERT_STREQ(result, "/etc"); result = mfree(result); - r = chase("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result, NULL)); ASSERT_STREQ(result, "/test-chase.fsldajfl"); result = mfree(result); - r = chase("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result, NULL); - assert_se(r > 0); + ASSERT_OK_POSITIVE(chase("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result, NULL)); ASSERT_STREQ(result, "/etc"); result = mfree(result); - r = chase("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL)); ASSERT_STREQ(result, "/test-chase.fsldajfl"); result = mfree(result); - r = chase("/.path/with/dot", temp, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL); - ASSERT_OK(r); + ASSERT_OK(chase("/.path/with/dot", temp, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL)); q = strjoina(temp, "/.path/with/dot"); ASSERT_STREQ(result, q); result = mfree(result); - r = chase("/etc/machine-id/foo", NULL, 0, &result, NULL); - assert_se(IN_SET(r, -ENOTDIR, -ENOENT)); + ASSERT_TRUE(IN_SET(chase("/etc/machine-id/foo", NULL, 0, &result, NULL), -ENOTDIR, -ENOENT)); result = mfree(result); /* Path that loops back to self */ p = strjoina(temp, "/recursive-symlink"); - ASSERT_OK(symlink("recursive-symlink", p)); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r == -ELOOP); + ASSERT_OK_ERRNO(symlink("recursive-symlink", p)); + ASSERT_ERROR(chase(p, NULL, 0, &result, NULL), ELOOP); /* Path which doesn't exist */ p = strjoina(temp, "/idontexist"); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r == -ENOENT); - - r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); - assert_se(path_equal(result, p)); + ASSERT_ERROR(chase(p, NULL, 0, &result, NULL), ENOENT); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_NONEXISTENT, &result, NULL)); + ASSERT_PATH_EQ(result, p); result = mfree(result); p = strjoina(temp, "/idontexist/meneither"); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r == -ENOENT); + ASSERT_ERROR(chase(p, NULL, 0, &result, NULL), ENOENT); - r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); - assert_se(path_equal(result, p)); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_NONEXISTENT, &result, NULL)); + ASSERT_PATH_EQ(result, p); result = mfree(result); /* Relative paths */ ASSERT_OK(safe_getcwd(&pwd)); - ASSERT_OK(chdir(temp)); + ASSERT_OK_ERRNO(chdir(temp)); p = "this/is/a/relative/path"; - r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_NONEXISTENT, &result, NULL)); p = strjoina(temp, "/", p); - assert_se(path_equal(result, p)); + ASSERT_PATH_EQ(result, p); result = mfree(result); p = "this/is/a/relative/path"; - r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, temp, CHASE_NONEXISTENT, &result, NULL)); p = strjoina(temp, "/", p); - assert_se(path_equal(result, p)); + ASSERT_PATH_EQ(result, p); result = mfree(result); - assert_se(chdir(pwd) >= 0); + ASSERT_OK_ERRNO(chdir(pwd)); /* Path which doesn't exist, but contains weird stuff */ p = strjoina(temp, "/idontexist/.."); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r == -ENOENT); + ASSERT_ERROR(chase(p, NULL, 0, &result, NULL), ENOENT); - r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == -ENOENT); + ASSERT_ERROR(chase(p, NULL, CHASE_NONEXISTENT, &result, NULL), ENOENT); p = strjoina(temp, "/target"); q = strjoina(temp, "/top"); - assert_se(symlink(q, p) >= 0); + ASSERT_OK_ERRNO(symlink(q, p)); p = strjoina(temp, "/target/idontexist"); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r == -ENOENT); + ASSERT_ERROR(chase(p, NULL, 0, &result, NULL), ENOENT); if (geteuid() == 0 && !userns_has_single_user()) { p = strjoina(temp, "/priv1"); - ASSERT_OK(mkdir(p, 0755)); + ASSERT_OK_ERRNO(mkdir(p, 0755)); q = strjoina(p, "/priv2"); - ASSERT_OK(mkdir(q, 0755)); + ASSERT_OK_ERRNO(mkdir(q, 0755)); ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); - ASSERT_OK(chown(q, UID_NOBODY, GID_NOBODY)); + ASSERT_OK_ERRNO(chown(q, UID_NOBODY, GID_NOBODY)); ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); ASSERT_OK(chown(p, UID_NOBODY, GID_NOBODY)); ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); - assert_se(chown(q, 0, 0) >= 0); - assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK); + ASSERT_OK_ERRNO(chown(q, 0, 0)); + ASSERT_ERROR(chase(q, NULL, CHASE_SAFE, NULL, NULL), ENOLINK); - ASSERT_OK(rmdir(q)); - ASSERT_OK(symlink("/etc/passwd", q)); - assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK); + ASSERT_OK_ERRNO(rmdir(q)); + ASSERT_OK_ERRNO(symlink("/etc/passwd", q)); + ASSERT_ERROR(chase(q, NULL, CHASE_SAFE, NULL, NULL), ENOLINK); - assert_se(chown(p, 0, 0) >= 0); + ASSERT_OK_ERRNO(chown(p, 0, 0)); ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); } p = strjoina(temp, "/machine-id-test"); - ASSERT_OK(symlink("/usr/../etc/./machine-id", p)); + ASSERT_OK_ERRNO(symlink("/usr/../etc/./machine-id", p)); - r = chase(p, NULL, 0, NULL, &pfd); - if (r != -ENOENT && sd_id128_get_machine(NULL) >= 0) { + if (chase(p, NULL, 0, NULL, &pfd) != -ENOENT && sd_id128_get_machine(NULL) >= 0) { _cleanup_close_ int fd = -EBADF; sd_id128_t a, b; @@ -360,112 +324,187 @@ TEST(chase) { ASSERT_OK(id128_read_fd(fd, ID128_FORMAT_PLAIN, &a)); ASSERT_OK(sd_id128_get_machine(&b)); - assert_se(sd_id128_equal(a, b)); + ASSERT_TRUE(sd_id128_equal(a, b)); } - assert_se(lstat(p, &st) >= 0); - r = chase_and_unlink(p, NULL, 0, 0, &result); - assert_se(r == 0); - assert_se(path_equal(result, p)); + ASSERT_OK_ERRNO(lstat(p, &st)); + ASSERT_OK_ZERO(chase_and_unlink(p, NULL, 0, 0, &result)); + ASSERT_PATH_EQ(result, p); result = mfree(result); - assert_se(lstat(p, &st) == -1 && errno == ENOENT); + ASSERT_ERROR_ERRNO(lstat(p, &st), ENOENT); /* Test CHASE_NOFOLLOW */ p = strjoina(temp, "/target"); q = strjoina(temp, "/symlink"); - assert_se(symlink(p, q) >= 0); - r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd); - ASSERT_OK(r); - ASSERT_OK(pfd); - assert_se(path_equal(result, q)); - ASSERT_OK(fstat(pfd, &st)); - assert_se(S_ISLNK(st.st_mode)); + ASSERT_OK_ERRNO(symlink(p, q)); + ASSERT_OK(chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd)); + ASSERT_PATH_EQ(result, q); + ASSERT_OK_ERRNO(fstat(pfd, &st)); + ASSERT_TRUE(S_ISLNK(st.st_mode)); result = mfree(result); pfd = safe_close(pfd); /* s1 -> s2 -> nonexistent */ q = strjoina(temp, "/s1"); - ASSERT_OK(symlink("s2", q)); + ASSERT_OK_ERRNO(symlink("s2", q)); p = strjoina(temp, "/s2"); - ASSERT_OK(symlink("nonexistent", p)); - r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd); - ASSERT_OK(r); - ASSERT_OK(pfd); - assert_se(path_equal(result, q)); - ASSERT_OK(fstat(pfd, &st)); - assert_se(S_ISLNK(st.st_mode)); + ASSERT_OK_ERRNO(symlink("nonexistent", p)); + ASSERT_OK(chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd)); + ASSERT_PATH_EQ(result, q); + ASSERT_OK_ERRNO(fstat(pfd, &st)); + ASSERT_TRUE(S_ISLNK(st.st_mode)); result = mfree(result); pfd = safe_close(pfd); /* Test CHASE_STEP */ p = strjoina(temp, "/start"); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); p = strjoina(temp, "/top/dot/dotdota"); ASSERT_STREQ(p, result); result = mfree(result); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); p = strjoina(temp, "/top/dotdota"); ASSERT_STREQ(p, result); result = mfree(result); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); p = strjoina(temp, "/top/../a"); ASSERT_STREQ(p, result); result = mfree(result); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); p = strjoina(temp, "/a"); ASSERT_STREQ(p, result); result = mfree(result); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); p = strjoina(temp, "/b"); ASSERT_STREQ(p, result); result = mfree(result); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); - r = chase("/usr", NULL, CHASE_STEP, &result, NULL); - assert_se(r > 0); + ASSERT_OK_POSITIVE(chase("/usr", NULL, CHASE_STEP, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); /* Make sure that symlinks in the "root" path are not resolved, but those below are */ p = strjoina("/etc/..", temp, "/self"); - assert_se(symlink(".", p) >= 0); + ASSERT_OK_ERRNO(symlink(".", p)); q = strjoina(p, "/top/dot/dotdota"); - r = chase(q, p, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(path_startswith(result, p), "usr")); + ASSERT_OK_POSITIVE(chase(q, p, 0, &result, NULL)); + ASSERT_PATH_EQ(path_startswith(result, p), "usr"); result = mfree(result); /* Test CHASE_PROHIBIT_SYMLINKS */ - assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); - assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); - assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); - assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); - assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); - assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); + ASSERT_ERROR(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL), EREMCHG); + ASSERT_ERROR(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL), EREMCHG); + ASSERT_ERROR(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL), EREMCHG); + ASSERT_ERROR(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL), EREMCHG); + ASSERT_ERROR(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL), EREMCHG); + ASSERT_ERROR(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL), EREMCHG); cleanup: ASSERT_OK(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL)); } +TEST(chase_and_open) { + _cleanup_free_ char *result = NULL; + _cleanup_close_ int fd = -EBADF; + + /* Test chase_and_open() with various CHASE_PARENT / CHASE_EXTRACT_FILENAME combinations. */ + + /* No CHASE_PARENT, no CHASE_EXTRACT_FILENAME, with ret_path — opens the target, returns full path. */ + fd = ASSERT_OK(chase_and_open("/usr/lib", NULL, 0, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_STREQ(result, "/usr/lib"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT with ret_path — opens parent dir, returns full path including final component. */ + fd = ASSERT_OK(chase_and_open("/usr/lib", NULL, CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "lib", F_OK, 0)); + ASSERT_STREQ(result, "/usr/lib"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_EXTRACT_FILENAME — opens parent dir, returns just the filename. */ + fd = ASSERT_OK(chase_and_open("/usr/lib", NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "lib", F_OK, 0)); + ASSERT_STREQ(result, "lib"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_EXTRACT_FILENAME only — opens the target itself, returns just the filename. */ + fd = ASSERT_OK(chase_and_open("/usr/lib", NULL, CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_STREQ(result, "lib"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_EXTRACT_FILENAME on a regular file (regression test for a bug where chase_and_open() + * reopened the parent directory instead of the target file). */ + fd = ASSERT_OK(chase_and_open("/etc/os-release", NULL, CHASE_EXTRACT_FILENAME, O_PATH|O_CLOEXEC, &result)); + ASSERT_STREQ(result, "os-release"); + ASSERT_OK(fd_verify_regular(fd)); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT through a symlink — symlink is followed, parent of the target is opened. */ + fd = ASSERT_OK(chase_and_open("/etc/os-release", NULL, CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_NOT_NULL(result); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_NOFOLLOW through a symlink — symlink is NOT followed, parent of the + * symlink is opened. */ + fd = ASSERT_OK(chase_and_open("/etc/os-release", NULL, CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "os-release", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_STREQ(result, "/etc/os-release"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME through a symlink — parent of the symlink + * is opened, returns just the symlink name. */ + fd = ASSERT_OK(chase_and_open("/etc/os-release", NULL, CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "os-release", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_STREQ(result, "os-release"); + fd = safe_close(fd); + result = mfree(result); + + /* When the resolved path equals the root directory itself, the filename should be "." — not + * the basename of the root directory. This is the edge case that chase_extract_filename() + * handles by stripping the root prefix before extracting, which plain path_extract_filename() + * would get wrong. */ + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + _cleanup_close_ int tfd = -EBADF; + + tfd = ASSERT_OK(mkdtemp_open(NULL, 0, &tmpdir)); + /* Create a symlink to "/" — when chased under tmpdir as root, it resolves to tmpdir itself. */ + ASSERT_OK_ERRNO(symlinkat("/", tfd, "to_root")); + + _cleanup_free_ char *link_path = ASSERT_NOT_NULL(path_join(tmpdir, "to_root")); + fd = ASSERT_OK(chase_and_open(link_path, tmpdir, 0, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_PATH_EQ(result, tmpdir); + fd = safe_close(fd); + result = mfree(result); +} + TEST(chaseat) { _cleanup_(rm_rf_physical_and_freep) char *t = NULL; - _cleanup_close_ int tfd = -EBADF, fd = -EBADF; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF, fd2 = -EBADF; _cleanup_free_ char *result = NULL; _cleanup_closedir_ DIR *dir = NULL; _cleanup_fclose_ FILE *f = NULL; @@ -474,13 +513,13 @@ TEST(chaseat) { ASSERT_OK(tfd = mkdtemp_open(NULL, 0, &t)); - /* Test that AT_FDCWD with CHASE_AT_RESOLVE_IN_ROOT resolves against / and not the current working + /* Test that AT_FDCWD resolves against / and not the current working * directory. */ - ASSERT_OK(symlinkat("/usr", tfd, "abc")); + ASSERT_OK_ERRNO(symlinkat("/usr", tfd, "abc")); p = strjoina(t, "/abc"); - ASSERT_OK(chaseat(AT_FDCWD, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_OK(chaseat(XAT_FDROOT, AT_FDCWD, p, 0, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); @@ -489,11 +528,24 @@ TEST(chaseat) { fd = open("/", O_CLOEXEC | O_DIRECTORY | O_PATH); ASSERT_OK(fd); - ASSERT_OK(chaseat(fd, p, 0, &result, NULL)); + ASSERT_OK(chaseat(XAT_FDROOT, fd, p, 0, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); - ASSERT_OK(chaseat(fd, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + /* (XAT_FDROOT, fd-of-/, relative): fd points to "/" which is the host root, so root_fd is + * normalized to XAT_FDROOT internally. A relative path resolves from "/". Result is absolute. */ + ASSERT_OK(chaseat(XAT_FDROOT, fd, "usr", 0, &result, &fd2)); + ASSERT_STREQ(result, "/usr"); + ASSERT_TRUE(inode_same_at(fd2, NULL, AT_FDCWD, "/usr", AT_EMPTY_PATH)); + result = mfree(result); + fd2 = safe_close(fd2); + + /* Same without ret_path to exercise the shortcut. */ + ASSERT_OK(chaseat(XAT_FDROOT, fd, "usr", 0, NULL, &fd2)); + ASSERT_TRUE(inode_same_at(fd2, NULL, AT_FDCWD, "/usr", AT_EMPTY_PATH)); + fd2 = safe_close(fd2); + + ASSERT_OK(chaseat(fd, fd, p, 0, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); @@ -501,12 +553,12 @@ TEST(chaseat) { /* Same but with XAT_FDROOT */ _cleanup_close_ int found_fd1 = -EBADF; - ASSERT_OK(chaseat(XAT_FDROOT, p, 0, &result, &found_fd1)); + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, p, 0, &result, &found_fd1)); ASSERT_STREQ(result, "/usr"); result = mfree(result); _cleanup_close_ int found_fd2 = -EBADF; - ASSERT_OK(chaseat(XAT_FDROOT, p, CHASE_AT_RESOLVE_IN_ROOT, &result, &found_fd2)); + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, p, 0, &result, &found_fd2)); ASSERT_STREQ(result, "/usr"); result = mfree(result); assert(fd_inode_same(found_fd1, found_fd2) > 0); @@ -514,12 +566,12 @@ TEST(chaseat) { /* Do the same XAT_FDROOT tests again, this time without querying the path, so that the open_tree() * shortcut can work */ _cleanup_close_ int found_fd3 = -EBADF; - ASSERT_OK(chaseat(XAT_FDROOT, p, 0, NULL, &found_fd3)); + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, p, 0, NULL, &found_fd3)); assert(fd_inode_same(found_fd1, found_fd3) > 0); assert(fd_inode_same(found_fd2, found_fd3) > 0); _cleanup_close_ int found_fd4 = -EBADF; - ASSERT_OK(chaseat(XAT_FDROOT, p, CHASE_AT_RESOLVE_IN_ROOT, NULL, &found_fd4)); + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, p, 0, NULL, &found_fd4)); assert(fd_inode_same(found_fd1, found_fd4) > 0); assert(fd_inode_same(found_fd2, found_fd4) > 0); assert(fd_inode_same(found_fd3, found_fd4) > 0); @@ -529,144 +581,272 @@ TEST(chaseat) { found_fd3 = safe_close(found_fd3); found_fd4 = safe_close(found_fd4); - /* If the file descriptor does not point to the root directory, the result will be relative - * unless the result is outside of the specified file descriptor. */ + /* (XAT_FDROOT, XAT_FDROOT, relative): relative path from host root. XAT_FDROOT as dir_fd + * redirects to root_fd which is also XAT_FDROOT (/), so "usr" resolves to /usr. Result is + * absolute because root_fd == XAT_FDROOT. */ + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, "usr", 0, &result, NULL)); + ASSERT_STREQ(result, "/usr"); + result = mfree(result); + + /* Same without ret_path so the shortcut can fire. */ + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, "usr", 0, NULL, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, AT_FDCWD, "/usr", AT_EMPTY_PATH)); + fd = safe_close(fd); + + /* (XAT_FDROOT, AT_FDCWD, relative): relative path from current working directory. */ + _cleanup_free_ char *cwd_saved = NULL; + ASSERT_OK(safe_getcwd(&cwd_saved)); + + ASSERT_OK_ERRNO(chdir(t)); - ASSERT_OK(chaseat(tfd, "abc", 0, &result, NULL)); + ASSERT_OK(chaseat(XAT_FDROOT, AT_FDCWD, "abc", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, AT_FDCWD, "/usr", AT_EMPTY_PATH)); ASSERT_STREQ(result, "/usr"); + fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "/abc", 0, &result, NULL)); + /* Same without ret_path to exercise the shortcut. */ + ASSERT_OK(chaseat(XAT_FDROOT, AT_FDCWD, "abc", 0, NULL, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, AT_FDCWD, "/usr", AT_EMPTY_PATH)); + fd = safe_close(fd); + + /* A plain file (no symlink indirection) should also work. */ + fd = ASSERT_OK_ERRNO(openat(tfd, "cwd_test", O_CREAT|O_CLOEXEC, 0600)); + fd = safe_close(fd); + + ASSERT_OK(chaseat(XAT_FDROOT, AT_FDCWD, "cwd_test", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, tfd, "cwd_test", AT_EMPTY_PATH)); + fd = safe_close(fd); + result = mfree(result); + + ASSERT_OK_ERRNO(chdir(cwd_saved)); + + /* If the file descriptor does not point to the root directory, the result will be relative + * unless the result is outside of the specified file descriptor. */ + + ASSERT_OK(chaseat(XAT_FDROOT, tfd, "abc", 0, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); - assert_se(chaseat(tfd, "abc", CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL) == -ENOENT); - assert_se(chaseat(tfd, "/abc", CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL) == -ENOENT); + ASSERT_ERROR(chaseat(tfd, tfd, "abc", 0, NULL, NULL), ENOENT); + ASSERT_ERROR(chaseat(tfd, tfd, "/abc", 0, NULL, NULL), ENOENT); - ASSERT_OK(chaseat(tfd, "abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, "abc", CHASE_NONEXISTENT, &result, NULL)); ASSERT_STREQ(result, "usr"); result = mfree(result); - ASSERT_OK(chaseat(tfd, "/abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, "/abc", CHASE_NONEXISTENT, &result, NULL)); ASSERT_STREQ(result, "usr"); result = mfree(result); /* Test that absolute path or not are the same when resolving relative to a directory file * descriptor and that we always get a relative path back. */ - ASSERT_OK(fd = openat(tfd, "def", O_CREAT|O_CLOEXEC, 0700)); + fd = ASSERT_OK_ERRNO(openat(tfd, "def", O_CREAT|O_CLOEXEC, 0700)); fd = safe_close(fd); - ASSERT_OK(symlinkat("/def", tfd, "qed")); - ASSERT_OK(chaseat(tfd, "qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_OK_ERRNO(symlinkat("/def", tfd, "qed")); + ASSERT_OK(chaseat(tfd, tfd, "qed", 0, &result, NULL)); ASSERT_STREQ(result, "def"); result = mfree(result); - ASSERT_OK(chaseat(tfd, "/qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, "/qed", 0, &result, NULL)); ASSERT_STREQ(result, "def"); result = mfree(result); - /* Valid directory file descriptor without CHASE_AT_RESOLVE_IN_ROOT should resolve symlinks against + /* Valid directory file descriptor should resolve symlinks against * host's root. */ - assert_se(chaseat(tfd, "/qed", 0, NULL, NULL) == -ENOENT); + ASSERT_ERROR(chaseat(XAT_FDROOT, tfd, "/qed", 0, NULL, NULL), ENOENT); /* Test CHASE_PARENT */ - ASSERT_OK(fd = open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)); - ASSERT_OK(symlinkat("/def", fd, "parent")); + fd = ASSERT_OK(open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)); + ASSERT_OK_ERRNO(symlinkat("/def", fd, "parent")); fd = safe_close(fd); /* Make sure that when we chase a symlink parent directory, that we chase the parent directory of the * symlink target and not the symlink itself. But if we add CHASE_NOFOLLOW, we get the parent * directory of the symlink itself. */ - ASSERT_OK(chaseat(tfd, "chase/parent", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd)); - ASSERT_OK(faccessat(fd, "def", F_OK, 0)); + ASSERT_OK(chaseat(tfd, tfd, "chase/parent", CHASE_PARENT, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(fd, "def", F_OK, 0)); ASSERT_STREQ(result, "def"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd)); - ASSERT_OK(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_OK(chaseat(tfd, tfd, "chase/parent", CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW)); ASSERT_STREQ(result, "chase/parent"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd)); - ASSERT_OK(faccessat(fd, "chase", F_OK, 0)); + ASSERT_OK(chaseat(tfd, tfd, "chase", CHASE_PARENT, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(fd, "chase", F_OK, 0)); ASSERT_STREQ(result, "chase"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, "/", CHASE_PARENT, &result, NULL)); ASSERT_STREQ(result, "."); result = mfree(result); - assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, ".", CHASE_PARENT, &result, NULL)); ASSERT_STREQ(result, "."); result = mfree(result); /* Test CHASE_MKDIR_0755 */ - ASSERT_OK(chaseat(tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL)); - ASSERT_OK(faccessat(tfd, "m/k/d/i", F_OK, 0)); - assert_se(RET_NERRNO(faccessat(tfd, "m/k/d/i/r", F_OK, 0)) == -ENOENT); + ASSERT_OK(chaseat(XAT_FDROOT, tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL)); + ASSERT_OK_ERRNO(faccessat(tfd, "m/k/d/i", F_OK, 0)); + ASSERT_ERROR(RET_NERRNO(faccessat(tfd, "m/k/d/i/r", F_OK, 0)), ENOENT); ASSERT_STREQ(result, "m/k/d/i/r"); result = mfree(result); - ASSERT_OK(chaseat(tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL)); - ASSERT_OK(faccessat(tfd, "m", F_OK, 0)); - assert_se(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)) == -ENOENT); + ASSERT_OK(chaseat(XAT_FDROOT, tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL)); + ASSERT_OK_ERRNO(faccessat(tfd, "m", F_OK, 0)); + ASSERT_ERROR(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)), ENOENT); ASSERT_STREQ(result, "q"); result = mfree(result); - assert_se(chaseat(tfd, "i/../p", CHASE_MKDIR_0755|CHASE_NONEXISTENT, NULL, NULL) == -ENOENT); + ASSERT_ERROR(chaseat(XAT_FDROOT, tfd, "i/../p", CHASE_MKDIR_0755|CHASE_NONEXISTENT, NULL, NULL), ENOENT); + + /* Test CHASE_MKDIR_0755|CHASE_PARENT — creates intermediate dirs but not the final component */ + + ASSERT_OK(chaseat(XAT_FDROOT, tfd, "mkp/a/r/e/n/t/file", CHASE_MKDIR_0755|CHASE_PARENT, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(tfd, "mkp/a/r/e/n/t", F_OK, 0)); + ASSERT_ERROR(RET_NERRNO(faccessat(tfd, "mkp/a/r/e/n/t/file", F_OK, 0)), ENOENT); + ASSERT_OK(fd_verify_directory(fd)); + fd = safe_close(fd); + ASSERT_STREQ(result, "mkp/a/r/e/n/t/file"); + result = mfree(result); /* Test CHASE_EXTRACT_FILENAME */ - ASSERT_OK(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd)); - ASSERT_OK(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_OK(chaseat(tfd, tfd, "chase/parent", CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW)); ASSERT_STREQ(result, "parent"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, &fd)); - ASSERT_OK(faccessat(fd, result, F_OK, 0)); + ASSERT_OK(chaseat(tfd, tfd, "chase", CHASE_PARENT|CHASE_EXTRACT_FILENAME, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(fd, result, F_OK, 0)); ASSERT_STREQ(result, "chase"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, "/", CHASE_PARENT|CHASE_EXTRACT_FILENAME, &result, NULL)); ASSERT_STREQ(result, "."); result = mfree(result); - ASSERT_OK(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, ".", CHASE_PARENT|CHASE_EXTRACT_FILENAME, &result, NULL)); ASSERT_STREQ(result, "."); result = mfree(result); - ASSERT_OK(chaseat(tfd, NULL, CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME, &result, NULL)); ASSERT_STREQ(result, "."); result = mfree(result); /* Test chase_and_openat() */ - fd = chase_and_openat(tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL); - ASSERT_OK(fd); + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL)); ASSERT_OK(fd_verify_regular(fd)); fd = safe_close(fd); - fd = chase_and_openat(tfd, "o/p/e/n/d/i/r", CHASE_MKDIR_0755, O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, NULL); - ASSERT_OK(fd); + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/d/i/r", CHASE_MKDIR_0755, O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, NULL)); ASSERT_OK(fd_verify_directory(fd)); fd = safe_close(fd); - fd = chase_and_openat(tfd, NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result); - ASSERT_OK(fd); + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); ASSERT_STREQ(result, "."); fd = safe_close(fd); result = mfree(result); + /* Test chase_and_openat() with CHASE_MKDIR_0755|CHASE_PARENT — opens parent dir */ + + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "mkopen/p/a/r/file.txt", CHASE_MKDIR_0755|CHASE_PARENT, O_RDONLY|O_CLOEXEC, NULL)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK(faccessat(tfd, "mkopen/p/a/r", F_OK, 0)); + ASSERT_ERROR(RET_NERRNO(faccessat(tfd, "mkopen/p/a/r/file.txt", F_OK, 0)), ENOENT); + fd = safe_close(fd); + + /* Test chase_and_openat() with CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY + O_CREAT — creates and opens target dir */ + + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "mkopen/d/i/r/target", CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, O_CREAT|O_RDONLY|O_CLOEXEC, NULL)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK(faccessat(tfd, "mkopen/d/i/r/target", F_OK, 0)); + fd = safe_close(fd); + + /* Test chase_and_openat() with various CHASE_PARENT / CHASE_EXTRACT_FILENAME combinations */ + + /* No CHASE_PARENT, no CHASE_EXTRACT_FILENAME, with ret_path — opens the target, returns full path. */ + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/d/i/r", 0, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_STREQ(result, "o/p/e/n/d/i/r"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT with ret_path — opens parent dir, returns full path including final component. */ + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/d/i/r", CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "r", F_OK, 0)); + ASSERT_STREQ(result, "o/p/e/n/d/i/r"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_EXTRACT_FILENAME with a real multi-component path — opens parent dir, + * returns just the filename. */ + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/d/i/r", CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "r", F_OK, 0)); + ASSERT_STREQ(result, "r"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_EXTRACT_FILENAME only (without CHASE_PARENT) — opens the target itself, returns just + * the filename. */ + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/d/i/r", CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_STREQ(result, "r"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT through a symlink — the symlink is followed, parent of the target is opened. + * "chase/parent" where parent→/def: resolves to /def, parent is the root dir. */ + fd = ASSERT_OK(chase_and_openat(tfd, tfd, "chase/parent", CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "def", F_OK, 0)); + ASSERT_STREQ(result, "def"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_EXTRACT_FILENAME through a symlink — parent of the target is opened, + * returns just the target filename. */ + fd = ASSERT_OK(chase_and_openat(tfd, tfd, "chase/parent", CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "def", F_OK, 0)); + ASSERT_STREQ(result, "def"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_NOFOLLOW through a symlink — the symlink is NOT followed, parent of the + * symlink is opened. */ + fd = ASSERT_OK(chase_and_openat(tfd, tfd, "chase/parent", CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_STREQ(result, "chase/parent"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME through a symlink — parent of the symlink + * is opened, returns just the symlink name. */ + fd = ASSERT_OK(chase_and_openat(tfd, tfd, "chase/parent", CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_STREQ(result, "parent"); + fd = safe_close(fd); + result = mfree(result); + /* Test chase_and_openatdir() */ - ASSERT_OK(chase_and_opendirat(tfd, "o/p/e/n/d/i", 0, &result, &dir)); + ASSERT_OK(chase_and_opendirat(XAT_FDROOT, tfd, "o/p/e/n/d/i", 0, &result, &dir)); FOREACH_DIRENT(de, dir, assert_not_reached()) ASSERT_STREQ(de->d_name, "r"); ASSERT_STREQ(result, "o/p/e/n/d/i"); @@ -674,55 +854,163 @@ TEST(chaseat) { /* Test chase_and_statat() */ - ASSERT_OK(chase_and_statat(tfd, "o/p", 0, &result, &st)); + ASSERT_OK(chase_and_statat(XAT_FDROOT, tfd, "o/p", 0, &result, &st)); ASSERT_OK(stat_verify_directory(&st)); ASSERT_STREQ(result, "o/p"); result = mfree(result); /* Test chase_and_accessat() */ - ASSERT_OK(chase_and_accessat(tfd, "o/p/e", 0, F_OK, &result)); + ASSERT_OK(chase_and_accessat(XAT_FDROOT, tfd, "o/p/e", 0, F_OK, &result)); ASSERT_STREQ(result, "o/p/e"); result = mfree(result); /* Test chase_and_fopenat_unlocked() */ - ASSERT_OK(chase_and_fopenat_unlocked(tfd, "o/p/e/n/f/i/l/e", 0, "re", &result, &f)); - assert_se(fread(&(char[1]) {}, 1, 1, f) == 0); - assert_se(feof(f)); + ASSERT_OK(chase_and_fopenat_unlocked(XAT_FDROOT, tfd, "o/p/e/n/f/i/l/e", 0, "re", &result, &f)); + ASSERT_EQ(fread(&(char[1]) {}, 1, 1, f), 0u); + ASSERT_TRUE(feof(f)); f = safe_fclose(f); ASSERT_STREQ(result, "o/p/e/n/f/i/l/e"); result = mfree(result); /* Test chase_and_unlinkat() */ - ASSERT_OK(chase_and_unlinkat(tfd, "o/p/e/n/f/i/l/e", 0, 0, &result)); + ASSERT_OK(chase_and_unlinkat(XAT_FDROOT, tfd, "o/p/e/n/f/i/l/e", 0, 0, &result)); ASSERT_STREQ(result, "o/p/e/n/f/i/l/e"); result = mfree(result); /* Test chase_and_open_parent_at() */ - ASSERT_OK(fd = chase_and_open_parent_at(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_NOFOLLOW, &result)); - ASSERT_OK(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW)); + fd = ASSERT_OK(chase_and_open_parent_at(XAT_FDROOT, tfd, "chase/parent", CHASE_NOFOLLOW, &result)); + ASSERT_OK_ERRNO(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW)); ASSERT_STREQ(result, "parent"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(fd = chase_and_open_parent_at(tfd, "chase", CHASE_AT_RESOLVE_IN_ROOT, &result)); - ASSERT_OK(faccessat(fd, result, F_OK, 0)); + fd = ASSERT_OK(chase_and_open_parent_at(XAT_FDROOT, tfd, "chase", 0, &result)); + ASSERT_OK_ERRNO(faccessat(fd, result, F_OK, 0)); ASSERT_STREQ(result, "chase"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(fd = chase_and_open_parent_at(tfd, "/", CHASE_AT_RESOLVE_IN_ROOT, &result)); + fd = ASSERT_OK(chase_and_open_parent_at(XAT_FDROOT, tfd, "/", 0, &result)); + ASSERT_STREQ(result, "."); + fd = safe_close(fd); + result = mfree(result); + + fd = ASSERT_OK(chase_and_open_parent_at(XAT_FDROOT, tfd, ".", 0, &result)); ASSERT_STREQ(result, "."); fd = safe_close(fd); result = mfree(result); +} - ASSERT_OK(fd = chase_and_open_parent_at(tfd, ".", CHASE_AT_RESOLVE_IN_ROOT, &result)); +TEST(chaseat_separate_root_and_dir) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int root_fd = -EBADF, sub_fd = -EBADF, fd = -EBADF; + _cleanup_free_ char *result = NULL; + + /* Exercise chaseat() with root_fd != dir_fd. The root marks the chroot boundary (symlinks may + * not escape it, absolute symlinks resolve to it), while dir_fd is the starting directory for + * relative paths. */ + + root_fd = ASSERT_OK(mkdtemp_open(NULL, 0, &t)); + + /* Create a file at the root and a subdirectory containing another file. */ + ASSERT_OK_ERRNO(mkdirat(root_fd, "sub", 0755)); + sub_fd = ASSERT_OK_ERRNO(openat(root_fd, "sub", O_CLOEXEC|O_DIRECTORY|O_PATH)); + + fd = ASSERT_OK_ERRNO(openat(root_fd, "outside", O_CREAT|O_CLOEXEC, 0600)); + fd = safe_close(fd); + + fd = ASSERT_OK_ERRNO(openat(sub_fd, "inside", O_CREAT|O_CLOEXEC, 0600)); + fd = safe_close(fd); + + /* Relative lookup from sub_fd under root_fd finds sub's own files. */ + ASSERT_OK(chaseat(root_fd, sub_fd, "inside", 0, &result, NULL)); + ASSERT_STREQ(result, "inside"); + result = mfree(result); + + /* Absolute path with dir_fd=sub_fd and root_fd set: path is relative to root_fd so "/inside" finds + * nothing. */ + ASSERT_ERROR(chaseat(root_fd, sub_fd, "/inside", 0, &result, NULL), ENOENT); + ASSERT_OK_ZERO(chaseat(root_fd, sub_fd, "/inside", CHASE_NONEXISTENT, &result, NULL)); + result = mfree(result); + + /* "../outside" from sub_fd goes up one level (within root), finds root's file. */ + ASSERT_OK(chaseat(root_fd, sub_fd, "../outside", 0, &result, NULL)); + ASSERT_STREQ(result, "../outside"); + result = mfree(result); + + /* "../../../outside" cannot escape above root_fd — clamped. Still resolves to root's file. */ + ASSERT_OK(chaseat(root_fd, sub_fd, "../../../outside", 0, &result, NULL)); + ASSERT_STREQ(result, "../outside"); + result = mfree(result); + + /* Absolute symlink inside sub pointing at "/outside" — with root_fd set, /outside resolves to + * root_fd/outside, not the host's /outside. */ + ASSERT_OK_ERRNO(symlinkat("/outside", sub_fd, "escape_abs")); + ASSERT_OK(chaseat(root_fd, sub_fd, "escape_abs", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, root_fd, "outside", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "/outside"); + result = mfree(result); + fd = safe_close(fd); + + /* Relative symlink trying to escape via many ".." — also clamped to root. */ + ASSERT_OK_ERRNO(symlinkat("../../../../../outside", sub_fd, "escape_rel")); + ASSERT_OK(chaseat(root_fd, sub_fd, "escape_rel", 0, &result, NULL)); + ASSERT_STREQ(result, "../outside"); + result = mfree(result); + + /* Symlink pointing to an absolute host path that does NOT exist under our root must fail, not + * leak to the host. /etc almost always exists on the host; under our tmp root it doesn't. */ + ASSERT_OK_ERRNO(symlinkat("/etc", sub_fd, "escape_host")); + ASSERT_ERROR(chaseat(root_fd, sub_fd, "escape_host/hosts", 0, NULL, NULL), ENOENT); + + /* Chasing just ".." from root_fd itself stays at root. */ + ASSERT_OK(chaseat(root_fd, root_fd, "..", 0, &result, NULL)); ASSERT_STREQ(result, "."); + result = mfree(result); + + /* (real-fd, XAT_FDROOT, relative): XAT_FDROOT as dir_fd redirects to root_fd, so relative + * paths start at root_fd. Result is relative because root_fd is a non-host-root fd and + * dir_fd (after redirection) equals root_fd. */ + ASSERT_OK(chaseat(root_fd, XAT_FDROOT, "sub/inside", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, sub_fd, "inside", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "sub/inside"); + fd = safe_close(fd); + result = mfree(result); + + /* (real-fd, XAT_FDROOT, absolute): same as relative — absolute paths also resolve from + * root_fd. Leading slash is stripped. */ + ASSERT_OK(chaseat(root_fd, XAT_FDROOT, "/sub/inside", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, sub_fd, "inside", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "sub/inside"); + fd = safe_close(fd); + result = mfree(result); + + /* (real-fd, XAT_FDROOT, absolute) resolving to root: "/outside" lives directly under + * root_fd. */ + ASSERT_OK(chaseat(root_fd, XAT_FDROOT, "/outside", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, root_fd, "outside", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "outside"); fd = safe_close(fd); result = mfree(result); + + /* (real-fd, XAT_FDROOT) with an absolute symlink: the symlink target "/outside" resolves + * relative to root_fd, not the host root. Since dir_fd == root_fd (XAT_FDROOT was redirected), + * the result stays relative. */ + ASSERT_OK(chaseat(root_fd, XAT_FDROOT, "sub/escape_abs", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, root_fd, "outside", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "outside"); + fd = safe_close(fd); + result = mfree(result); + + /* (real-fd, XAT_FDROOT) with non-existent path. */ + ASSERT_OK_ZERO(chaseat(root_fd, XAT_FDROOT, "/nonexistent", CHASE_NONEXISTENT, &result, NULL)); + ASSERT_STREQ(result, "nonexistent"); + result = mfree(result); + ASSERT_ERROR(chaseat(root_fd, XAT_FDROOT, "/nonexistent", 0, NULL, NULL), ENOENT); } TEST(chaseat_prefix_root) { @@ -746,7 +1034,7 @@ TEST(chaseat_prefix_root) { ret = mfree(ret); ASSERT_OK(chaseat_prefix_root("hoge", "a/b//./c///", &ret)); - assert_se(expected = path_join(cwd, "a/b/c/hoge")); + expected = ASSERT_NOT_NULL(path_join(cwd, "a/b/c/hoge")); ASSERT_STREQ(ret, expected); ret = mfree(ret); @@ -757,8 +1045,8 @@ TEST(chaseat_prefix_root) { ret = mfree(ret); - assert_se(chaseat_prefix_root("./hoge/aaa/../././b", "a/b//./c///", &ret) >= 0); - assert_se(expected = path_join(cwd, "a/b/c/hoge/aaa/../././b")); + ASSERT_OK(chaseat_prefix_root("./hoge/aaa/../././b", "a/b//./c///", &ret)); + expected = ASSERT_NOT_NULL(path_join(cwd, "a/b/c/hoge/aaa/../././b")); ASSERT_STREQ(ret, expected); } @@ -767,9 +1055,9 @@ TEST(trailing_dot_dot) { _cleanup_close_ int fd = -EBADF; ASSERT_OK(chase("/usr/..", NULL, CHASE_PARENT, &path, &fd)); - assert_se(path_equal(path, "/")); + ASSERT_PATH_EQ(path, "/"); ASSERT_OK(fd_get_path(fd, &fdpath)); - assert_se(path_equal(fdpath, "/")); + ASSERT_PATH_EQ(fdpath, "/"); path = mfree(path); fdpath = mfree(fdpath); @@ -784,9 +1072,9 @@ TEST(trailing_dot_dot) { _cleanup_free_ char *expected1 = ASSERT_PTR(path_join(t, "a/b/c")); _cleanup_free_ char *expected2 = ASSERT_PTR(path_join(t, "a/b")); - assert_se(path_equal(path, expected1)); + ASSERT_PATH_EQ(path, expected1); ASSERT_OK(fd_get_path(fd, &fdpath)); - assert_se(path_equal(fdpath, expected2)); + ASSERT_PATH_EQ(fdpath, expected2); } TEST(use_chase_as_mkdir_p) { diff --git a/src/test/test-chid.c b/src/test/test-chid.c index 564b09c183e7e..86b748016aba0 100644 --- a/src/test/test-chid.c +++ b/src/test/test-chid.c @@ -3,7 +3,7 @@ #include "chid-fundamental.h" #include "tests.h" -const char16_t *const test_fields[_CHID_SMBIOS_FIELDS_MAX] = { +static const char16_t *const test_fields[_CHID_SMBIOS_FIELDS_MAX] = { [CHID_SMBIOS_MANUFACTURER] = u"Micro-Star International Co., Ltd.", [CHID_SMBIOS_PRODUCT_NAME] = u"MS-7D70", [CHID_SMBIOS_PRODUCT_SKU] = u"To be filled by O.E.M.", diff --git a/src/test/test-compress-benchmark.c b/src/test/test-compress-benchmark.c index da68c6cb7b8cd..8b00a00a06fcc 100644 --- a/src/test/test-compress-benchmark.c +++ b/src/test/test-compress-benchmark.c @@ -1,27 +1,36 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" +#include "argv-util.h" #include "compress.h" -#include "nulstr-util.h" #include "parse-util.h" #include "process-util.h" #include "random-util.h" +#include "string-table.h" #include "tests.h" #include "time-util.h" -typedef int (compress_t)(const void *src, uint64_t src_size, void *dst, - size_t dst_alloc_size, size_t *dst_size, int level); -typedef int (decompress_t)(const void *src, uint64_t src_size, - void **dst, size_t* dst_size, size_t dst_max); - -#if HAVE_COMPRESSION - static usec_t arg_duration; static size_t arg_start; #define MAX_SIZE (1024*1024LU) #define PRIME 1048571 /* A prime close enough to one megabyte that mod 4 == 3 */ +typedef enum BenchmarkDataType { + BENCHMARK_DATA_ZEROS, + BENCHMARK_DATA_SIMPLE, + BENCHMARK_DATA_RANDOM, + _BENCHMARK_DATA_TYPE_MAX, +} BenchmarkDataType; + +static const char* const benchmark_data_type_table[_BENCHMARK_DATA_TYPE_MAX] = { + [BENCHMARK_DATA_ZEROS] = "zeros", + [BENCHMARK_DATA_SIMPLE] = "simple", + [BENCHMARK_DATA_RANDOM] = "random", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(benchmark_data_type, BenchmarkDataType); + static size_t _permute(size_t x) { size_t residue; @@ -39,19 +48,24 @@ static size_t permute(size_t x) { return _permute((_permute(x) + arg_start) % MAX_SIZE ^ 0xFF345); } -static char* make_buf(size_t count, const char *type) { +static char* make_buf(size_t count, BenchmarkDataType type) { char *buf; - size_t i; buf = malloc(count); - assert_se(buf); + ASSERT_NOT_NULL(buf); - if (streq(type, "zeros")) + switch (type) { + + case BENCHMARK_DATA_ZEROS: memzero(buf, count); - else if (streq(type, "simple")) - for (i = 0; i < count; i++) + break; + + case BENCHMARK_DATA_SIMPLE: + for (size_t i = 0; i < count; i++) buf[i] = 'a' + i % ('z' - 'a' + 1); - else if (streq(type, "random")) { + break; + + case BENCHMARK_DATA_RANDOM: { size_t step = count / 10; random_bytes(buf, step); @@ -64,110 +78,103 @@ static char* make_buf(size_t count, const char *type) { memzero(buf + 7*step, step); random_bytes(buf + 8*step, step); memzero(buf + 9*step, step); - } else + break; + } + + default: assert_not_reached(); + } return buf; } -static void test_compress_decompress(const char* label, const char* type, - compress_t compress, decompress_t decompress) { - usec_t n, n2 = 0; - float dt; +TEST(benchmark) { + for (BenchmarkDataType dt = 0; dt < _BENCHMARK_DATA_TYPE_MAX; dt++) + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; - _cleanup_free_ char *text = NULL, *buf = NULL; - _cleanup_free_ void *buf2 = NULL; - size_t skipped = 0, compressed = 0, total = 0; + const char *label = compression_to_string(c); + const char *type = benchmark_data_type_to_string(dt); + usec_t n, n2 = 0; - text = make_buf(MAX_SIZE, type); - buf = calloc(MAX_SIZE + 1, 1); - assert_se(text && buf); + _cleanup_free_ char *text = NULL, *buf = NULL; + _cleanup_free_ void *buf2 = NULL; + size_t skipped = 0, compressed = 0, total = 0; - n = now(CLOCK_MONOTONIC); + text = make_buf(MAX_SIZE, dt); + buf = calloc(MAX_SIZE + 1, 1); + ASSERT_NOT_NULL(text); + ASSERT_NOT_NULL(buf); - for (size_t i = 0; i <= MAX_SIZE; i++) { - size_t j = 0, k = 0, size; - int r; + n = now(CLOCK_MONOTONIC); - size = permute(i); - if (size == 0) - continue; + for (size_t i = 0; i <= MAX_SIZE; i++) { + size_t j = 0, k = 0, size; + int r; - log_debug("%s %zu %zu", type, i, size); + size = permute(i); + if (size == 0) + continue; - memzero(buf, MIN(size + 1000, MAX_SIZE)); + log_debug("%s %zu %zu", type, i, size); - r = compress(text, size, buf, size, &j, /* level= */ -1); - /* assume compression must be successful except for small or random inputs */ - assert_se(r >= 0 || (size < 2048 && r == -ENOBUFS) || streq(type, "random")); + memzero(buf, MIN(size + 1000, MAX_SIZE)); - /* check for overwrites */ - assert_se(buf[size] == 0); - if (r < 0) { - skipped += size; - continue; - } + r = compress_blob(c, text, size, buf, size, &j, /* level= */ -1); + /* assume compression must be successful except for small or random inputs */ + ASSERT_TRUE(r >= 0 || (size < 2048 && r == -ENOBUFS) || dt == BENCHMARK_DATA_RANDOM); - assert_se(j > 0); - if (j >= size) - log_error("%s \"compressed\" %zu -> %zu", label, size, j); + /* check for overwrites */ + ASSERT_EQ(buf[size], 0); + if (r < 0) { + skipped += size; + continue; + } - r = decompress(buf, j, &buf2, &k, 0); - assert_se(r == 0); - assert_se(k == size); + ASSERT_TRUE(j > 0); + if (j >= size) + log_error("%s \"compressed\" %zu -> %zu", label, size, j); - assert_se(memcmp(text, buf2, size) == 0); + ASSERT_OK_ZERO(decompress_blob(c, buf, j, &buf2, &k, 0)); + ASSERT_EQ(k, size); + ASSERT_EQ(memcmp(text, buf2, size), 0); - total += size; - compressed += j; + total += size; + compressed += j; - n2 = now(CLOCK_MONOTONIC); - if (n2 - n > arg_duration) - break; - } + n2 = now(CLOCK_MONOTONIC); + if (n2 - n > arg_duration) + break; + } - dt = (n2-n) / 1e6; + float elapsed = (n2-n) / 1e6; - log_info("%s/%s: compressed & decompressed %zu bytes in %.2fs (%.2fMiB/s), " - "mean compression %.2f%%, skipped %zu bytes", - label, type, total, dt, - total / 1024. / 1024 / dt, - 100 - compressed * 100. / total, - skipped); + log_info("%s/%s: compressed & decompressed %zu bytes in %.2fs (%.2fMiB/s), " + "mean compression %.2f%%, skipped %zu bytes", + label, type, total, elapsed, + total / 1024. / 1024 / elapsed, + 100 - compressed * 100. / total, + skipped); + } } -#endif - -int main(int argc, char *argv[]) { -#if HAVE_COMPRESSION - test_setup_logging(LOG_INFO); - if (argc >= 2) { +static int intro(void) { + if (saved_argc >= 2) { unsigned x; - assert_se(safe_atou(argv[1], &x) >= 0); + ASSERT_OK(safe_atou(saved_argv[1], &x)); arg_duration = x * USEC_PER_SEC; } else arg_duration = slow_tests_enabled() ? 2 * USEC_PER_SEC : USEC_PER_SEC / 50; - if (argc == 3) - (void) safe_atozu(argv[2], &arg_start); + if (saved_argc == 3) + (void) safe_atozu(saved_argv[2], &arg_start); else arg_start = getpid_cached(); - NULSTR_FOREACH(i, "zeros\0simple\0random\0") { -#if HAVE_XZ - test_compress_decompress("XZ", i, compress_blob_xz, decompress_blob_xz); -#endif -#if HAVE_LZ4 - test_compress_decompress("LZ4", i, compress_blob_lz4, decompress_blob_lz4); -#endif -#if HAVE_ZSTD - test_compress_decompress("ZSTD", i, compress_blob_zstd, decompress_blob_zstd); -#endif - } return 0; -#else - return log_tests_skipped("No compression feature is enabled"); -#endif } + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-compress.c b/src/test/test-compress.c index 7b12e88adf661..0c90443a8738b 100644 --- a/src/test/test-compress.c +++ b/src/test/test-compress.c @@ -4,377 +4,465 @@ #include #include -#if HAVE_LZ4 -#include -#endif - #include "alloc-util.h" +#include "argv-util.h" #include "compress.h" -#include "dlfcn-util.h" #include "fd-util.h" +#include "io-util.h" #include "path-util.h" #include "random-util.h" #include "tests.h" #include "tmpfile-util.h" -#if HAVE_XZ -# define XZ_OK 0 -#else -# define XZ_OK -EPROTONOSUPPORT -#endif +#define HUGE_SIZE (4096*1024) -#if HAVE_LZ4 -# define LZ4_OK 0 -#else -# define LZ4_OK -EPROTONOSUPPORT -#endif +static const char text[] = + "text\0foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF" + "foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF"; +static char data[512] = "random\0"; +static char *huge = NULL; +static const char *srcfile; + +static const char* cat_for_compression(Compression c) { + switch (c) { + case COMPRESSION_XZ: return "xzcat"; + case COMPRESSION_LZ4: return "lz4cat"; + case COMPRESSION_ZSTD: return "zstdcat"; + case COMPRESSION_GZIP: return "zcat"; + case COMPRESSION_BZIP2: return "bzcat"; + default: return NULL; + } +} -#define HUGE_SIZE (4096*1024) +TEST(compress_decompress_blob) { + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; + + const char *label = compression_to_string(c); + + for (size_t t = 0; t < 2; t++) { + const char *input = t == 0 ? text : data; + size_t input_len = t == 0 ? sizeof(text) : sizeof(data); + bool may_fail = t == 1; + + char compressed[512]; + size_t csize; + _cleanup_free_ char *decompressed = NULL; + int r; + + log_info("/* testing %s %s blob compression/decompression */", label, input); + + r = compress_blob(c, input, input_len, compressed, sizeof(compressed), &csize, -1); + if (r == -ENOBUFS) { + log_info_errno(r, "compression failed: %m"); + ASSERT_TRUE(may_fail); + } else { + ASSERT_OK(r); + ASSERT_OK_ZERO(decompress_blob(c, compressed, csize, (void **) &decompressed, &csize, 0)); + ASSERT_NOT_NULL(decompressed); + ASSERT_EQ(memcmp(decompressed, input, input_len), 0); + } + + ASSERT_FAIL(decompress_blob(c, "garbage", 7, (void **) &decompressed, &csize, 0)); + } + } +} -typedef int (compress_blob_t)(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level); -typedef int (decompress_blob_t)(const void *src, uint64_t src_size, - void **dst, - size_t* dst_size, size_t dst_max); -typedef int (decompress_sw_t)(const void *src, uint64_t src_size, - void **buffer, - const void *prefix, size_t prefix_len, - uint8_t extra); - -typedef int (compress_stream_t)(int fdf, int fdt, uint64_t max_bytes, uint64_t *uncompressed_size); -typedef int (decompress_stream_t)(int fdf, int fdt, uint64_t max_size); - -#if HAVE_COMPRESSION -_unused_ static void test_compress_decompress( - const char *compression, - compress_blob_t compress, - decompress_blob_t decompress, - const char *data, - size_t data_len, - bool may_fail) { - - char compressed[512]; - size_t csize; - _cleanup_free_ char *decompressed = NULL; - int r; - - log_info("/* testing %s %s blob compression/decompression */", - compression, data); - - r = compress(data, data_len, compressed, sizeof(compressed), &csize, /* level= */ -1); - if (r == -ENOBUFS) { - log_info_errno(r, "compression failed: %m"); - assert_se(may_fail); - } else { - assert_se(r >= 0); - r = decompress(compressed, csize, - (void **) &decompressed, &csize, 0); - assert_se(r == 0); - assert_se(decompressed); - assert_se(memcmp(decompressed, data, data_len) == 0); +TEST(decompress_startswith) { + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; + + const char *label = compression_to_string(c); + + struct { const char *buf; size_t len; bool may_fail; } inputs[] = { + { text, sizeof(text), false }, + { data, sizeof(data), true }, + { huge, HUGE_SIZE, true }, + }; + + for (size_t t = 0; t < ELEMENTSOF(inputs); t++) { + char *compressed; + _cleanup_free_ char *compressed1 = NULL, *compressed2 = NULL, *decompressed = NULL; + size_t csize, len; + int r; + + log_info("/* testing decompress_startswith with %s on %.20s */", label, inputs[t].buf); + + compressed = compressed1 = malloc(512); + ASSERT_NOT_NULL(compressed1); + r = compress_blob(c, inputs[t].buf, inputs[t].len, compressed, 512, &csize, -1); + if (r == -ENOBUFS) { + log_info_errno(r, "compression failed: %m"); + ASSERT_TRUE(inputs[t].may_fail); + + compressed = compressed2 = malloc(20000); + ASSERT_NOT_NULL(compressed2); + r = compress_blob(c, inputs[t].buf, inputs[t].len, compressed, 20000, &csize, -1); + } + if (r == -ENOBUFS) { + log_info_errno(r, "compression failed again: %m"); + ASSERT_TRUE(inputs[t].may_fail); + continue; + } + ASSERT_OK(r); + + len = strlen(inputs[t].buf); + + ASSERT_OK_POSITIVE(decompress_startswith(c, compressed, csize, (void **) &decompressed, inputs[t].buf, len, '\0')); + ASSERT_OK_ZERO(decompress_startswith(c, compressed, csize, (void **) &decompressed, inputs[t].buf, len, 'w')); + ASSERT_OK_POSITIVE(decompress_startswith(c, compressed, csize, (void **) &decompressed, inputs[t].buf, len - 1, inputs[t].buf[len-1])); + ASSERT_OK_ZERO(decompress_startswith(c, compressed, csize, (void **) &decompressed, inputs[t].buf, len - 1, 'w')); + } } +} - r = decompress("garbage", 7, - (void **) &decompressed, &csize, 0); - assert_se(r < 0); +TEST(decompress_startswith_large) { + /* Test decompress_startswith with large data to exercise the buffer growth path. */ - /* make sure to have the minimal lz4 compressed size */ - r = decompress("00000000\1g", 9, - (void **) &decompressed, &csize, 0); - assert_se(r < 0); + _cleanup_free_ char *large = NULL; + size_t large_size = 8 * 1024; - r = decompress("\100000000g", 9, - (void **) &decompressed, &csize, 0); - assert_se(r < 0); + ASSERT_NOT_NULL(large = malloc(large_size)); + for (size_t i = 0; i < large_size; i++) + large[i] = 'A' + (i % 26); - explicit_bzero_safe(decompressed, MALLOC_SIZEOF_SAFE(decompressed)); -} + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; + + _cleanup_free_ char *compressed = NULL; + size_t csize; -_unused_ static void test_decompress_startswith(const char *compression, - compress_blob_t compress, - decompress_sw_t decompress_sw, - const char *data, - size_t data_len, - bool may_fail) { - - char *compressed; - _cleanup_free_ char *compressed1 = NULL, *compressed2 = NULL, *decompressed = NULL; - size_t csize, len; - int r; - - log_info("/* testing decompress_startswith with %s on %.20s text */", - compression, data); - -#define BUFSIZE_1 512 -#define BUFSIZE_2 20000 - - compressed = compressed1 = malloc(BUFSIZE_1); - assert_se(compressed1); - r = compress(data, data_len, compressed, BUFSIZE_1, &csize, /* level= */ -1); - if (r == -ENOBUFS) { - log_info_errno(r, "compression failed: %m"); - assert_se(may_fail); - - compressed = compressed2 = malloc(BUFSIZE_2); - assert_se(compressed2); - r = compress(data, data_len, compressed, BUFSIZE_2, &csize, /* level= */ -1); + log_info("/* decompress_startswith_large with %s */", compression_to_string(c)); + + ASSERT_NOT_NULL(compressed = malloc(large_size)); + int r = compress_blob(c, large, large_size, compressed, large_size, &csize, -1); + if (r == -ENOBUFS) { + log_info_errno(r, "compression failed: %m"); + continue; + } + ASSERT_OK(r); + + _cleanup_free_ void *buf = NULL; + + ASSERT_OK_POSITIVE(decompress_startswith(c, compressed, csize, &buf, large, 1, large[1])); + ASSERT_OK_ZERO(decompress_startswith(c, compressed, csize, &buf, large, 1, 0xff)); + ASSERT_OK_POSITIVE(decompress_startswith(c, compressed, csize, &buf, large, 512, large[512])); + ASSERT_OK_ZERO(decompress_startswith(c, compressed, csize, &buf, large, 512, 0xff)); + ASSERT_OK_POSITIVE(decompress_startswith(c, compressed, csize, &buf, large, 4096, large[4096])); + ASSERT_OK_ZERO(decompress_startswith(c, compressed, csize, &buf, large, 4096, 0xff)); } - assert_se(r >= 0); - - len = strlen(data); - - r = decompress_sw(compressed, csize, (void **) &decompressed, data, len, '\0'); - assert_se(r > 0); - r = decompress_sw(compressed, csize, (void **) &decompressed, data, len, 'w'); - assert_se(r == 0); - r = decompress_sw(compressed, csize, (void **) &decompressed, "barbarbar", 9, ' '); - assert_se(r == 0); - r = decompress_sw(compressed, csize, (void **) &decompressed, data, len - 1, data[len-1]); - assert_se(r > 0); - r = decompress_sw(compressed, csize, (void **) &decompressed, data, len - 1, 'w'); - assert_se(r == 0); - r = decompress_sw(compressed, csize, (void **) &decompressed, data, len, '\0'); - assert_se(r > 0); } -_unused_ static void test_decompress_startswith_short(const char *compression, - compress_blob_t compress, - decompress_sw_t decompress_sw) { - +TEST(decompress_startswith_short) { #define TEXT "HUGE=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - char buf[1024]; - size_t csize; - int r; + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; - log_info("/* %s with %s */", __func__, compression); + char buf[1024]; + size_t csize; - r = compress(TEXT, sizeof TEXT, buf, sizeof buf, &csize, /* level= */ -1); - assert_se(r >= 0); + log_info("/* decompress_startswith_short with %s */", compression_to_string(c)); - for (size_t i = 1; i < strlen(TEXT); i++) { - _cleanup_free_ void *buf2 = NULL; + ASSERT_OK(compress_blob(c, TEXT, sizeof TEXT, buf, sizeof buf, &csize, -1)); - assert_se(buf2 = malloc(i)); + for (size_t i = 1; i < strlen(TEXT); i++) { + _cleanup_free_ void *buf2 = NULL; - assert_se(decompress_sw(buf, csize, &buf2, TEXT, i, TEXT[i]) == 1); - assert_se(decompress_sw(buf, csize, &buf2, TEXT, i, 'y') == 0); - } -} + ASSERT_NOT_NULL(buf2 = malloc(i)); -_unused_ static void test_compress_stream(const char *compression, - const char *cat, - compress_stream_t compress, - decompress_stream_t decompress, - const char *srcfile) { - - _cleanup_close_ int src = -EBADF, dst = -EBADF, dst2 = -EBADF; - _cleanup_(unlink_tempfilep) char - pattern[] = "/tmp/systemd-test.compressed.XXXXXX", - pattern2[] = "/tmp/systemd-test.compressed.XXXXXX"; - int r; - _cleanup_free_ char *cmd = NULL, *cmd2 = NULL; - struct stat st = {}; - uint64_t uncompressed_size; - - r = find_executable(cat, NULL); - if (r < 0) { - log_error_errno(r, "Skipping %s, could not find %s binary: %m", __func__, cat); - return; + ASSERT_OK_POSITIVE(decompress_startswith(c, buf, csize, &buf2, TEXT, i, TEXT[i])); + ASSERT_OK_ZERO(decompress_startswith(c, buf, csize, &buf2, TEXT, i, 'y')); + } } +#undef TEXT +} - log_debug("/* testing %s compression */", compression); +TEST(compress_decompress_stream) { + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; - log_debug("/* create source from %s */", srcfile); + const char *cat = cat_for_compression(c); + if (!cat) + continue; - ASSERT_OK(src = open(srcfile, O_RDONLY|O_CLOEXEC)); + int r = find_executable(cat, NULL); + if (r < 0) { + log_error_errno(r, "Skipping %s, could not find %s binary: %m", + compression_to_string(c), cat); + continue; + } - log_debug("/* test compression */"); + _cleanup_close_ int src = -EBADF, dst = -EBADF, dst2 = -EBADF; + _cleanup_(unlink_tempfilep) char + pattern[] = "/tmp/systemd-test.compressed.XXXXXX", + pattern2[] = "/tmp/systemd-test.compressed.XXXXXX"; + _cleanup_free_ char *cmd = NULL, *cmd2 = NULL; + struct stat st = {}; + uint64_t uncompressed_size; - assert_se((dst = mkostemp_safe(pattern)) >= 0); + log_debug("/* testing %s stream compression */", compression_to_string(c)); - ASSERT_OK(compress(src, dst, -1, &uncompressed_size)); + ASSERT_OK(src = open(srcfile, O_RDONLY|O_CLOEXEC)); + ASSERT_OK(dst = mkostemp_safe(pattern)); - if (cat) { - assert_se(asprintf(&cmd, "%s %s | diff '%s' -", cat, pattern, srcfile) > 0); - assert_se(system(cmd) == 0); - } + ASSERT_OK(compress_stream(c, src, dst, -1, &uncompressed_size)); - log_debug("/* test decompression */"); + ASSERT_OK_POSITIVE(asprintf(&cmd, "%s %s | diff '%s' -", cat, pattern, srcfile)); + ASSERT_OK_ZERO(system(cmd)); - assert_se((dst2 = mkostemp_safe(pattern2)) >= 0); + ASSERT_OK(dst2 = mkostemp_safe(pattern2)); - assert_se(stat(srcfile, &st) == 0); - assert_se((uint64_t)st.st_size == uncompressed_size); + ASSERT_OK_ZERO_ERRNO(stat(srcfile, &st)); + ASSERT_EQ((uint64_t) st.st_size, uncompressed_size); - assert_se(lseek(dst, 0, SEEK_SET) == 0); - r = decompress(dst, dst2, st.st_size); - assert_se(r == 0); + ASSERT_OK_ERRNO(lseek(dst, 0, SEEK_SET)); + ASSERT_OK_ZERO(decompress_stream(c, dst, dst2, st.st_size)); - assert_se(asprintf(&cmd2, "diff '%s' %s", srcfile, pattern2) > 0); - assert_se(system(cmd2) == 0); + ASSERT_OK_POSITIVE(asprintf(&cmd2, "diff '%s' %s", srcfile, pattern2)); + ASSERT_OK_ZERO(system(cmd2)); - log_debug("/* test faulty decompression */"); + log_debug("/* test faulty decompression */"); - assert_se(lseek(dst, 1, SEEK_SET) == 1); - r = decompress(dst, dst2, st.st_size); - assert_se(IN_SET(r, 0, -EBADMSG)); + ASSERT_OK_ERRNO(lseek(dst, 1, SEEK_SET)); + r = decompress_stream(c, dst, dst2, st.st_size); + ASSERT_TRUE(IN_SET(r, 0, -EBADMSG)); - assert_se(lseek(dst, 0, SEEK_SET) == 0); - assert_se(lseek(dst2, 0, SEEK_SET) == 0); - r = decompress(dst, dst2, st.st_size - 1); - assert_se(r == -EFBIG); + ASSERT_OK_ERRNO(lseek(dst, 0, SEEK_SET)); + ASSERT_OK_ERRNO(lseek(dst2, 0, SEEK_SET)); + ASSERT_ERROR(decompress_stream(c, dst, dst2, st.st_size - 1), EFBIG); + } } -#endif -#if HAVE_LZ4 -extern DLSYM_PROTOTYPE(LZ4_compress_default); -extern DLSYM_PROTOTYPE(LZ4_decompress_safe); -extern DLSYM_PROTOTYPE(LZ4_decompress_safe_partial); -extern DLSYM_PROTOTYPE(LZ4_versionNumber); +struct decompressor_test_data { + uint8_t *buf; + size_t size; +}; -static void test_lz4_decompress_partial(void) { - char buf[20000], buf2[100]; - size_t buf_size = sizeof(buf), compressed; - int r; - _cleanup_free_ char *huge = NULL; +static int test_decompressor_callback(const void *p, size_t size, void *userdata) { + struct decompressor_test_data *d = ASSERT_PTR(userdata); - log_debug("/* %s */", __func__); + if (!GREEDY_REALLOC(d->buf, d->size + size)) + return -ENOMEM; - assert_se(huge = malloc(HUGE_SIZE)); - memcpy(huge, "HUGE=", STRLEN("HUGE=")); - memset(&huge[STRLEN("HUGE=")], 'x', HUGE_SIZE - STRLEN("HUGE=") - 1); - huge[HUGE_SIZE - 1] = '\0'; + memcpy(d->buf + d->size, p, size); + d->size += size; + return 0; +} - r = sym_LZ4_compress_default(huge, buf, HUGE_SIZE, buf_size); - assert_se(r >= 0); - compressed = r; - log_info("Compressed %i → %zu", HUGE_SIZE, compressed); - - r = sym_LZ4_decompress_safe(buf, huge, r, HUGE_SIZE); - assert_se(r >= 0); - log_info("Decompressed → %i", r); - - r = sym_LZ4_decompress_safe_partial(buf, huge, - compressed, - 12, HUGE_SIZE); - assert_se(r >= 0); - log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE, r); - - for (size_t size = 1; size < sizeof(buf2); size++) { - /* This failed in older lz4s but works in newer ones. */ - r = sym_LZ4_decompress_safe_partial(buf, buf2, compressed, size, size); - log_info("Decompressed partial %zu/%zu → %i (%s)", size, size, r, - r < 0 ? "bad" : "good"); - if (r >= 0 && sym_LZ4_versionNumber() >= 10803) - /* lz4 <= 1.8.2 should fail that test, let's only check for newer ones */ - assert_se(memcmp(buf2, huge, r) == 0); +TEST(decompress_stream_sparse) { + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; + + _cleanup_close_ int src = -EBADF, compressed = -EBADF, decompressed = -EBADF; + _cleanup_(unlink_tempfilep) char + pattern_src[] = "/tmp/systemd-test.sparse-src.XXXXXX", + pattern_compressed[] = "/tmp/systemd-test.sparse-compressed.XXXXXX", + pattern_decompressed[] = "/tmp/systemd-test.sparse-decompressed.XXXXXX"; + /* Create a sparse-like input: 4K of data, 64K of zeros, 4K of data, 64K trailing zeros. + * Total apparent size: 136K, but most of it is zeros. */ + uint8_t data_block[4096]; + struct stat st_src, st_decompressed; + uint64_t uncompressed_size; + + log_debug("/* testing %s sparse decompression */", compression_to_string(c)); + + random_bytes(data_block, sizeof(data_block)); + + ASSERT_OK(src = mkostemp_safe(pattern_src)); + + /* Write: 4K data, 64K zeros, 4K data, 64K zeros */ + ASSERT_OK(loop_write(src, data_block, sizeof(data_block))); + ASSERT_OK_ERRNO(ftruncate(src, sizeof(data_block) + 65536)); + ASSERT_OK_ERRNO(lseek(src, sizeof(data_block) + 65536, SEEK_SET)); + ASSERT_OK(loop_write(src, data_block, sizeof(data_block))); + ASSERT_OK_ERRNO(ftruncate(src, 2 * sizeof(data_block) + 2 * 65536)); + ASSERT_EQ(lseek(src, 0, SEEK_SET), (off_t) 0); + + ASSERT_OK_ERRNO(fstat(src, &st_src)); + ASSERT_EQ(st_src.st_size, 2 * (off_t) sizeof(data_block) + 2 * 65536); + + /* Compress */ + ASSERT_OK(compressed = mkostemp_safe(pattern_compressed)); + ASSERT_OK(compress_stream(c, src, compressed, -1, &uncompressed_size)); + ASSERT_EQ((uint64_t) st_src.st_size, uncompressed_size); + + /* Decompress to a regular file (sparse writes auto-detected) */ + ASSERT_OK(decompressed = mkostemp_safe(pattern_decompressed)); + ASSERT_EQ(lseek(compressed, 0, SEEK_SET), (off_t) 0); + ASSERT_OK_ZERO(decompress_stream(c, compressed, decompressed, st_src.st_size)); + + /* Verify apparent size matches */ + ASSERT_OK_ERRNO(fstat(decompressed, &st_decompressed)); + ASSERT_EQ(st_decompressed.st_size, st_src.st_size); + + /* Verify content matches by comparing bytes */ + ASSERT_EQ(lseek(src, 0, SEEK_SET), (off_t) 0); + ASSERT_EQ(lseek(decompressed, 0, SEEK_SET), (off_t) 0); + + for (off_t offset = 0; offset < st_src.st_size;) { + uint8_t buf_src[4096], buf_dst[4096]; + size_t to_read = MIN((size_t) (st_src.st_size - offset), sizeof(buf_src)); + + ASSERT_EQ(loop_read(src, buf_src, to_read, true), (ssize_t) to_read); + ASSERT_EQ(loop_read(decompressed, buf_dst, to_read, true), (ssize_t) to_read); + ASSERT_EQ(memcmp(buf_src, buf_dst, to_read), 0); + offset += to_read; + } + + /* Verify the decompressed file is actually sparse (uses less disk than apparent size). + * st_blocks is in 512-byte units. The file has 128K of zeros, so disk usage should be + * noticeably less than the apparent size if sparse writes worked. + * Only assert if the filesystem supports holes (SEEK_HOLE). */ + log_debug("%s sparse decompression: apparent=%jd disk=%jd", + compression_to_string(c), + (intmax_t) st_decompressed.st_size, + (intmax_t) st_decompressed.st_blocks * 512); + if (lseek(decompressed, 0, SEEK_HOLE) < st_decompressed.st_size) + ASSERT_LT(st_decompressed.st_blocks * 512, st_decompressed.st_size); + else + log_debug("Filesystem does not support holes, skipping sparsity check"); + + /* Test all-zeros input: entire output should be a hole */ + log_debug("/* testing %s sparse decompression of all-zeros */", compression_to_string(c)); + { + _cleanup_close_ int zsrc = -EBADF, zcompressed = -EBADF, zdecompressed = -EBADF; + _cleanup_(unlink_tempfilep) char + zp_src[] = "/tmp/systemd-test.sparse-zero-src.XXXXXX", + zp_compressed[] = "/tmp/systemd-test.sparse-zero-compressed.XXXXXX", + zp_decompressed[] = "/tmp/systemd-test.sparse-zero-decompressed.XXXXXX"; + struct stat zst; + uint64_t zsize; + uint8_t zeros[65536] = {}; + + ASSERT_OK(zsrc = mkostemp_safe(zp_src)); + ASSERT_OK(loop_write(zsrc, zeros, sizeof(zeros))); + ASSERT_EQ(lseek(zsrc, 0, SEEK_SET), (off_t) 0); + + ASSERT_OK(zcompressed = mkostemp_safe(zp_compressed)); + ASSERT_OK(compress_stream(c, zsrc, zcompressed, -1, &zsize)); + ASSERT_EQ(zsize, (uint64_t) sizeof(zeros)); + + ASSERT_OK(zdecompressed = mkostemp_safe(zp_decompressed)); + ASSERT_EQ(lseek(zcompressed, 0, SEEK_SET), (off_t) 0); + ASSERT_OK_ZERO(decompress_stream(c, zcompressed, zdecompressed, sizeof(zeros))); + + ASSERT_OK_ERRNO(fstat(zdecompressed, &zst)); + ASSERT_EQ(zst.st_size, (off_t) sizeof(zeros)); + /* All zeros — disk usage should be minimal */ + log_debug("%s all-zeros sparse: apparent=%jd disk=%jd", + compression_to_string(c), (intmax_t) zst.st_size, (intmax_t) zst.st_blocks * 512); + if (lseek(zdecompressed, 0, SEEK_HOLE) < zst.st_size) + ASSERT_LT(zst.st_blocks * 512, zst.st_size); + else + log_debug("Filesystem does not support holes, skipping sparsity check"); + } + + /* Test data ending with non-zero bytes: ftruncate should be a no-op */ + log_debug("/* testing %s sparse decompression ending with data */", compression_to_string(c)); + { + _cleanup_close_ int dsrc = -EBADF, dcompressed = -EBADF, ddecompressed = -EBADF; + _cleanup_(unlink_tempfilep) char + dp_src[] = "/tmp/systemd-test.sparse-end-src.XXXXXX", + dp_compressed[] = "/tmp/systemd-test.sparse-end-compressed.XXXXXX", + dp_decompressed[] = "/tmp/systemd-test.sparse-end-decompressed.XXXXXX"; + struct stat dst; + uint64_t dsize; + uint8_t zeros[65536] = {}; + + /* 64K zeros followed by 4K random data */ + ASSERT_OK(dsrc = mkostemp_safe(dp_src)); + ASSERT_OK(loop_write(dsrc, zeros, sizeof(zeros))); + ASSERT_OK(loop_write(dsrc, data_block, sizeof(data_block))); + ASSERT_EQ(lseek(dsrc, 0, SEEK_SET), (off_t) 0); + + ASSERT_OK(dcompressed = mkostemp_safe(dp_compressed)); + ASSERT_OK(compress_stream(c, dsrc, dcompressed, -1, &dsize)); + ASSERT_EQ(dsize, (uint64_t)(sizeof(zeros) + sizeof(data_block))); + + ASSERT_OK(ddecompressed = mkostemp_safe(dp_decompressed)); + ASSERT_EQ(lseek(dcompressed, 0, SEEK_SET), (off_t) 0); + ASSERT_OK_ZERO(decompress_stream(c, dcompressed, ddecompressed, dsize)); + + ASSERT_OK_ERRNO(fstat(ddecompressed, &dst)); + ASSERT_EQ(dst.st_size, (off_t)(sizeof(zeros) + sizeof(data_block))); + } } } -#endif -int main(int argc, char *argv[]) { -#if HAVE_COMPRESSION - _unused_ const char text[] = - "text\0foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF" - "foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF"; +TEST(compressor_decompressor_push_api) { + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; + + log_info("/* testing %s Compressor/Decompressor push API */", compression_to_string(c)); - /* The file to test compression on can be specified as the first argument */ - const char *srcfile = argc > 1 ? argv[1] : argv[0]; + _cleanup_(compressor_freep) Compressor *compressor = NULL; + _cleanup_(compressor_freep) Decompressor *decompressor = NULL; + _cleanup_free_ void *compressed = NULL, *finish_buf = NULL; + size_t compressed_size = 0, compressed_alloc = 0; + size_t finish_size = 0, finish_alloc = 0; - char data[512] = "random\0"; + /* Compress */ + ASSERT_OK(compressor_new(&compressor, c)); + ASSERT_EQ(compressor_type(compressor), c); - _cleanup_free_ char *huge = NULL; + ASSERT_OK(compressor_start(compressor, text, sizeof(text), &compressed, &compressed_size, &compressed_alloc)); + ASSERT_OK(compressor_finish(compressor, &finish_buf, &finish_size, &finish_alloc)); - assert_se(huge = malloc(HUGE_SIZE)); + size_t total_compressed = compressed_size + finish_size; + _cleanup_free_ void *full_compressed = malloc(total_compressed); + ASSERT_NOT_NULL(full_compressed); + memcpy(full_compressed, compressed, compressed_size); + if (finish_size > 0) + memcpy((uint8_t*) full_compressed + compressed_size, finish_buf, finish_size); + + compressor = compressor_free(compressor); + + /* Decompress via detect + push and verify content */ + ASSERT_OK_POSITIVE(decompressor_detect(&decompressor, full_compressed, total_compressed)); + ASSERT_EQ(compressor_type(decompressor), c); + + struct decompressor_test_data result = {}; + ASSERT_OK(decompressor_push(decompressor, full_compressed, total_compressed, test_decompressor_callback, &result)); + ASSERT_EQ(result.size, sizeof(text)); + ASSERT_EQ(memcmp(result.buf, text, sizeof(text)), 0); + free(result.buf); + + decompressor = compressor_free(decompressor); + } + + /* Test compressor_type on NULL */ + ASSERT_EQ(compressor_type(NULL), _COMPRESSION_INVALID); + + /* Test decompressor_force_off */ + _cleanup_(compressor_freep) Decompressor *d = NULL; + ASSERT_OK(decompressor_force_off(&d)); + ASSERT_EQ(compressor_type(d), COMPRESSION_NONE); + d = compressor_free(d); + + /* Test decompressor_detect returning 0 on too-small input */ + ASSERT_OK_ZERO(decompressor_detect(&d, "x", 1)); + ASSERT_NULL(d); +} + +static int intro(void) { + srcfile = saved_argc > 1 ? saved_argv[1] : saved_argv[0]; + + ASSERT_NOT_NULL(huge = malloc(HUGE_SIZE)); memcpy(huge, "HUGE=", STRLEN("HUGE=")); memset(&huge[STRLEN("HUGE=")], 'x', HUGE_SIZE - STRLEN("HUGE=") - 1); huge[HUGE_SIZE - 1] = '\0'; - test_setup_logging(LOG_DEBUG); - random_bytes(data + 7, sizeof(data) - 7); -#if HAVE_XZ - test_compress_decompress("XZ", compress_blob_xz, decompress_blob_xz, - text, sizeof(text), false); - test_compress_decompress("XZ", compress_blob_xz, decompress_blob_xz, - data, sizeof(data), true); - - test_decompress_startswith("XZ", - compress_blob_xz, decompress_startswith_xz, - text, sizeof(text), false); - test_decompress_startswith("XZ", - compress_blob_xz, decompress_startswith_xz, - data, sizeof(data), true); - test_decompress_startswith("XZ", - compress_blob_xz, decompress_startswith_xz, - huge, HUGE_SIZE, true); - - test_compress_stream("XZ", "xzcat", - compress_stream_xz, decompress_stream_xz, srcfile); - - test_decompress_startswith_short("XZ", compress_blob_xz, decompress_startswith_xz); - -#else - log_info("/* XZ test skipped */"); -#endif - -#if HAVE_LZ4 - if (dlopen_lz4() >= 0) { - test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4, - text, sizeof(text), false); - test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4, - data, sizeof(data), true); - - test_decompress_startswith("LZ4", - compress_blob_lz4, decompress_startswith_lz4, - text, sizeof(text), false); - test_decompress_startswith("LZ4", - compress_blob_lz4, decompress_startswith_lz4, - data, sizeof(data), true); - test_decompress_startswith("LZ4", - compress_blob_lz4, decompress_startswith_lz4, - huge, HUGE_SIZE, true); - - test_compress_stream("LZ4", "lz4cat", - compress_stream_lz4, decompress_stream_lz4, srcfile); - - test_lz4_decompress_partial(); - - test_decompress_startswith_short("LZ4", compress_blob_lz4, decompress_startswith_lz4); - } else - log_error("/* Can't load liblz4 */"); -#else - log_info("/* LZ4 test skipped */"); -#endif - -#if HAVE_ZSTD - test_compress_decompress("ZSTD", compress_blob_zstd, decompress_blob_zstd, - text, sizeof(text), false); - test_compress_decompress("ZSTD", compress_blob_zstd, decompress_blob_zstd, - data, sizeof(data), true); - - test_decompress_startswith("ZSTD", - compress_blob_zstd, decompress_startswith_zstd, - text, sizeof(text), false); - test_decompress_startswith("ZSTD", - compress_blob_zstd, decompress_startswith_zstd, - data, sizeof(data), true); - test_decompress_startswith("ZSTD", - compress_blob_zstd, decompress_startswith_zstd, - huge, HUGE_SIZE, true); - - test_compress_stream("ZSTD", "zstdcat", - compress_stream_zstd, decompress_stream_zstd, srcfile); - - test_decompress_startswith_short("ZSTD", compress_blob_zstd, decompress_startswith_zstd); -#else - log_info("/* ZSTD test skipped */"); -#endif - return 0; -#else - return log_tests_skipped("no compression algorithm supported"); -#endif } + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-copy.c b/src/test/test-copy.c index 758a597fc539a..719301478246e 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -364,9 +364,7 @@ static void test_copy_bytes_regular_file_one(const char *src, bool try_reflink, /* Make sure the file is now higher than max_bytes */ assert_se(ftruncate(fd2, max_bytes + 1) == 0); - assert_se(lseek(fd2, 0, SEEK_SET) == 0); - - r = copy_bytes(fd2, fd3, max_bytes, try_reflink ? COPY_REFLINK : 0); + r = copy_bytes(fd2, fd3, max_bytes, COPY_SEEK0_SOURCE | (try_reflink ? COPY_REFLINK : 0)); if (max_bytes == UINT64_MAX) assert_se(r == 0); else @@ -460,9 +458,8 @@ TEST_RET(copy_holes) { assert_se(lseek(fd, 0, SEEK_END) == 2 * blksz); /* Only ftruncate() can create holes at the end of a file. */ assert_se(ftruncate(fd, 3 * blksz) >= 0); - assert_se(lseek(fd, 0, SEEK_SET) >= 0); - assert_se(copy_bytes(fd, fd_copy, UINT64_MAX, COPY_HOLES) >= 0); + assert_se(copy_bytes(fd, fd_copy, UINT64_MAX, COPY_SEEK0_SOURCE|COPY_HOLES) >= 0); /* Test that the hole starts at the beginning of the file. */ assert_se(lseek(fd_copy, 0, SEEK_HOLE) == 0); @@ -526,26 +523,20 @@ TEST_RET(copy_holes_with_gaps) { assert_se(st.st_size == 3 * blksz); /* Copy to the middle of the second hole */ - assert_se(lseek(fd, 0, SEEK_SET) >= 0); - assert_se(lseek(fd_copy, 0, SEEK_SET) >= 0); assert_se(ftruncate(fd_copy, 0) >= 0); - assert_se(copy_bytes(fd, fd_copy, 4 * blksz, COPY_HOLES) >= 0); + assert_se(copy_bytes(fd, fd_copy, 4 * blksz, COPY_SEEK0_SOURCE|COPY_SEEK0_TARGET|COPY_HOLES) >= 0); ASSERT_OK_ERRNO(fstat(fd_copy, &st)); assert_se(st.st_size == 4 * blksz); /* Copy to the end of the second hole */ - assert_se(lseek(fd, 0, SEEK_SET) >= 0); - assert_se(lseek(fd_copy, 0, SEEK_SET) >= 0); assert_se(ftruncate(fd_copy, 0) >= 0); - assert_se(copy_bytes(fd, fd_copy, 5 * blksz, COPY_HOLES) >= 0); + assert_se(copy_bytes(fd, fd_copy, 5 * blksz, COPY_SEEK0_SOURCE|COPY_SEEK0_TARGET|COPY_HOLES) >= 0); ASSERT_OK_ERRNO(fstat(fd_copy, &st)); assert_se(st.st_size == 5 * blksz); /* Copy everything */ - assert_se(lseek(fd, 0, SEEK_SET) >= 0); - assert_se(lseek(fd_copy, 0, SEEK_SET) >= 0); assert_se(ftruncate(fd_copy, 0) >= 0); - assert_se(copy_bytes(fd, fd_copy, UINT64_MAX, COPY_HOLES) >= 0); + assert_se(copy_bytes(fd, fd_copy, UINT64_MAX, COPY_SEEK0_SOURCE|COPY_SEEK0_TARGET|COPY_HOLES) >= 0); ASSERT_OK_ERRNO(fstat(fd_copy, &st)); assert_se(st.st_size == 6 * blksz); diff --git a/src/test/test-creds.c b/src/test/test-creds.c index 5cad608093341..e380550a5a70c 100644 --- a/src/test/test-creds.c +++ b/src/test/test-creds.c @@ -177,7 +177,7 @@ static void test_encrypt_decrypt_with(sd_id128_t mode, uid_t uid) { &decrypted); ASSERT_OK(r); - ASSERT_EQ(iovec_memcmp(&plaintext, &decrypted), 0); + ASSERT_TRUE(iovec_equal(&plaintext, &decrypted)); } static bool try_tpm2(void) { diff --git a/src/test/test-openssl.c b/src/test/test-crypto-util.c similarity index 93% rename from src/test/test-openssl.c rename to src/test/test-crypto-util.c index a09484a2ba8ad..a2fd090ed3fab 100644 --- a/src/test/test-openssl.c +++ b/src/test/test-crypto-util.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "openssl-util.h" +#include "crypto-util.h" #include "tests.h" TEST(openssl_pkey_from_pem) { @@ -42,16 +42,16 @@ TEST(rsa_pkey_n_e) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; assert_se(rsa_pkey_from_n_e(n, n_len, &e, sizeof(e), &pkey) >= 0); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new(pkey, NULL); assert_se(ctx); - assert_se(EVP_PKEY_verify_init(ctx) == 1); + assert_se(sym_EVP_PKEY_verify_init(ctx) == 1); const char *msg = "this is a secret"; DEFINE_HEX_PTR(sig, "14b53e0c6ad99a350c3d7811e8160f4ae03ad159815bb91bddb9735b833588df2eac221fbd3fc4ece0dd63bfaeddfdaf4ae67021e759f3638bc194836413414f54e8c4d01c9c37fa4488ea2ef772276b8a33822a53c97b1c35acfb4bc621cfb8fad88f0cf7d5491f05236886afbf9ed47f9469536482f50f74a20defa59d99676bed62a17b5eb98641df5a2f8080fa4b24f2749cc152fa65ba34c14022fcb27f1b36f52021950d7b9b6c3042c50b84cfb7d55a5f9235bfd58e1bf1f604eb93416c5fb5fd90cb68f1270dfa9daf67f52c604f62c2f2beee5e7e672b0e6e9833dd43dba99b77668540c850c9a81a5ea7aaf6297383e6135bd64572362333121fc7"); - assert_se(EVP_PKEY_verify(ctx, sig, sig_len, (unsigned char*) msg, strlen(msg)) == 1); + assert_se(sym_EVP_PKEY_verify(ctx, sig, sig_len, (unsigned char*) msg, strlen(msg)) == 1); DEFINE_HEX_PTR(invalid_sig, "1234"); - assert_se(EVP_PKEY_verify(ctx, invalid_sig, invalid_sig_len, (unsigned char*) msg, strlen(msg)) != 1); + assert_se(sym_EVP_PKEY_verify(ctx, invalid_sig, invalid_sig_len, (unsigned char*) msg, strlen(msg)) != 1); _cleanup_free_ void *n2 = NULL, *e2 = NULL; size_t n2_size, e2_size; @@ -69,16 +69,16 @@ TEST(ecc_pkey_curve_x_y) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; assert_se(ecc_pkey_from_curve_x_y(curveid, x, x_len, y, y_len, &pkey) >= 0); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new(pkey, NULL); assert_se(ctx); - assert_se(EVP_PKEY_verify_init(ctx) == 1); + assert_se(sym_EVP_PKEY_verify_init(ctx) == 1); const char *msg = "this is a secret"; DEFINE_HEX_PTR(sig, "3045022100f6ca10f7ed57a020679899b26dd5ac5a1079265885e2a6477f527b6a3f02b5ca02207b550eb3e7b69360aff977f7f6afac99c3f28266b6c5338ce373f6b59263000a"); - assert_se(EVP_PKEY_verify(ctx, sig, sig_len, (unsigned char*) msg, strlen(msg)) == 1); + assert_se(sym_EVP_PKEY_verify(ctx, sig, sig_len, (unsigned char*) msg, strlen(msg)) == 1); DEFINE_HEX_PTR(invalid_sig, "1234"); - assert_se(EVP_PKEY_verify(ctx, invalid_sig, invalid_sig_len, (unsigned char*) msg, strlen(msg)) != 1); + assert_se(sym_EVP_PKEY_verify(ctx, invalid_sig, invalid_sig_len, (unsigned char*) msg, strlen(msg)) != 1); _cleanup_free_ void *x2 = NULL, *y2 = NULL; size_t x2_size, y2_size; @@ -465,4 +465,31 @@ TEST(ecc_ecdh) { assert_se(memcmp_nn(secretAC, secretAC_size, secretAB, secretAB_size) != 0); } -DEFINE_TEST_MAIN(LOG_DEBUG); +TEST(string_hashsum) { + _cleanup_free_ char *out1 = NULL, *out2 = NULL, *out3 = NULL, *out4 = NULL; + + ASSERT_OK(string_hashsum("asdf", 4, "SHA224", &out1)); + /* echo -n 'asdf' | sha224sum - */ + ASSERT_STREQ(out1, "7872a74bcbf298a1e77d507cd95d4f8d96131cbbd4cdfc571e776c8a"); + + ASSERT_OK(string_hashsum("asdf", 4, "SHA256", &out2)); + /* echo -n 'asdf' | sha256sum - */ + ASSERT_STREQ(out2, "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"); + + ASSERT_OK(string_hashsum("", 0, "SHA224", &out3)); + /* echo -n '' | sha224sum - */ + ASSERT_STREQ(out3, "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"); + + ASSERT_OK(string_hashsum("", 0, "SHA256", &out4)); + /* echo -n '' | sha256sum - */ + ASSERT_STREQ(out4, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); +} + +static int intro(void) { + if (dlopen_libcrypto(LOG_DEBUG) < 0) + return log_tests_skipped("libcrypto is not available"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-cryptolib.c b/src/test/test-cryptolib.c deleted file mode 100644 index d86236bafe028..0000000000000 --- a/src/test/test-cryptolib.c +++ /dev/null @@ -1,27 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "alloc-util.h" -#include "openssl-util.h" -#include "tests.h" - -TEST(string_hashsum) { - _cleanup_free_ char *out1 = NULL, *out2 = NULL, *out3 = NULL, *out4 = NULL; - - ASSERT_OK(string_hashsum("asdf", 4, "SHA224", &out1)); - /* echo -n 'asdf' | sha224sum - */ - ASSERT_STREQ(out1, "7872a74bcbf298a1e77d507cd95d4f8d96131cbbd4cdfc571e776c8a"); - - ASSERT_OK(string_hashsum("asdf", 4, "SHA256", &out2)); - /* echo -n 'asdf' | sha256sum - */ - ASSERT_STREQ(out2, "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"); - - ASSERT_OK(string_hashsum("", 0, "SHA224", &out3)); - /* echo -n '' | sha224sum - */ - ASSERT_STREQ(out3, "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"); - - ASSERT_OK(string_hashsum("", 0, "SHA256", &out4)); - /* echo -n '' | sha256sum - */ - ASSERT_STREQ(out4, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); -} - -DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index 4b805326982aa..7421a77024f1b 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -5,9 +5,13 @@ #include "blkid-util.h" #include "bpf-dlopen.h" #include "compress.h" +#include "crypto-util.h" #include "cryptsetup-util.h" +#include "curl-util.h" #include "elf-util.h" +#include "fdisk-util.h" #include "gcrypt-util.h" +#include "gnutls-util.h" #include "idn-util.h" #include "libarchive-util.h" #include "libaudit-util.h" @@ -15,6 +19,7 @@ #include "libfido2-util.h" #include "libmount-util.h" #include "main-func.h" +#include "microhttpd-util.h" #include "module-util.h" #include "pam-util.h" #include "password-quality-util-passwdqc.h" @@ -24,15 +29,16 @@ #include "qrcode-util.h" #include "seccomp-util.h" #include "selinux-util.h" +#include "ssl-util.h" #include "tests.h" #include "tpm2-util.h" -#define ASSERT_DLOPEN(func, cond) \ - do { \ - if (cond) \ - ASSERT_OK(func()); \ - else \ - ASSERT_ERROR(func(), EOPNOTSUPP); \ +#define ASSERT_DLOPEN(func, cond) \ + do { \ + if (cond) \ + ASSERT_OK(func(LOG_DEBUG)); \ + else \ + ASSERT_ERROR(func(LOG_DEBUG), EOPNOTSUPP); \ } while (false) static int run(int argc, char **argv) { @@ -42,11 +48,15 @@ static int run(int argc, char **argv) { * where .so versions change and distributions update, but systemd doesn't have the new so names * around yet. */ + ASSERT_DLOPEN(dlopen_bzip2, HAVE_BZIP2); ASSERT_DLOPEN(dlopen_bpf, HAVE_LIBBPF); ASSERT_DLOPEN(dlopen_cryptsetup, HAVE_LIBCRYPTSETUP); + ASSERT_DLOPEN(dlopen_curl, HAVE_LIBCURL); ASSERT_DLOPEN(dlopen_dw, HAVE_ELFUTILS); ASSERT_DLOPEN(dlopen_elf, HAVE_ELFUTILS); + ASSERT_DLOPEN(dlopen_fdisk, HAVE_LIBFDISK); ASSERT_DLOPEN(dlopen_gcrypt, HAVE_GCRYPT); + ASSERT_DLOPEN(dlopen_gnutls, HAVE_GNUTLS); ASSERT_DLOPEN(dlopen_idn, HAVE_LIBIDN2); ASSERT_DLOPEN(dlopen_libacl, HAVE_ACL); ASSERT_DLOPEN(dlopen_libapparmor, HAVE_APPARMOR); @@ -60,14 +70,18 @@ static int run(int argc, char **argv) { ASSERT_DLOPEN(dlopen_libpam, HAVE_PAM); ASSERT_DLOPEN(dlopen_libseccomp, HAVE_SECCOMP); ASSERT_DLOPEN(dlopen_libselinux, HAVE_SELINUX); + ASSERT_DLOPEN(dlopen_libcrypto, HAVE_OPENSSL); + ASSERT_DLOPEN(dlopen_libssl, HAVE_OPENSSL); + ASSERT_DLOPEN(dlopen_xz, HAVE_XZ); ASSERT_DLOPEN(dlopen_lz4, HAVE_LZ4); - ASSERT_DLOPEN(dlopen_lzma, HAVE_XZ); + ASSERT_DLOPEN(dlopen_microhttpd, HAVE_MICROHTTPD); ASSERT_DLOPEN(dlopen_p11kit, HAVE_P11KIT); ASSERT_DLOPEN(dlopen_passwdqc, HAVE_PASSWDQC); ASSERT_DLOPEN(dlopen_pcre2, HAVE_PCRE2); ASSERT_DLOPEN(dlopen_pwquality, HAVE_PWQUALITY); ASSERT_DLOPEN(dlopen_qrencode, HAVE_QRENCODE); ASSERT_DLOPEN(dlopen_tpm2, HAVE_TPM2); + ASSERT_DLOPEN(dlopen_zlib, HAVE_ZLIB); ASSERT_DLOPEN(dlopen_zstd, HAVE_ZSTD); return 0; diff --git a/src/test/test-engine.c b/src/test/test-engine.c index 06a1d9e6fd49a..e1a2f7ea04823 100644 --- a/src/test/test-engine.c +++ b/src/test/test-engine.c @@ -184,32 +184,32 @@ int main(int argc, char *argv[]) { assert_se(manager_add_job(m, JOB_START, a_conj, JOB_REPLACE, NULL, &j) == -EDEADLK); manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); - assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); - assert_se(!hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); - assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); - assert_se(!hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); assert_se(unit_add_dependency(a, UNIT_PROPAGATES_RELOAD_TO, b, true, UNIT_DEPENDENCY_UDEV) >= 0); assert_se(unit_add_dependency(a, UNIT_PROPAGATES_RELOAD_TO, c, true, UNIT_DEPENDENCY_PROC_SWAP) >= 0); - assert_se( hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); - assert_se( hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); - assert_se( hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); - assert_se( hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_TRUE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + ASSERT_TRUE(hashmap_contains(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_TRUE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + ASSERT_TRUE(hashmap_contains(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); unit_remove_dependencies(a, UNIT_DEPENDENCY_UDEV); - assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); - assert_se(!hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); - assert_se( hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); - assert_se( hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_TRUE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + ASSERT_TRUE(hashmap_contains(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); unit_remove_dependencies(a, UNIT_DEPENDENCY_PROC_SWAP); - assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); - assert_se(!hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); - assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); - assert_se(!hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); assert_se(manager_load_unit(m, "unit-with-multiple-dashes.service", NULL, NULL, &unit_with_multiple_dashes) >= 0); diff --git a/src/test/test-errno-util.c b/src/test/test-errno-util.c index 9eb729c2e4ba3..30a47cbcdeef4 100644 --- a/src/test/test-errno-util.c +++ b/src/test/test-errno-util.c @@ -6,10 +6,11 @@ TEST(strerror_not_threadsafe) { /* Just check that strerror really is not thread-safe. */ - log_info("strerror(%d) → %s", 200, strerror(200)); - log_info("strerror(%d) → %s", 201, strerror(201)); - log_info("strerror(%d) → %s", INT_MAX, strerror(INT_MAX)); + log_info("strerror(%d) → %s", 200, strerror(200)); /* NOLINT(bugprone-unsafe-functions) */ + log_info("strerror(%d) → %s", 201, strerror(201)); /* NOLINT(bugprone-unsafe-functions) */ + log_info("strerror(%d) → %s", INT_MAX, strerror(INT_MAX)); /* NOLINT(bugprone-unsafe-functions) */ + /* NOLINTNEXTLINE(bugprone-unsafe-functions) */ log_info("strerror(%d), strerror(%d) → %p, %p", 200, 201, strerror(200), strerror(201)); /* This call is not allowed, because the first returned string becomes invalid when diff --git a/src/test/test-exec-util.c b/src/test/test-exec-util.c index 7c3b872c10dfd..886d771fb5e01 100644 --- a/src/test/test-exec-util.c +++ b/src/test/test-exec-util.c @@ -231,7 +231,7 @@ static int gather_stdout_three(int fd, void *arg) { return 0; } -const gather_stdout_callback_t gather_stdouts[] = { +static const gather_stdout_callback_t gather_stdouts[] = { gather_stdout_one, gather_stdout_two, gather_stdout_three, diff --git a/src/test/test-execute.c b/src/test/test-execute.c index ebd0260892bfd..e14205bdf86a3 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -1633,7 +1633,7 @@ static int intro(void) { if (path_is_read_only_fs("/sys") > 0) return log_tests_skipped("/sys is mounted read-only"); - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0) return log_tests_skipped("libmount not available."); diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index d43c8735164d5..338bbc29d07bc 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -151,7 +151,7 @@ TEST(fd_move_above_stdio) { new_fd = fd_move_above_stdio(new_fd); assert_se(new_fd >= 3); - assert_se(dup(original_stdin) == 0); + assert_se(fcntl(original_stdin, F_DUPFD, 0) == 0); assert_se(close_nointr(original_stdin) != EBADF); assert_se(close_nointr(new_fd) != EBADF); } @@ -738,7 +738,7 @@ TEST(path_is_root_at) { test_path_is_root_at_one(true); } -TEST(fds_are_same_mount) { +TEST(fds_inode_and_mount_same) { _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF, fd3 = -EBADF, fd4 = -EBADF; fd1 = open("/sys", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW); @@ -749,11 +749,8 @@ TEST(fds_are_same_mount) { if (fd1 < 0 || fd2 < 0 || fd3 < 0 || fd4 < 0) return (void) log_tests_skipped_errno(errno, "Failed to open /sys or /proc or /"); - if (fds_are_same_mount(fd1, fd4) > 0 && fds_are_same_mount(fd2, fd4) > 0) - return (void) log_tests_skipped("Cannot test fds_are_same_mount() as /sys and /proc are not mounted"); - - assert_se(fds_are_same_mount(fd1, fd2) == 0); - assert_se(fds_are_same_mount(fd2, fd3) > 0); + assert_se(fds_inode_and_mount_same(fd1, fd2) == 0); + assert_se(fds_inode_and_mount_same(fd2, fd3) > 0); } TEST(fd_get_path) { diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index 38d92299467a7..a752b1c7cda23 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -9,6 +9,7 @@ #include "fd-util.h" #include "fileio.h" #include "fs-util.h" +#include "iovec-util.h" #include "memfd-util.h" #include "parse-util.h" #include "path-util.h" @@ -695,4 +696,55 @@ TEST(fdopen_independent) { f = safe_fclose(f); } +TEST(write_data_file_atomic_at) { + struct iovec a = IOVEC_MAKE_STRING("hallo"); + ASSERT_OK(write_data_file_atomic_at(AT_FDCWD, "/tmp/wdfa", &a, /* flags= */ 0)); + + _cleanup_(iovec_done) struct iovec ra = {}; + ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len)); + ASSERT_TRUE(iovec_equal(&a, &ra)); + ASSERT_OK_ERRNO(unlink("/tmp/wdfa")); + + ASSERT_OK(write_data_file_atomic_at(XAT_FDROOT, "tmp/wdfa", &a, /* flags= */ 0)); + iovec_done(&ra); + ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len)); + ASSERT_TRUE(iovec_equal(&a, &ra)); + ASSERT_OK_ERRNO(unlink("/tmp/wdfa")); + + ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, NULL, &a, /* flags= */ 0), EINVAL); + ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, "", &a, /* flags= */ 0), EINVAL); + ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, "/", &a, /* flags= */ 0), EISDIR); + ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, ".", &a, /* flags= */ 0), EISDIR); + ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, "/tmp/", &a, /* flags= */ 0), EISDIR); + + _cleanup_free_ char *cwd = NULL; + ASSERT_OK(safe_getcwd(&cwd)); + ASSERT_OK_ERRNO(chdir("/tmp")); + + ASSERT_OK(write_data_file_atomic_at(AT_FDCWD, "wdfa", &a, /* flags= */ 0)); + iovec_done(&ra); + ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len)); + ASSERT_TRUE(iovec_equal(&a, &ra)); + ASSERT_OK_ERRNO(unlink("/tmp/wdfa")); + + ASSERT_OK(write_data_file_atomic_at(XAT_FDROOT, "tmp/wdfa", &a, /* flags= */ 0)); + iovec_done(&ra); + ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len)); + ASSERT_TRUE(iovec_equal(&a, &ra)); + ASSERT_OK_ERRNO(unlink("/tmp/wdfa")); + + ASSERT_OK_ERRNO(chdir(cwd)); + + ASSERT_ERROR(write_data_file_atomic_at(XAT_FDROOT, "tmp/zzz/wdfa", &a, /* flags= */ 0), ENOENT); + ASSERT_OK(write_data_file_atomic_at(XAT_FDROOT, "tmp/zzz/wdfa", &a, WRITE_DATA_FILE_MKDIR_0755)); + iovec_done(&ra); + ASSERT_OK(read_full_file("/tmp/zzz/wdfa", (char**) &ra.iov_base, &ra.iov_len)); + ASSERT_TRUE(iovec_equal(&a, &ra)); + ASSERT_OK_ERRNO(unlink("/tmp/zzz/wdfa")); + + ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, "/tmp/zzz", &a, /* flags= */ 0), EEXIST); + + ASSERT_OK_ERRNO(rmdir("/tmp/zzz")); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-format-table.c b/src/test/test-format-table.c index 7b501d11e4217..9aae79e223987 100644 --- a/src/test/test-format-table.c +++ b/src/test/test-format-table.c @@ -395,13 +395,13 @@ TEST(json) { SD_JSON_BUILD_ARRAY( SD_JSON_BUILD_OBJECT( SD_JSON_BUILD_PAIR("foo_bar", JSON_BUILD_CONST_STRING("v1")), - SD_JSON_BUILD_PAIR("quux", SD_JSON_BUILD_UNSIGNED(4711)), - SD_JSON_BUILD_PAIR("zzz", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_UNSIGNED("quux", 4711), + SD_JSON_BUILD_PAIR_BOOLEAN("zzz", true), SD_JSON_BUILD_PAIR("asdf-custom", SD_JSON_BUILD_NULL)), SD_JSON_BUILD_OBJECT( SD_JSON_BUILD_PAIR("foo_bar", SD_JSON_BUILD_STRV(STRV_MAKE("a", "b", "c"))), SD_JSON_BUILD_PAIR("quux", SD_JSON_BUILD_NULL), - SD_JSON_BUILD_PAIR("zzz", SD_JSON_BUILD_UNSIGNED(0755)), + SD_JSON_BUILD_PAIR_UNSIGNED("zzz", 0755), SD_JSON_BUILD_PAIR("asdf-custom", SD_JSON_BUILD_NULL))))); ASSERT_TRUE(sd_json_variant_equal(v, w)); @@ -581,6 +581,72 @@ TEST(table) { "5min 5min \n"); } +TEST(tristate) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *w = NULL; + _cleanup_(table_unrefp) Table *t = NULL; + _cleanup_free_ char *formatted = NULL; + + ASSERT_NOT_NULL((t = table_new("name", "flag"))); + + ASSERT_OK(table_add_many(t, + TABLE_STRING, "neg", + TABLE_TRISTATE, -1)); + ASSERT_OK(table_add_many(t, + TABLE_STRING, "zero", + TABLE_TRISTATE, 0)); + ASSERT_OK(table_add_many(t, + TABLE_STRING, "pos", + TABLE_TRISTATE, 1)); + + ASSERT_OK(table_format(t, &formatted)); + printf("%s\n", formatted); + ASSERT_STREQ(formatted, + "NAME FLAG\n" + "neg \n" + "zero no\n" + "pos yes\n"); + formatted = mfree(formatted); + + /* Try a non-default ersatz string. */ + table_set_ersatz_string(t, TABLE_ERSATZ_DASH); + ASSERT_OK(table_format(t, &formatted)); + printf("%s\n", formatted); + ASSERT_STREQ(formatted, + "NAME FLAG\n" + "neg -\n" + "zero no\n" + "pos yes\n"); + formatted = mfree(formatted); + + /* Sorting: -1 < 0 < 1 */ + ASSERT_OK(table_set_sort(t, (size_t) 1, SIZE_MAX)); + ASSERT_OK(table_format(t, &formatted)); + printf("%s\n", formatted); + ASSERT_STREQ(formatted, + "NAME FLAG\n" + "neg -\n" + "zero no\n" + "pos yes\n"); + formatted = mfree(formatted); + + /* JSON: -1 → null, 0 → false, positive → true */ + ASSERT_OK(table_to_json(t, &v)); + + ASSERT_OK(sd_json_build(&w, + SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("name", JSON_BUILD_CONST_STRING("neg")), + SD_JSON_BUILD_PAIR("flag", SD_JSON_BUILD_NULL)), + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("name", JSON_BUILD_CONST_STRING("zero")), + SD_JSON_BUILD_PAIR_BOOLEAN("flag", false)), + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("name", JSON_BUILD_CONST_STRING("pos")), + SD_JSON_BUILD_PAIR_BOOLEAN("flag", true))))); + + ASSERT_TRUE(sd_json_variant_equal(v, w)); +} + TEST(signed_integers) { _cleanup_(table_unrefp) Table *t = NULL; _cleanup_free_ char *formatted = NULL; @@ -624,23 +690,23 @@ TEST(signed_integers) { ASSERT_OK(sd_json_build(&b, SD_JSON_BUILD_ARRAY( SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("int", SD_JSON_BUILD_INTEGER(-1)), - SD_JSON_BUILD_PAIR("int8", SD_JSON_BUILD_INTEGER(-1)), - SD_JSON_BUILD_PAIR("int16", SD_JSON_BUILD_INTEGER(-1)), - SD_JSON_BUILD_PAIR("int32", SD_JSON_BUILD_INTEGER(-1)), - SD_JSON_BUILD_PAIR("int64", SD_JSON_BUILD_INTEGER(-1))), + SD_JSON_BUILD_PAIR_INTEGER("int", -1), + SD_JSON_BUILD_PAIR_INTEGER("int8", -1), + SD_JSON_BUILD_PAIR_INTEGER("int16", -1), + SD_JSON_BUILD_PAIR_INTEGER("int32", -1), + SD_JSON_BUILD_PAIR_INTEGER("int64", -1)), SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("int", SD_JSON_BUILD_INTEGER(INT_MAX)), - SD_JSON_BUILD_PAIR("int8", SD_JSON_BUILD_INTEGER(INT8_MAX)), - SD_JSON_BUILD_PAIR("int16", SD_JSON_BUILD_INTEGER(INT16_MAX)), - SD_JSON_BUILD_PAIR("int32", SD_JSON_BUILD_INTEGER(INT32_MAX)), - SD_JSON_BUILD_PAIR("int64", SD_JSON_BUILD_INTEGER(INT64_MAX))), + SD_JSON_BUILD_PAIR_INTEGER("int", INT_MAX), + SD_JSON_BUILD_PAIR_INTEGER("int8", INT8_MAX), + SD_JSON_BUILD_PAIR_INTEGER("int16", INT16_MAX), + SD_JSON_BUILD_PAIR_INTEGER("int32", INT32_MAX), + SD_JSON_BUILD_PAIR_INTEGER("int64", INT64_MAX)), SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("int", SD_JSON_BUILD_INTEGER(INT_MIN)), - SD_JSON_BUILD_PAIR("int8", SD_JSON_BUILD_INTEGER(INT8_MIN)), - SD_JSON_BUILD_PAIR("int16", SD_JSON_BUILD_INTEGER(INT16_MIN)), - SD_JSON_BUILD_PAIR("int32", SD_JSON_BUILD_INTEGER(INT32_MIN)), - SD_JSON_BUILD_PAIR("int64", SD_JSON_BUILD_INTEGER(INT64_MIN)))))); + SD_JSON_BUILD_PAIR_INTEGER("int", INT_MIN), + SD_JSON_BUILD_PAIR_INTEGER("int8", INT8_MIN), + SD_JSON_BUILD_PAIR_INTEGER("int16", INT16_MIN), + SD_JSON_BUILD_PAIR_INTEGER("int32", INT32_MIN), + SD_JSON_BUILD_PAIR_INTEGER("int64", INT64_MIN))))); sd_json_variant_dump(b, SD_JSON_FORMAT_NEWLINE, stdout, NULL); ASSERT_TRUE(sd_json_variant_equal(a, b)); @@ -686,21 +752,21 @@ TEST(unsigned_integers) { ASSERT_OK(sd_json_build(&b, SD_JSON_BUILD_ARRAY( SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("uint", SD_JSON_BUILD_UNSIGNED(0)), - SD_JSON_BUILD_PAIR("uint8", SD_JSON_BUILD_UNSIGNED(0)), - SD_JSON_BUILD_PAIR("uint16", SD_JSON_BUILD_UNSIGNED(0)), - SD_JSON_BUILD_PAIR("uint32", SD_JSON_BUILD_UNSIGNED(0)), - SD_JSON_BUILD_PAIR("uhex32", SD_JSON_BUILD_UNSIGNED(0)), - SD_JSON_BUILD_PAIR("uint64", SD_JSON_BUILD_UNSIGNED(0)), - SD_JSON_BUILD_PAIR("uhex64", SD_JSON_BUILD_UNSIGNED(0))), + SD_JSON_BUILD_PAIR_UNSIGNED("uint", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("uint8", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("uint16", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("uint32", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("uhex32", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("uint64", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("uhex64", 0)), SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("uint", SD_JSON_BUILD_UNSIGNED(UINT_MAX)), - SD_JSON_BUILD_PAIR("uint8", SD_JSON_BUILD_UNSIGNED(UINT8_MAX)), - SD_JSON_BUILD_PAIR("uint16", SD_JSON_BUILD_UNSIGNED(UINT16_MAX)), - SD_JSON_BUILD_PAIR("uint32", SD_JSON_BUILD_UNSIGNED(UINT32_MAX)), - SD_JSON_BUILD_PAIR("uhex32", SD_JSON_BUILD_UNSIGNED(UINT32_MAX)), - SD_JSON_BUILD_PAIR("uint64", SD_JSON_BUILD_UNSIGNED(UINT64_MAX)), - SD_JSON_BUILD_PAIR("uhex64", SD_JSON_BUILD_UNSIGNED(UINT64_MAX)))))); + SD_JSON_BUILD_PAIR_UNSIGNED("uint", UINT_MAX), + SD_JSON_BUILD_PAIR_UNSIGNED("uint8", UINT8_MAX), + SD_JSON_BUILD_PAIR_UNSIGNED("uint16", UINT16_MAX), + SD_JSON_BUILD_PAIR_UNSIGNED("uint32", UINT32_MAX), + SD_JSON_BUILD_PAIR_UNSIGNED("uhex32", UINT32_MAX), + SD_JSON_BUILD_PAIR_UNSIGNED("uint64", UINT64_MAX), + SD_JSON_BUILD_PAIR_UNSIGNED("uhex64", UINT64_MAX))))); sd_json_variant_dump(b, SD_JSON_FORMAT_NEWLINE, stdout, NULL); ASSERT_TRUE(sd_json_variant_equal(a, b)); @@ -734,10 +800,10 @@ TEST(vertical) { ASSERT_OK(table_to_json(t, &a)); ASSERT_OK(sd_json_build(&b, SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("pfft_aa", SD_JSON_BUILD_STRING("foo")), - SD_JSON_BUILD_PAIR("dimpfelmoser", SD_JSON_BUILD_UNSIGNED(1024)), - SD_JSON_BUILD_PAIR("custom-quux", SD_JSON_BUILD_STRING("asdf")), - SD_JSON_BUILD_PAIR("lllllllllllo", SD_JSON_BUILD_STRING("jjjjjjjjjjjjjjjjj"))))); + SD_JSON_BUILD_PAIR_STRING("pfft_aa", "foo"), + SD_JSON_BUILD_PAIR_UNSIGNED("dimpfelmoser", 1024), + SD_JSON_BUILD_PAIR_STRING("custom-quux", "asdf"), + SD_JSON_BUILD_PAIR_STRING("lllllllllllo", "jjjjjjjjjjjjjjjjj")))); ASSERT_TRUE(sd_json_variant_equal(a, b)); } @@ -890,7 +956,7 @@ TEST(table_ansi) { "FOO BAR BAZ KKK\n" "hallo knuerzredgreen noansi thisisgrey\n"); - ASSERT_OK(table_print(table, /* f= */ NULL)); + ASSERT_OK(table_print(table)); _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL, *jj = NULL; diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index a56f8f92ada9a..23aa5a5815fd1 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -16,6 +16,7 @@ #include "process-util.h" #include "random-util.h" #include "rm-rf.h" +#include "socket-util.h" #include "stat-util.h" #include "string-util.h" #include "strv.h" @@ -740,6 +741,149 @@ TEST(xopenat_regular) { assert_se(unlink("/tmp/xopenat-regular-test") >= 0); } +TEST(xopenat_socket) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF; + + ASSERT_OK(tfd = mkdtemp_open(NULL, 0, &t)); + + /* Create a Unix domain socket via bind(). */ + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + ASSERT_OK(fd); + + const char *sockpath = strjoina(t, "/test.sock"); + union sockaddr_union sa = { .un.sun_family = AF_UNIX }; + strncpy(sa.un.sun_path, sockpath, sizeof(sa.un.sun_path) - 1); + ASSERT_OK_ERRNO(bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sockpath) + 1)); + fd = safe_close(fd); + + /* XO_SOCKET requires O_PATH. */ + fd = xopenat_full(tfd, "test.sock", O_PATH|O_CLOEXEC, XO_SOCKET, 0); + ASSERT_OK(fd); + fd = safe_close(fd); + + /* Reopen via empty path should also work. */ + fd = ASSERT_OK(xopenat_full(tfd, "test.sock", O_PATH|O_CLOEXEC, 0, 0)); + _cleanup_close_ int fd2 = xopenat_full(fd, NULL, O_PATH|O_CLOEXEC, XO_SOCKET, 0); + ASSERT_OK(fd2); + fd = safe_close(fd); + + /* Non-socket inodes must be rejected. */ + ASSERT_OK_ERRNO(mkdirat(tfd, "dir", 0755)); + ASSERT_ERROR(xopenat_full(tfd, "dir", O_PATH|O_CLOEXEC, XO_SOCKET, 0), EISDIR); + + fd = ASSERT_OK_ERRNO(openat(tfd, "reg", O_CREAT|O_CLOEXEC, 0600)); + fd = safe_close(fd); + ASSERT_ERROR(xopenat_full(tfd, "reg", O_PATH|O_CLOEXEC, XO_SOCKET, 0), ENOTSOCK); + + /* Reopen via empty path of a non-socket fd must also be rejected. */ + fd = ASSERT_OK(xopenat_full(tfd, "reg", O_PATH|O_CLOEXEC, 0, 0)); + ASSERT_ERROR(xopenat_full(fd, NULL, O_PATH|O_CLOEXEC, XO_SOCKET, 0), ENOTSOCK); + fd = safe_close(fd); + + fd = ASSERT_OK(xopenat_full(tfd, "dir", O_PATH|O_CLOEXEC, 0, 0)); + ASSERT_ERROR(xopenat_full(fd, NULL, O_PATH|O_CLOEXEC, XO_SOCKET, 0), EISDIR); + fd = safe_close(fd); +} + +TEST(xopenat_trigger_automount) { + _cleanup_close_ int fd = -EBADF; + + /* We can't easily set up an autofs mount in a test, but we can verify that + * XO_TRIGGER_AUTOMOUNT works on a regular path and produces the same inode as a + * plain O_PATH open. */ + fd = xopenat_full(AT_FDCWD, "/usr", O_PATH|O_CLOEXEC|O_DIRECTORY, XO_TRIGGER_AUTOMOUNT, 0); + ASSERT_OK(fd); + + _cleanup_close_ int fd2 = xopenat_full(AT_FDCWD, "/usr", O_PATH|O_CLOEXEC|O_DIRECTORY, 0, 0); + ASSERT_OK(fd2); + ASSERT_OK_POSITIVE(fd_inode_same(fd, fd2)); +} + +TEST(xopenat_auto_rw_ro) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF; + int fl; + + assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0); + + /* Regular writable file: XO_AUTO_RW_RO should end up in O_RDWR. */ + + fd = xopenat_full(tfd, "rw", O_CREAT|O_EXCL|O_CLOEXEC, XO_AUTO_RW_RO, 0644); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDWR); + fd = safe_close(fd); + + /* Same thing, but with XO_REGULAR set too. */ + + fd = xopenat_full(tfd, "rw2", O_CREAT|O_EXCL|O_CLOEXEC, XO_AUTO_RW_RO|XO_REGULAR, 0644); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDWR); + fd = safe_close(fd); + + /* Reopen via empty path on an O_PATH fd must also end up in O_RDWR. */ + + _cleanup_close_ int path_fd = xopenat_full(tfd, "rw", O_PATH|O_CLOEXEC, 0, 0); + assert_se(path_fd >= 0); + fd = xopenat_full(path_fd, "", O_CLOEXEC, XO_AUTO_RW_RO, 0); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDWR); + fd = safe_close(fd); + + /* Directories can only be opened read-only: XO_AUTO_RW_RO with O_DIRECTORY must fall back to O_RDONLY. */ + + fd = xopenat_full(tfd, "subdir", O_DIRECTORY|O_CREAT|O_CLOEXEC, XO_AUTO_RW_RO, 0755); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDONLY); + fd = safe_close(fd); + + /* Same for opening an existing directory. */ + + fd = xopenat_full(tfd, "subdir", O_DIRECTORY|O_CLOEXEC, XO_AUTO_RW_RO, 0); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDONLY); + fd = safe_close(fd); + + /* Fallback when the inode is not writable: create a file as read-only mode and verify that + * XO_AUTO_RW_RO falls back to O_RDONLY. Root bypasses mode bits via CAP_DAC_OVERRIDE, so skip + * this when running as root. */ + + if (geteuid() != 0) { + fd = openat(tfd, "ro", O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC, 0444); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(fchmodat(tfd, "ro", 0444, 0) >= 0); + + /* Plain case: no XO_REGULAR. */ + fd = xopenat_full(tfd, "ro", O_CLOEXEC, XO_AUTO_RW_RO, 0); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDONLY); + fd = safe_close(fd); + + /* With XO_REGULAR (exercises the pin-via-O_PATH + reopen path). */ + fd = xopenat_full(tfd, "ro", O_CLOEXEC, XO_AUTO_RW_RO|XO_REGULAR, 0); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDONLY); + fd = safe_close(fd); + + /* Also exercise the empty-path/fd-reopen branch. */ + _cleanup_close_ int ro_path_fd = xopenat_full(tfd, "ro", O_PATH|O_CLOEXEC, 0, 0); + assert_se(ro_path_fd >= 0); + fd = xopenat_full(ro_path_fd, "", O_CLOEXEC, XO_AUTO_RW_RO, 0); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDONLY); + fd = safe_close(fd); + } +} + TEST(xopenat_lock_full) { _cleanup_(rm_rf_physical_and_freep) char *t = NULL; _cleanup_close_ int tfd = -EBADF, fd = -EBADF; @@ -871,10 +1015,10 @@ TEST(xat_fdroot) { ASSERT_OK_POSITIVE(path_is_root_at(fd, ".")); ASSERT_OK_POSITIVE(path_is_root_at(fd, "/")); - ASSERT_OK_POSITIVE(fds_are_same_mount(fd, fd)); - ASSERT_OK_POSITIVE(fds_are_same_mount(XAT_FDROOT, XAT_FDROOT)); - ASSERT_OK_POSITIVE(fds_are_same_mount(fd, XAT_FDROOT)); - ASSERT_OK_POSITIVE(fds_are_same_mount(XAT_FDROOT, fd)); + ASSERT_OK_POSITIVE(fds_inode_and_mount_same(fd, fd)); + ASSERT_OK_POSITIVE(fds_inode_and_mount_same(XAT_FDROOT, XAT_FDROOT)); + ASSERT_OK_POSITIVE(fds_inode_and_mount_same(fd, XAT_FDROOT)); + ASSERT_OK_POSITIVE(fds_inode_and_mount_same(XAT_FDROOT, fd)); ASSERT_OK_POSITIVE(dir_fd_is_root(XAT_FDROOT)); ASSERT_OK_POSITIVE(dir_fd_is_root(fd)); diff --git a/src/test/test-getopt.c b/src/test/test-getopt.c index e17621af6a944..0ba1c678f241d 100644 --- a/src/test/test-getopt.c +++ b/src/test/test-getopt.c @@ -90,27 +90,27 @@ TEST(getopt_long) { test_getopt_long_one(STRV_MAKE("arg0", "--", "string1", - "string2", - "string3", + "--help", + "-h", "string4"), "hr:o::", options, NULL, STRV_MAKE("string1", - "string2", - "string3", + "--help", + "-h", "string4")); test_getopt_long_one(STRV_MAKE("arg0", "string1", "string2", "--", - "string3", + "--", "string4"), "hr:o::", options, NULL, STRV_MAKE("string1", "string2", - "string3", + "--", "string4")); test_getopt_long_one(STRV_MAKE("arg0", diff --git a/src/test/test-gpt.c b/src/test/test-gpt.c index 6772d46ef64bd..430f8e07fdd72 100644 --- a/src/test/test-gpt.c +++ b/src/test/test-gpt.c @@ -1,8 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "architecture.h" +#include "fd-util.h" #include "gpt.h" #include "log.h" +#include "memfd-util.h" +#include "memory-util.h" #include "pretty-print.h" #include "strv.h" #include "tests.h" @@ -49,19 +54,19 @@ TEST(verity_mappings) { PartitionDesignator q; q = partition_verity_hash_of(p); - assert_se(q < 0 || partition_verity_hash_to_data(q) == p); + ASSERT_TRUE(q < 0 || partition_verity_hash_to_data(q) == p); q = partition_verity_sig_of(p); - assert_se(q < 0 || partition_verity_sig_to_data(q) == p); + ASSERT_TRUE(q < 0 || partition_verity_sig_to_data(q) == p); q = partition_verity_hash_to_data(p); - assert_se(q < 0 || partition_verity_hash_of(q) == p); + ASSERT_TRUE(q < 0 || partition_verity_hash_of(q) == p); q = partition_verity_sig_to_data(p); - assert_se(q < 0 || partition_verity_sig_of(q) == p); + ASSERT_TRUE(q < 0 || partition_verity_sig_of(q) == p); q = partition_verity_to_data(p); - assert_se(q < 0 || partition_verity_hash_of(q) == p || partition_verity_sig_of(q) == p); + ASSERT_TRUE(q < 0 || partition_verity_hash_of(q) == p || partition_verity_sig_of(q) == p); } } @@ -94,7 +99,7 @@ TEST(override_architecture) { x = gpt_partition_type_override_architecture(x, ARCHITECTURE_ARM64); ASSERT_EQ(x.arch, y.arch); ASSERT_EQ(x.designator, y.designator); - assert_se(sd_id128_equal(x.uuid, y.uuid)); + ASSERT_EQ_ID128(x.uuid, y.uuid); ASSERT_STREQ(x.name, y.name); /* If the partition type does not have an architecture, nothing should change. */ @@ -105,8 +110,148 @@ TEST(override_architecture) { x = gpt_partition_type_override_architecture(x, ARCHITECTURE_ARM64); ASSERT_EQ(x.arch, y.arch); ASSERT_EQ(x.designator, y.designator); - assert_se(sd_id128_equal(x.uuid, y.uuid)); + ASSERT_EQ_ID128(x.uuid, y.uuid); ASSERT_STREQ(x.name, y.name); } +static void make_gpt(int fd, uint32_t sector_size, const GptPartitionEntry *part_entries, size_t n_entries) { + /* Zero-fill enough for header probing (gpt_probe reads 2*4096 = 8KB) */ + static const uint8_t zeros[2 * 4096] = {}; + ASSERT_OK_EQ_ERRNO(pwrite(fd, zeros, sizeof(zeros), 0), (ssize_t) sizeof(zeros)); + + GptHeader h = { + .signature = { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' }, + .revision = htole32(UINT32_C(0x00010000)), + .header_size = htole32(sizeof(GptHeader)), + .my_lba = htole64(1), + .partition_entry_lba = htole64(2), + .number_of_partition_entries = htole32(n_entries), + .size_of_partition_entry = htole32(sizeof(GptPartitionEntry)), + }; + ASSERT_OK_EQ_ERRNO(pwrite(fd, &h, sizeof(h), sector_size), (ssize_t) sizeof(h)); + + if (n_entries > 0) { + size_t entries_size = n_entries * sizeof(GptPartitionEntry); + ASSERT_OK_EQ_ERRNO(pwrite(fd, part_entries, entries_size, 2 * sector_size), (ssize_t) entries_size); + } +} + +TEST(gpt_probe_empty) { + _cleanup_close_ int fd = -EBADF; + + fd = ASSERT_OK(memfd_new("test-gpt-probe")); + ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 0); +} + +TEST(gpt_probe_too_short) { + _cleanup_close_ int fd = -EBADF; + static const uint8_t buf[4096] = {}; + + fd = ASSERT_OK(memfd_new("test-gpt-probe")); + ASSERT_OK_EQ_ERRNO(pwrite(fd, buf, sizeof(buf), 0), (ssize_t) sizeof(buf)); + ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 0); +} + +TEST(gpt_probe_no_signature) { + _cleanup_close_ int fd = -EBADF; + static const uint8_t buf[2 * 4096] = {}; + + fd = ASSERT_OK(memfd_new("test-gpt-probe")); + ASSERT_OK_EQ_ERRNO(pwrite(fd, buf, sizeof(buf), 0), (ssize_t) sizeof(buf)); + ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 0); +} + +TEST(gpt_probe_sector_512) { + _cleanup_close_ int fd = -EBADF; + + const GptPartitionEntry entries[2] = { + { + .unique_partition_guid = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 }, + .starting_lba = htole64(100), + .ending_lba = htole64(200), + }, + { + .unique_partition_guid = { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20 }, + .starting_lba = htole64(300), + .ending_lba = htole64(400), + }, + }; + + fd = ASSERT_OK(memfd_new("test-gpt-probe")); + make_gpt(fd, 512, entries, 2); + + /* Sector size detection only */ + ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 512); + + /* Header return */ + GptHeader h; + ASSERT_OK_EQ(gpt_probe(fd, &h, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 512); + ASSERT_EQ(le32toh(h.number_of_partition_entries), 2u); + ASSERT_EQ(le32toh(h.size_of_partition_entry), (uint32_t) sizeof(GptPartitionEntry)); + + /* Full probe with entries */ + _cleanup_free_ void *ret_entries = NULL; + uint32_t n, sz; + ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, &ret_entries, &n, &sz), (ssize_t) 512); + ASSERT_EQ(n, 2u); + ASSERT_EQ(sz, (uint32_t) sizeof(GptPartitionEntry)); + ASSERT_NOT_NULL(ret_entries); + + GptPartitionEntry *e = ret_entries; + ASSERT_EQ(memcmp_nn(e[0].unique_partition_guid, sizeof(e[0].unique_partition_guid), entries[0].unique_partition_guid, sizeof(entries[0].unique_partition_guid)), 0); + ASSERT_EQ(memcmp_nn(e[1].unique_partition_guid, sizeof(e[1].unique_partition_guid), entries[1].unique_partition_guid, sizeof(entries[1].unique_partition_guid)), 0); + ASSERT_EQ(le64toh(e[0].starting_lba), UINT64_C(100)); + ASSERT_EQ(le64toh(e[1].starting_lba), UINT64_C(300)); +} + +TEST(gpt_probe_sector_4096) { + _cleanup_close_ int fd = -EBADF; + + const GptPartitionEntry entry = { + .unique_partition_guid = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, + 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99 }, + .starting_lba = htole64(50), + .ending_lba = htole64(100), + }; + + fd = ASSERT_OK(memfd_new("test-gpt-probe")); + make_gpt(fd, 4096, &entry, 1); + + ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 4096); + + _cleanup_free_ void *ret_entries = NULL; + uint32_t n, sz; + ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, &ret_entries, &n, &sz), (ssize_t) 4096); + ASSERT_EQ(n, 1u); + + GptPartitionEntry *e = ret_entries; + ASSERT_EQ(memcmp_nn(e[0].unique_partition_guid, sizeof(e[0].unique_partition_guid), entry.unique_partition_guid, sizeof(entry.unique_partition_guid)), 0); + ASSERT_EQ(le64toh(e[0].starting_lba), UINT64_C(50)); +} + +TEST(gpt_probe_ambiguous) { + _cleanup_close_ int fd = -EBADF; + + const GptPartitionEntry entry = {}; + + fd = ASSERT_OK(memfd_new("test-gpt-probe")); + make_gpt(fd, 512, &entry, 1); + + /* Place a second valid header at offset 4096 */ + GptHeader h2 = { + .signature = { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' }, + .revision = htole32(UINT32_C(0x00010000)), + .header_size = htole32(sizeof(GptHeader)), + .my_lba = htole64(1), + .partition_entry_lba = htole64(2), + .number_of_partition_entries = htole32(1), + .size_of_partition_entry = htole32(sizeof(GptPartitionEntry)), + }; + ASSERT_OK_EQ_ERRNO(pwrite(fd, &h2, sizeof(h2), 4096), (ssize_t) sizeof(h2)); + + ASSERT_ERROR(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), ENOTUNIQ); +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-hashmap-plain.c b/src/test/test-hashmap-plain.c index 5cc84e884d92f..da5866ce4f60c 100644 --- a/src/test/test-hashmap-plain.c +++ b/src/test/test-hashmap-plain.c @@ -264,7 +264,7 @@ TEST(hashmap_remove1) { r = hashmap_get(m, "key 2"); ASSERT_STREQ(r, "val 2"); - assert_se(!hashmap_get(m, "key 1")); + ASSERT_FALSE(hashmap_contains(m, "key 1")); } TEST(hashmap_remove2) { @@ -295,7 +295,7 @@ TEST(hashmap_remove2) { r = hashmap_get(m, key2); ASSERT_STREQ(r, val2); - assert_se(!hashmap_get(m, key1)); + ASSERT_FALSE(hashmap_contains(m, key1)); } TEST(hashmap_remove_value) { @@ -322,14 +322,14 @@ TEST(hashmap_remove_value) { r = hashmap_get(m, "key 2"); ASSERT_STREQ(r, "val 2"); - assert_se(!hashmap_get(m, "key 1")); + ASSERT_FALSE(hashmap_contains(m, "key 1")); r = hashmap_remove_value(m, "key 2", val1); ASSERT_NULL(r); r = hashmap_get(m, "key 2"); ASSERT_STREQ(r, "val 2"); - assert_se(!hashmap_get(m, "key 1")); + ASSERT_FALSE(hashmap_contains(m, "key 1")); } TEST(hashmap_remove_and_put) { @@ -354,7 +354,7 @@ TEST(hashmap_remove_and_put) { r = hashmap_get(m, "key 2"); ASSERT_STREQ(r, "val 2"); - assert_se(!hashmap_get(m, "key 1")); + ASSERT_FALSE(hashmap_contains(m, "key 1")); valid = hashmap_put(m, "key 3", (void*) (const char *) "val 3"); assert_se(valid == 1); @@ -388,7 +388,7 @@ TEST(hashmap_remove_and_replace) { r = hashmap_get(m, key2); assert_se(r == key2); - assert_se(!hashmap_get(m, key1)); + ASSERT_FALSE(hashmap_contains(m, key1)); valid = hashmap_put(m, key3, key3); assert_se(valid == 1); @@ -396,7 +396,7 @@ TEST(hashmap_remove_and_replace) { assert_se(valid == 0); r = hashmap_get(m, key2); assert_se(r == key2); - assert_se(!hashmap_get(m, key3)); + ASSERT_FALSE(hashmap_contains(m, key3)); /* Repeat this test several times to increase the chance of hitting * the less likely case in hashmap_remove_and_replace where it @@ -410,7 +410,7 @@ TEST(hashmap_remove_and_replace) { UINT_TO_PTR(10*i + 2), UINT_TO_PTR(10*i + 2)); assert_se(valid == 0); - assert_se(!hashmap_get(m, UINT_TO_PTR(10*i + 1))); + ASSERT_FALSE(hashmap_contains(m, UINT_TO_PTR(10*i + 1))); for (j = 2; j < 7; j++) { r = hashmap_get(m, UINT_TO_PTR(10*i + j)); assert_se(r == UINT_TO_PTR(10*i + j)); @@ -914,10 +914,10 @@ TEST(path_hashmap) { assert_se(hashmap_get(h, "/.///./foox//.//") == INT_TO_PTR(4)); assert_se(hashmap_get(h, "/foox/") == INT_TO_PTR(4)); assert_se(hashmap_get(h, "/foox") == INT_TO_PTR(4)); - assert_se(!hashmap_get(h, "foox")); + ASSERT_FALSE(hashmap_contains(h, "foox")); assert_se(hashmap_get(h, "foo/bar/quux") == INT_TO_PTR(6)); assert_se(hashmap_get(h, "foo////bar////quux/////") == INT_TO_PTR(6)); - assert_se(!hashmap_get(h, "/foo////bar////quux/////")); + ASSERT_FALSE(hashmap_contains(h, "/foo////bar////quux/////")); assert_se(hashmap_get(h, "foo././//ba.r////.quux///.//.") == INT_TO_PTR(9)); } @@ -950,14 +950,14 @@ TEST(string_strv_hashmap) { ASSERT_TRUE(strv_equal(s, STRV_MAKE("BAR"))); string_strv_hashmap_remove(m, "foo", "BAR"); - ASSERT_NULL(hashmap_get(m, "foo")); + ASSERT_FALSE(hashmap_contains(m, "foo")); string_strv_hashmap_remove(m, "xxx", "BAR"); ASSERT_NOT_NULL((s = hashmap_get(m, "xxx"))); ASSERT_TRUE(strv_equal(s, STRV_MAKE("bar"))); string_strv_hashmap_remove(m, "xxx", "bar"); - ASSERT_NULL(hashmap_get(m, "xxx")); + ASSERT_FALSE(hashmap_contains(m, "xxx")); ASSERT_TRUE(hashmap_isempty(m)); } diff --git a/src/test/test-hashmap.c b/src/test/test-hashmap.c index 7f25ad101141c..4029d4a478fd2 100644 --- a/src/test/test-hashmap.c +++ b/src/test/test-hashmap.c @@ -3,14 +3,17 @@ #include "hashmap.h" #include "tests.h" +// NOLINTNEXTLINE(misc-use-internal-linkage) unsigned custom_counter = 0; static void custom_destruct(void* p) { custom_counter--; free(p); } +// NOLINTBEGIN(misc-use-internal-linkage) DEFINE_HASH_OPS_FULL(boring_hash_ops, char, string_hash_func, string_compare_func, free, char, free); DEFINE_HASH_OPS_FULL(custom_hash_ops, char, string_hash_func, string_compare_func, custom_destruct, char, custom_destruct); +// NOLINTEND(misc-use-internal-linkage) TEST(ordered_hashmap_next) { _cleanup_ordered_hashmap_free_ OrderedHashmap *m = NULL; @@ -154,6 +157,7 @@ TEST(hashmap_put_strdup_null) { * they don't apply to ordered hashmaps. */ /* This variable allows us to assert that the tests from different compilation units were actually run. */ +// NOLINTNEXTLINE(misc-use-internal-linkage) int n_extern_tests_run = 0; static int intro(void) { diff --git a/src/test/test-iovec-util.c b/src/test/test-iovec-util.c index 217ee8cf5d9ec..bd73be1ea76e6 100644 --- a/src/test/test-iovec-util.c +++ b/src/test/test-iovec-util.c @@ -1,8 +1,97 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "iovec-util.h" +#include "iovec-wrapper.h" +#include "memory-util.h" #include "tests.h" +TEST(iovec_shift) { + const struct iovec iov = CONST_IOVEC_MAKE_STRING("54321"); + + ASSERT_EQ(iovec_memcmp(&IOVEC_SHIFT(&iov, 0), &CONST_IOVEC_MAKE_STRING("54321")), 0); + ASSERT_EQ(iovec_memcmp(&IOVEC_SHIFT(&iov, 1), &CONST_IOVEC_MAKE_STRING("4321")), 0); + ASSERT_EQ(iovec_memcmp(&IOVEC_SHIFT(&iov, 2), &CONST_IOVEC_MAKE_STRING("321")), 0); + ASSERT_EQ(iovec_memcmp(&IOVEC_SHIFT(&iov, 3), &CONST_IOVEC_MAKE_STRING("21")), 0); + ASSERT_EQ(iovec_memcmp(&IOVEC_SHIFT(&iov, 4), &CONST_IOVEC_MAKE_STRING("1")), 0); + ASSERT_FALSE(iovec_is_set(&IOVEC_SHIFT(&iov, 5))); + ASSERT_FALSE(iovec_is_set(&IOVEC_SHIFT(&iov, 6))); + ASSERT_FALSE(iovec_is_set(&IOVEC_SHIFT(&iov, 7))); + + const struct iovec empty = {}; + ASSERT_FALSE(iovec_is_set(&IOVEC_SHIFT(&empty, 0))); + ASSERT_FALSE(iovec_is_set(&IOVEC_SHIFT(&empty, 1))); +} + +TEST(iovec_inc) { + struct iovec iov = IOVEC_MAKE_STRING("54321"); + + ASSERT_EQ(iovec_memcmp(iovec_inc(&iov, 0), &CONST_IOVEC_MAKE_STRING("54321")), 0); + ASSERT_EQ(iovec_memcmp(iovec_inc(&iov, 1), &CONST_IOVEC_MAKE_STRING("4321")), 0); + ASSERT_EQ(iovec_memcmp(iovec_inc(&iov, 1), &CONST_IOVEC_MAKE_STRING("321")), 0); + ASSERT_EQ(iovec_memcmp(iovec_inc(&iov, 1), &CONST_IOVEC_MAKE_STRING("21")), 0); + ASSERT_EQ(iovec_memcmp(iovec_inc(&iov, 1), &CONST_IOVEC_MAKE_STRING("1")), 0); + ASSERT_FALSE(iovec_is_set(iovec_inc(&iov, 1))); + ASSERT_FALSE(iovec_is_set(iovec_inc(&iov, 1))); + ASSERT_FALSE(iovec_is_set(iovec_inc(&iov, 1))); + + struct iovec empty = {}; + ASSERT_FALSE(iovec_is_set(iovec_inc(&empty, 0))); + ASSERT_FALSE(iovec_is_set(iovec_inc(&empty, 1))); +} + +TEST(iovec_inc_many) { + ASSERT_TRUE(iovec_inc_many(NULL, 0, 0)); + ASSERT_TRUE(iovec_inc_many(&(struct iovec) {}, 0, 0)); + ASSERT_TRUE(iovec_inc_many(&(struct iovec) {}, 1, 0)); + + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("aaa"))); + ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("bbb"))); + ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("ccc"))); + + ASSERT_FALSE(iovec_inc_many(iovw.iovec, iovw.count, 0)); + ASSERT_TRUE(iovec_equal(&iovw.iovec[0], &IOVEC_MAKE_STRING("aaa"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[1], &IOVEC_MAKE_STRING("bbb"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc"))); + + ASSERT_FALSE(iovec_inc_many(iovw.iovec, iovw.count, 1)); + ASSERT_TRUE(iovec_equal(&iovw.iovec[0], &IOVEC_MAKE_STRING("aa"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[1], &IOVEC_MAKE_STRING("bbb"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc"))); + + ASSERT_FALSE(iovec_inc_many(iovw.iovec, iovw.count, 3)); + ASSERT_FALSE(iovec_is_set(&iovw.iovec[0])); + ASSERT_NULL(iovw.iovec[0].iov_base); + ASSERT_EQ(iovw.iovec[0].iov_len, 0u); + ASSERT_TRUE(iovec_equal(&iovw.iovec[1], &IOVEC_MAKE_STRING("bb"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc"))); + + ASSERT_FALSE(iovec_inc_many(iovw.iovec, iovw.count, 4)); + ASSERT_NULL(iovw.iovec[0].iov_base); + ASSERT_EQ(iovw.iovec[0].iov_len, 0u); + ASSERT_NULL(iovw.iovec[1].iov_base); + ASSERT_EQ(iovw.iovec[1].iov_len, 0u); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &IOVEC_MAKE_STRING("c"))); + + ASSERT_TRUE(iovec_inc_many(iovw.iovec, iovw.count, 1)); + ASSERT_NULL(iovw.iovec[0].iov_base); + ASSERT_EQ(iovw.iovec[0].iov_len, 0u); + ASSERT_NULL(iovw.iovec[1].iov_base); + ASSERT_EQ(iovw.iovec[1].iov_len, 0u); + ASSERT_NULL(iovw.iovec[2].iov_base); + ASSERT_EQ(iovw.iovec[2].iov_len, 0u); + + ASSERT_TRUE(iovec_inc_many(iovw.iovec, iovw.count, 0)); + ASSERT_NULL(iovw.iovec[0].iov_base); + ASSERT_EQ(iovw.iovec[0].iov_len, 0u); + ASSERT_NULL(iovw.iovec[1].iov_base); + ASSERT_EQ(iovw.iovec[1].iov_len, 0u); + ASSERT_NULL(iovw.iovec[2].iov_base); + ASSERT_EQ(iovw.iovec[2].iov_len, 0u); + + ASSERT_SIGNAL(iovec_inc_many(iovw.iovec, iovw.count, 1), SIGABRT); +} + TEST(iovec_memcmp) { struct iovec iov1 = CONST_IOVEC_MAKE_STRING("abcdef"), iov2 = IOVEC_MAKE_STRING("bcdefg"), empty = {}; @@ -67,4 +156,11 @@ TEST(iovec_append) { assert_se(iovec_memcmp(&iov, &IOVEC_MAKE_STRING("waldoquuxp")) == 0); } +TEST(iovec_make_byte) { + struct iovec x = IOVEC_MAKE_BYTE('x'); + + ASSERT_EQ(x.iov_len, 1U); + ASSERT_EQ(memcmp_nn(x.iov_base, x.iov_len, "x", 1), 0); +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-iovec-wrapper.c b/src/test/test-iovec-wrapper.c new file mode 100644 index 0000000000000..791a041ab8d6f --- /dev/null +++ b/src/test/test-iovec-wrapper.c @@ -0,0 +1,435 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "tests.h" + +TEST(iovw_compare) { + _cleanup_(iovw_done) struct iovec_wrapper a1 = {}, a2 = {}, b = {}, c = {}, d = {}, e = {}; + + ASSERT_OK(iovw_put(&a1, (char*) "foo", 3)); + ASSERT_OK(iovw_put(&a1, (char*) "aaaaa", 5)); + + ASSERT_OK(iovw_put(&a2, (char*) "foo", 3)); + ASSERT_OK(iovw_put(&a2, (char*) "aaaaa", 5)); + + ASSERT_OK(iovw_put(&b, (char*) "foo", 3)); + ASSERT_OK(iovw_put(&b, (char*) "bbbbb", 5)); + + ASSERT_OK(iovw_put(&c, (char*) "foo", 3)); + + ASSERT_OK(iovw_put(&d, (char*) "fooaa", 5)); + ASSERT_OK(iovw_put(&d, (char*) "aaa", 3)); + + ASSERT_EQ(iovw_compare(&a1, &a1), 0); + ASSERT_EQ(iovw_compare(&a1, &a2), 0); + ASSERT_EQ(iovw_compare(&a2, &a1), 0); + ASSERT_LT(iovw_compare(&a1, &b), 0); + ASSERT_GT(iovw_compare(&b, &a1), 0); + ASSERT_EQ(iovw_compare(&b, &b), 0); + ASSERT_GT(iovw_compare(&a1, &c), 0); + ASSERT_LT(iovw_compare(&c, &a1), 0); + ASSERT_EQ(iovw_compare(&c, &c), 0); + ASSERT_LT(iovw_compare(&a1, &d), 0); + ASSERT_GT(iovw_compare(&d, &a1), 0); + ASSERT_EQ(iovw_compare(&d, &d), 0); + ASSERT_GT(iovw_compare(&a1, &e), 0); + ASSERT_LT(iovw_compare(&e, &a1), 0); + ASSERT_EQ(iovw_compare(&e, &e), 0); + ASSERT_GT(iovw_compare(&a1, NULL), 0); + ASSERT_LT(iovw_compare(NULL, &a1), 0); + ASSERT_EQ(iovw_compare(NULL, NULL), 0); + + ASSERT_TRUE(iovw_equal(&a1, &a1)); + ASSERT_TRUE(iovw_equal(&a1, &a2)); + ASSERT_TRUE(iovw_equal(&a2, &a1)); + ASSERT_FALSE(iovw_equal(&a1, &b)); + ASSERT_FALSE(iovw_equal(&b, &a1)); + ASSERT_TRUE(iovw_equal(&b, &b)); + ASSERT_FALSE(iovw_equal(&a1, &c)); + ASSERT_FALSE(iovw_equal(&c, &a1)); + ASSERT_TRUE(iovw_equal(&c, &c)); + ASSERT_FALSE(iovw_equal(&a1, &d)); + ASSERT_FALSE(iovw_equal(&d, &a1)); + ASSERT_TRUE(iovw_equal(&d, &d)); + ASSERT_FALSE(iovw_equal(&a1, &e)); + ASSERT_FALSE(iovw_equal(&e, &a1)); + ASSERT_TRUE(iovw_equal(&e, &e)); + ASSERT_FALSE(iovw_equal(&a1, NULL)); + ASSERT_FALSE(iovw_equal(NULL, &a1)); + ASSERT_TRUE(iovw_equal(NULL, NULL)); +} + +TEST(iovw_put) { + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + + /* Zero-length insertions are no-ops and do not touch the data pointer */ + ASSERT_OK_ZERO(iovw_put(&iovw, NULL, 0)); + ASSERT_OK_ZERO(iovw_put(&iovw, (char*) "foo", 0)); + ASSERT_EQ(iovw.count, 0U); + + ASSERT_OK(iovw_put(&iovw, (char*) "foo", 3)); + ASSERT_OK(iovw_put(&iovw, (char*) "barbar", 6)); + ASSERT_OK(iovw_put(&iovw, (char*) "q", 1)); + ASSERT_EQ(iovw.count, 3U); + + ASSERT_EQ(iovw.iovec[0].iov_len, 3U); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "foo", 3), 0); + ASSERT_EQ(iovw.iovec[1].iov_len, 6U); + ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, "barbar", 6), 0); + ASSERT_EQ(iovw.iovec[2].iov_len, 1U); + ASSERT_EQ(memcmp(iovw.iovec[2].iov_base, "q", 1), 0); +} + +TEST(iovw_put_iov) { + /* iovw_put_iov() does not copy the input, hence do not use iovw_done_free */ + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + + /* Appending an empty/NULL source is a no-op */ + ASSERT_OK_ZERO(iovw_put_iov(&iovw, NULL)); + ASSERT_OK_ZERO(iovw_put_iov(&iovw, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 0U); + + ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("aaa"))); + ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("bbb"))); + ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("ccc"))); + ASSERT_EQ(iovw.count, 3U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[0], &IOVEC_MAKE_STRING("aaa"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[1], &IOVEC_MAKE_STRING("bbb"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc"))); +} + +TEST(iovw_put_iovw) { + _cleanup_(iovw_done) struct iovec_wrapper target = {}, source = {}; + + /* Appending an empty/NULL source is a no-op */ + ASSERT_OK_ZERO(iovw_put_iovw(&target, NULL)); + ASSERT_OK_ZERO(iovw_put_iovw(&target, &source)); + ASSERT_EQ(target.count, 0U); + + ASSERT_OK(iovw_put_iov(&source, &IOVEC_MAKE_STRING("aaa"))); + ASSERT_OK(iovw_put_iov(&source, &IOVEC_MAKE_STRING("bbb"))); + ASSERT_OK(iovw_put_iov(&source, &IOVEC_MAKE_STRING("ccc"))); + ASSERT_EQ(source.count, 3U); + + /* Pre-seed target with one entry to check that append adds on top rather than replacing */ + ASSERT_OK(iovw_put_iov(&target, &IOVEC_MAKE_STRING("xxx"))); + ASSERT_OK(iovw_put_iov(&target, &IOVEC_MAKE_STRING("yyy"))); + ASSERT_OK(iovw_put_iov(&target, &IOVEC_MAKE_STRING("zzz"))); + ASSERT_EQ(target.count, 3U); + + ASSERT_OK(iovw_put_iovw(&target, &source)); + ASSERT_EQ(target.count, 6U); + ASSERT_TRUE(iovec_equal(&target.iovec[0], &IOVEC_MAKE_STRING("xxx"))); + ASSERT_TRUE(iovec_equal(&target.iovec[1], &IOVEC_MAKE_STRING("yyy"))); + ASSERT_TRUE(iovec_equal(&target.iovec[2], &IOVEC_MAKE_STRING("zzz"))); + ASSERT_TRUE(iovec_equal(&target.iovec[3], &IOVEC_MAKE_STRING("aaa"))); + ASSERT_TRUE(iovec_equal(&target.iovec[4], &IOVEC_MAKE_STRING("bbb"))); + ASSERT_TRUE(iovec_equal(&target.iovec[5], &IOVEC_MAKE_STRING("ccc"))); + + /* iovw_put_iovw() does not copy data, hence the pointers must be equal */ + ASSERT_PTR_EQ(target.iovec[3].iov_base, source.iovec[0].iov_base); + ASSERT_PTR_EQ(target.iovec[4].iov_base, source.iovec[1].iov_base); + ASSERT_PTR_EQ(target.iovec[5].iov_base, source.iovec[2].iov_base); + + /* Source is unchanged */ + ASSERT_EQ(source.count, 3U); + ASSERT_TRUE(iovec_equal(&source.iovec[0], &IOVEC_MAKE_STRING("aaa"))); + ASSERT_TRUE(iovec_equal(&source.iovec[1], &IOVEC_MAKE_STRING("bbb"))); + ASSERT_TRUE(iovec_equal(&source.iovec[2], &IOVEC_MAKE_STRING("ccc"))); + + /* Cannot pass the same objects */ + ASSERT_ERROR(iovw_put_iovw(&target, &target), EINVAL); +} + +TEST(iovw_extend) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + /* Appending an empty/NULL source is a no-op */ + ASSERT_OK_ZERO(iovw_extend(&iovw, NULL, 0)); + ASSERT_OK_ZERO(iovw_extend(&iovw, "foo", 0)); + ASSERT_EQ(iovw.count, 0U); + + /* iovw_extend() copies the data; the wrapper owns the copies. */ + char buf[4] = { 'o', 'n', 'e', '\0' }; + ASSERT_OK(iovw_extend(&iovw, buf, 3)); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, 3U); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "one", 3), 0); + + /* Insert with a NUL */ + ASSERT_OK(iovw_extend(&iovw, buf, 4)); + ASSERT_EQ(iovw.count, 2U); + ASSERT_EQ(iovw.iovec[1].iov_len, 4U); + ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, "one\0", 4), 0); + + /* Mutating the caller's buffer does not affect what's stored */ + memset(buf, 'X', sizeof buf); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "one", 3), 0); +} + +TEST(iovw_extend_iov) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + /* Appending an empty/NULL source is a no-op */ + ASSERT_OK_ZERO(iovw_extend_iov(&iovw, NULL)); + ASSERT_OK_ZERO(iovw_extend_iov(&iovw, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 0U); + + ASSERT_OK(iovw_extend_iov(&iovw, &IOVEC_MAKE_STRING("aaa"))); + ASSERT_OK(iovw_extend_iov(&iovw, &IOVEC_MAKE_STRING("bbb"))); + ASSERT_OK(iovw_extend_iov(&iovw, &IOVEC_MAKE_STRING("ccc"))); + ASSERT_EQ(iovw.count, 3U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[0], &IOVEC_MAKE_STRING("aaa"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[1], &IOVEC_MAKE_STRING("bbb"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc"))); +} + +TEST(iovw_extend_iovw) { + _cleanup_(iovw_done_free) struct iovec_wrapper target = {}; + _cleanup_(iovw_done) struct iovec_wrapper source = {}; + + /* Appending an empty/NULL source is a no-op */ + ASSERT_OK_ZERO(iovw_extend_iovw(&target, NULL)); + ASSERT_OK_ZERO(iovw_extend_iovw(&target, &source)); + ASSERT_EQ(target.count, 0U); + + ASSERT_OK(iovw_put(&source, (char*) "one", 3)); + ASSERT_OK(iovw_put(&source, (char*) "twotwo", 6)); + ASSERT_EQ(source.count, 2U); + + /* Pre-seed target with one entry to check that append adds on top rather than replacing */ + char *seed = strdup("zero"); + ASSERT_NOT_NULL(seed); + ASSERT_OK(iovw_put(&target, seed, strlen(seed))); + + ASSERT_OK(iovw_extend_iovw(&target, &source)); + ASSERT_EQ(target.count, 3U); + + /* Appended entries must be fresh copies, not aliases of the source entries */ + ASSERT_TRUE(target.iovec[1].iov_base != source.iovec[0].iov_base); + ASSERT_TRUE(target.iovec[2].iov_base != source.iovec[1].iov_base); + + ASSERT_EQ(target.iovec[1].iov_len, 3U); + ASSERT_EQ(memcmp(target.iovec[1].iov_base, "one", 3), 0); + ASSERT_EQ(target.iovec[2].iov_len, 6U); + ASSERT_EQ(memcmp(target.iovec[2].iov_base, "twotwo", 6), 0); + + /* Source is unchanged */ + ASSERT_EQ(source.count, 2U); + + /* Cannot pass the same objects */ + ASSERT_ERROR(iovw_extend_iovw(&target, &target), EINVAL); +} + +TEST(iovw_consume) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + char *p = strdup("consumed"); + ASSERT_NOT_NULL(p); + ASSERT_OK(iovw_consume(&iovw, p, strlen(p))); + ASSERT_EQ(iovw.count, 1U); + /* iovw_consume moves ownership in place, no copy */ + ASSERT_PTR_EQ(iovw.iovec[0].iov_base, p); + + /* Zero-length: iovw_put returns 0 without adding anything. Even in that case, iovw_consume() frees + * the payload. Confirm by strdup'ing something to verify that when running with sanitizer/valgrind. */ + char *q = ASSERT_NOT_NULL(strdup("")); + ASSERT_OK_ZERO(iovw_consume(&iovw, q, 0)); + ASSERT_EQ(iovw.count, 1U); +} + +TEST(iovw_consume_iov) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + ASSERT_OK_ZERO(iovw_consume_iov(&iovw, NULL)); + ASSERT_EQ(iovw.count, 0U); + + ASSERT_OK_ZERO(iovw_consume_iov(&iovw, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 0U); + + struct iovec iov = { + .iov_base = ASSERT_NOT_NULL(strdup("consumed")), + .iov_len = strlen("consumed"), + }; + ASSERT_OK(iovw_consume_iov(&iovw, &iov)); + ASSERT_EQ(iovw.count, 1U); + /* iovw_consume_iov takes the ownership of the buffer, and emptifies the iovec. */ + ASSERT_NULL(iov.iov_base); + ASSERT_EQ(iov.iov_len, 0U); + + iov = (struct iovec) { + .iov_base = ASSERT_NOT_NULL(strdup("")), + .iov_len = 0, + }; + ASSERT_OK_ZERO(iovw_consume_iov(&iovw, &iov)); + ASSERT_EQ(iovw.count, 1U); + /* zero length iovec is also freed */ + ASSERT_NULL(iov.iov_base); + ASSERT_EQ(iov.iov_len, 0U); +} + +TEST(iovw_isempty) { + ASSERT_TRUE(iovw_isempty(NULL)); + + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + ASSERT_TRUE(iovw_isempty(&iovw)); + + ASSERT_OK(iovw_put(&iovw, (char*) "x", 1)); + ASSERT_FALSE(iovw_isempty(&iovw)); +} + +TEST(iovw_put_string_field) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + ASSERT_OK(iovw_put_string_field(&iovw, "FOO=", "bar")); + ASSERT_OK(iovw_put_string_field(&iovw, "BAZ=", "quux")); + ASSERT_EQ(iovw.count, 2U); + + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("FOO=bar")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "FOO=bar", strlen("FOO=bar")), 0); + ASSERT_EQ(iovw.iovec[1].iov_len, strlen("BAZ=quux")); + ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, "BAZ=quux", strlen("BAZ=quux")), 0); + + /* Non-replacing put: a second FOO= just appends rather than replacing */ + ASSERT_OK(iovw_put_string_field(&iovw, "FOO=", "second")); + ASSERT_EQ(iovw.count, 3U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("FOO=bar")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "FOO=bar", strlen("FOO=bar")), 0); + ASSERT_EQ(iovw.iovec[2].iov_len, strlen("FOO=second")); + ASSERT_EQ(memcmp(iovw.iovec[2].iov_base, "FOO=second", strlen("FOO=second")), 0); +} + +TEST(iovw_replace_string_field) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + /* If the field does not exist yet, replace acts like put */ + ASSERT_OK(iovw_replace_string_field(&iovw, "A=", "1")); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("A=1")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "A=1", strlen("A=1")), 0); + + /* Replacing an existing field updates it in place */ + ASSERT_OK(iovw_replace_string_field(&iovw, "A=", "twentytwo")); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("A=twentytwo")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "A=twentytwo", strlen("A=twentytwo")), 0); + + /* Distinct field still appends */ + ASSERT_OK(iovw_replace_string_field(&iovw, "B=", "x")); + ASSERT_EQ(iovw.count, 2U); +} + +TEST(iovw_put_string_fieldf) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + ASSERT_OK(iovw_put_string_fieldf(&iovw, "N=", "%d-%s", 42, "answer")); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("N=42-answer")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "N=42-answer", strlen("N=42-answer")), 0); + + /* Replacing variant */ + ASSERT_OK(iovw_replace_string_fieldf(&iovw, "N=", "%d", 7)); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("N=7")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "N=7", strlen("N=7")), 0); +} + +TEST(iovw_put_string_field_free) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + /* iovw_put_string_field_free takes ownership of the value string (frees it on return). */ + char *v = strdup("hello"); + ASSERT_NOT_NULL(v); + ASSERT_OK(iovw_put_string_field_free(&iovw, "K=", v)); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("K=hello")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "K=hello", strlen("K=hello")), 0); +} + +TEST(iovw_rebase) { + /* iovw_rebase shifts all iov_base pointers from an old base to a new base. Fabricate a + * stand-in "old base" and "new base" and a wrapper with offsets pointing into the old + * base, then verify they get rewritten to point into the new base. */ + + uint8_t old_base[64] = {}, new_base[64] = {}; + for (size_t i = 0; i < sizeof old_base; i++) { + old_base[i] = (uint8_t) i; + new_base[i] = (uint8_t) (100 + i); + } + + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + + ASSERT_OK(iovw_put(&iovw, old_base + 0, 4)); + ASSERT_OK(iovw_put(&iovw, old_base + 10, 2)); + ASSERT_OK(iovw_put(&iovw, old_base + 30, 8)); + ASSERT_EQ(iovw.count, 3U); + + iovw_rebase(&iovw, old_base, new_base); + + ASSERT_PTR_EQ(iovw.iovec[0].iov_base, new_base + 0); + ASSERT_PTR_EQ(iovw.iovec[1].iov_base, new_base + 10); + ASSERT_PTR_EQ(iovw.iovec[2].iov_base, new_base + 30); + + /* Lengths are preserved */ + ASSERT_EQ(iovw.iovec[0].iov_len, 4U); + ASSERT_EQ(iovw.iovec[1].iov_len, 2U); + ASSERT_EQ(iovw.iovec[2].iov_len, 8U); + + /* And the contents through the new base match what we staged there */ + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, new_base + 0, 4), 0); + ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, new_base + 10, 2), 0); + ASSERT_EQ(memcmp(iovw.iovec[2].iov_base, new_base + 30, 8), 0); +} + +TEST(iovw_size) { + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + ASSERT_EQ(iovw_size(&iovw), 0U); + + ASSERT_OK(iovw_put(&iovw, (char*) "abcd", 4)); + ASSERT_OK(iovw_put(&iovw, (char*) "efghij", 6)); + ASSERT_OK(iovw_put(&iovw, (char*) "kl", 2)); + ASSERT_EQ(iovw_size(&iovw), 12U); +} + +TEST(iovw_concat) { + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + + /* Empty wrapper -> empty string with 0 length */ + _cleanup_(iovec_done) struct iovec iov = {}; + ASSERT_OK(iovw_concat(&iovw, &iov)); + ASSERT_FALSE(iovec_is_set(&iov)); + ASSERT_STREQ(iov.iov_base, ""); + iovec_done(&iov); + + ASSERT_OK(iovw_put(&iovw, (char*) "foo", 3)); + ASSERT_OK(iovw_put(&iovw, (char*) "\0", 1)); + ASSERT_OK(iovw_put(&iovw, (char*) "bar", 4)); + + ASSERT_OK(iovw_concat(&iovw, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE("foo\0bar\0", 8))); +} + +TEST(iovw_to_cstring) { + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + _cleanup_free_ char *s; + + /* Empty wrapper → empty string */ + s = iovw_to_cstring(&iovw); + ASSERT_NOT_NULL(s); + ASSERT_STREQ(s, ""); + s = mfree(s); + + ASSERT_OK(iovw_put(&iovw, (char*) "foo", 3)); + ASSERT_OK(iovw_put(&iovw, (char*) "/", 1)); + ASSERT_OK(iovw_put(&iovw, (char*) "bar", 3)); + + s = iovw_to_cstring(&iovw); + ASSERT_NOT_NULL(s); + ASSERT_STREQ(s, "foo/bar"); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-json.c b/src/test/test-json.c index 679152fd955a8..1785ef416f5b1 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include #include @@ -555,6 +556,49 @@ TEST(depth) { fputs("\n", stdout); } +static char *prepare_nested_json(const char *open, unsigned depth) { + char *s, *p; + size_t olen; + + assert_se(open); + + olen = strlen(open); + s = p = new(char, olen * depth + 1); + if (!s) + return NULL; + + for (unsigned i = 0; i < depth; i++) + p = mempcpy(p, open, olen); + *p = '\0'; + + return s; +} + +TEST(parse_depth) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_free_ char *s = NULL; + + /* Refuse parsing > DEPTH_MAX (currently 2048) levels of nested arrays */ + s = prepare_nested_json("[", 2049); + ASSERT_ERROR(sd_json_parse(s, 0, &v, NULL, NULL), ELNRNG); + s = mfree(s); + + /* Same for nested objects */ + s = prepare_nested_json("{\"a\":", 2049); + ASSERT_ERROR(sd_json_parse(s, 0, &v, NULL, NULL), ELNRNG); + s = mfree(s); + + /* <= DEPTH_MAX levels of nested arrays should be refused by EINVAL + * later in the parsing process */ + s = prepare_nested_json("[", 2048); + ASSERT_ERROR(sd_json_parse(s, 0, &v, NULL, NULL), EINVAL); + s = mfree(s); + + /* And the same for nested objects */ + s = prepare_nested_json("{\"a\":", 2048); + ASSERT_ERROR(sd_json_parse(s, 0, &v, NULL, NULL), EINVAL); +} + TEST(normalize) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *w = NULL; _cleanup_free_ char *t = NULL; @@ -646,7 +690,9 @@ static void test_float_match(sd_json_variant *v) { assert_se(sd_json_variant_is_array(v)); assert_se(sd_json_variant_elements(v) == 11); + assert_se(!iszero_safe(sd_json_variant_real(sd_json_variant_by_index(v, 0)))); assert_se(fabs(1.0 - (DBL_MIN / sd_json_variant_real(sd_json_variant_by_index(v, 0)))) <= delta); + assert_se(!iszero_safe(sd_json_variant_real(sd_json_variant_by_index(v, 1)))); assert_se(fabs(1.0 - (DBL_MAX / sd_json_variant_real(sd_json_variant_by_index(v, 1)))) <= delta); assert_se(sd_json_variant_is_null(sd_json_variant_by_index(v, 2))); /* nan is not supported by json → null */ assert_se(sd_json_variant_is_null(sd_json_variant_by_index(v, 3))); /* +inf is not supported by json → null */ @@ -664,6 +710,7 @@ static void test_float_match(sd_json_variant *v) { sd_json_variant_integer(sd_json_variant_by_index(v, 8)) == -10); assert_se(sd_json_variant_is_real(sd_json_variant_by_index(v, 9)) && !sd_json_variant_is_integer(sd_json_variant_by_index(v, 9))); + assert_se(!iszero_safe(sd_json_variant_real(sd_json_variant_by_index(v, 9)))); assert_se(fabs(1.0 - (DBL_MIN / 2 / sd_json_variant_real(sd_json_variant_by_index(v, 9)))) <= delta); assert_se(sd_json_variant_is_real(sd_json_variant_by_index(v, 10)) && !sd_json_variant_is_integer(sd_json_variant_by_index(v, 10))); @@ -1159,8 +1206,8 @@ TEST(json_iovec) { assert_se(json_variant_unbase64_iovec(sd_json_variant_by_key(j, "nr1"), &a) >= 0); assert_se(json_variant_unhex_iovec(sd_json_variant_by_key(j, "nr2"), &b) >= 0); - assert_se(iovec_memcmp(&iov1, &a) == 0); - assert_se(iovec_memcmp(&iov2, &b) == 0); + assert_se(iovec_equal(&iov1, &a)); + assert_se(iovec_equal(&iov2, &b)); assert_se(iovec_memcmp(&iov2, &a) < 0); assert_se(iovec_memcmp(&iov1, &b) > 0); } @@ -1588,4 +1635,242 @@ TEST(json_variant_compare) { test_json_variant_compare_one("{\"a\":\"b\",\"b\":\"c\"}", "{\"a\":\"b\"}", 1); } +TEST(must_be) { + ASSERT_OK(sd_json_parse("null", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("null", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("null", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("null", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("true", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("true", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("true", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("true", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("\"foo\"", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("\"foo\"", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("\"foo\"", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("\"foo\"", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("4711", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("4711", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("4711", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("4711", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("-4711", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("-4711", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("-4711", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("-4711", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("-4.5", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("-4.5", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("-4.5", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("-4.5", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("{}", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_OK(sd_json_parse("{}", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("{}", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_OK(sd_json_parse("{}", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + + ASSERT_OK(sd_json_parse("[]", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("[]", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_OK(sd_json_parse("[]", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_OK(sd_json_parse("[]", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); +} + +TEST(json_dispatch_in_addr) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + + /* 192.168.1.1 = { 192, 168, 1, 1 } */ + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", JSON_BUILD_IN4_ADDR(&(const struct in_addr) { .s_addr = htobe32(0xC0A80101U) })), + SD_JSON_BUILD_PAIR("null_addr", SD_JSON_BUILD_NULL)))); + + struct { + struct in_addr addr; + struct in_addr null_addr; + } data = {}; + + ASSERT_OK(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, offsetof(typeof(data), addr) }, + { "null_addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, offsetof(typeof(data), null_addr) }, + {}, + }, + /* flags= */ 0, + &data)); + + ASSERT_EQ(be32toh(data.addr.s_addr), 0xC0A80101U); + ASSERT_EQ(data.null_addr.s_addr, 0U); + + struct in_addr dummy = {}; + + /* Too few bytes (3 instead of 4) */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_UNSIGNED(192), SD_JSON_BUILD_UNSIGNED(168), SD_JSON_BUILD_UNSIGNED(1)))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); + + /* Too many bytes (5 instead of 4) */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_UNSIGNED(192), SD_JSON_BUILD_UNSIGNED(168), SD_JSON_BUILD_UNSIGNED(1), SD_JSON_BUILD_UNSIGNED(1), SD_JSON_BUILD_UNSIGNED(0)))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); + + /* Not an array or string */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_BOOLEAN(true))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); + + /* A string */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", JSON_BUILD_CONST_STRING("192.168.1.1"))))); + zero(data); + ASSERT_OK(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 }, + {}, + }, + /* flags= */ 0, + &data)); + ASSERT_EQ(be32toh(data.addr.s_addr), 0xC0A80101U); + + /* Byte value out of range (> 255) */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_UNSIGNED(256), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1)))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); + + /* Negative element */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_INTEGER(-1), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1)))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); +} + +TEST(json_dispatch_in6_addr) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + + /* ::1 */ + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", JSON_BUILD_IN6_ADDR(&(const struct in6_addr) { .s6_addr = { [15] = 1 } })), + SD_JSON_BUILD_PAIR("null_addr", SD_JSON_BUILD_NULL)))); + + struct { + struct in6_addr addr; + struct in6_addr null_addr; + } data = {}; + + ASSERT_OK(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, offsetof(typeof(data), addr) }, + { "null_addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, offsetof(typeof(data), null_addr) }, + {}, + }, + /* flags= */ 0, + &data)); + + ASSERT_EQ(data.addr.s6_addr[15], 1); + for (size_t i = 0; i < 15; i++) + ASSERT_EQ(data.addr.s6_addr[i], 0); + for (size_t i = 0; i < 16; i++) + ASSERT_EQ(data.null_addr.s6_addr[i], 0); + + struct in6_addr dummy = {}; + + /* Too few bytes (15 instead of 16) */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1)))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); + + /* Too many bytes (17 instead of 16) */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1), + SD_JSON_BUILD_UNSIGNED(0)))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); + + /* Not an array */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_BOOLEAN(true))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); + + /* A string */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", JSON_BUILD_CONST_STRING("::1"))))); + + zero(data); + ASSERT_OK(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0 }, + {}, + }, + /* flags= */ 0, + &data)); + + ASSERT_EQ(data.addr.s6_addr[15], 1); + for (size_t i = 0; i < 15; i++) + ASSERT_EQ(data.addr.s6_addr[i], 0); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-kexec.c b/src/test/test-kexec.c new file mode 100644 index 0000000000000..a8dd397acc4cd --- /dev/null +++ b/src/test/test-kexec.c @@ -0,0 +1,261 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "alloc-util.h" +#include "compress.h" +#include "fd-util.h" +#include "io-util.h" +#include "reboot-util.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "unaligned.h" + +static int find_kernel_image(char **ret) { + struct utsname u; + + ASSERT_OK_ERRNO(uname(&u)); + + /* Kernel image names vary across architectures and distributions: + * vmlinuz — compressed Linux kernel (x86, most distros) + * vmlinux — uncompressed ELF kernel (ppc64, s390) + * Image — uncompressed flat binary (arm64, riscv) + * Image.gz — gzip-compressed Image (arm64) + * zImage — compressed kernel (arm 32-bit) + * vmlinuz.efi — EFI ZBOOT PE wrapper (arm64 with CONFIG_EFI_ZBOOT) */ + static const char *const names[] = { + "vmlinuz", + "vmlinux", + "Image", + "Image.gz", + "zImage", + "vmlinuz.efi", + }; + + /* Try /usr/lib/modules// first (kernel-install convention), + * then /boot/-, then /boot/ */ + for (size_t i = 0; i < ELEMENTSOF(names); i++) { + _cleanup_free_ char *path = NULL; + + path = strjoin("/usr/lib/modules/", u.release, "/", names[i]); + if (!path) + return -ENOMEM; + + if (access(path, R_OK) >= 0) { + *ret = TAKE_PTR(path); + return 0; + } + } + + /* /boot may not be accessible without root, skip gracefully */ + if (access("/boot", R_OK) >= 0) { + for (size_t i = 0; i < ELEMENTSOF(names); i++) { + _cleanup_free_ char *path = NULL; + + path = strjoin("/boot/", names[i], "-", u.release); + if (!path) + return -ENOMEM; + + if (access(path, R_OK) >= 0) { + *ret = TAKE_PTR(path); + return 0; + } + } + + for (size_t i = 0; i < ELEMENTSOF(names); i++) { + _cleanup_free_ char *path = NULL; + + path = strjoin("/boot/", names[i]); + if (!path) + return -ENOMEM; + + if (access(path, R_OK) >= 0) { + *ret = TAKE_PTR(path); + return 0; + } + } + } + + return -ENOENT; +} + +TEST(passthrough_unrecognized) { + /* A file with unrecognized magic should pass through as-is (return 0) */ + _cleanup_close_ int fd = -EBADF; + _cleanup_(unlink_tempfilep) char path[] = "/tmp/test-kexec.XXXXXX"; + + ASSERT_OK(fd = mkostemp_safe(path)); + ASSERT_OK_EQ_ERRNO(write(fd, "HELLO WORLD\0", 12), 12); + ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_SET)); + + _cleanup_close_ int kernel_fd = -EBADF, initrd_fd = -EBADF; + ASSERT_OK_ZERO(kexec_maybe_decompress_kernel(path, fd, &kernel_fd, &initrd_fd)); + ASSERT_EQ(kernel_fd, -EBADF); + ASSERT_EQ(initrd_fd, -EBADF); +} + +TEST(gzip_round_trip) { + _cleanup_close_ int src_fd = -EBADF, gz_fd = -EBADF; + _cleanup_(unlink_tempfilep) char + src_path[] = "/tmp/test-kexec-src.XXXXXX", + gz_path[] = "/tmp/test-kexec-gz.XXXXXX"; + int r; + + r = dlopen_zlib(LOG_DEBUG); + if (r < 0) { + log_tests_skipped("zlib not available"); + return; + } + + /* Create a source file with known content */ + ASSERT_OK(src_fd = mkostemp_safe(src_path)); + char buf[4096]; + memset(buf, 'A', sizeof(buf)); + ASSERT_OK(loop_write(src_fd, buf, sizeof(buf))); + + /* Compress it with gzip */ + ASSERT_OK_ERRNO(lseek(src_fd, 0, SEEK_SET)); + ASSERT_OK(gz_fd = mkostemp_safe(gz_path)); + ASSERT_OK(compress_stream(COMPRESSION_GZIP, src_fd, gz_fd, UINT64_MAX, NULL)); + + /* Feed the gzip file to kexec_maybe_decompress_kernel */ + ASSERT_OK_ERRNO(lseek(gz_fd, 0, SEEK_SET)); + + _cleanup_close_ int kernel_fd = -EBADF, initrd_fd = -EBADF; + ASSERT_OK_POSITIVE(kexec_maybe_decompress_kernel(gz_path, gz_fd, &kernel_fd, &initrd_fd)); + ASSERT_GE(kernel_fd, 0); + ASSERT_EQ(initrd_fd, -EBADF); + + /* Verify the decompressed content matches the original */ + char result[4096]; + ASSERT_OK_EQ_ERRNO(pread(kernel_fd, result, sizeof(result), 0), (ssize_t) sizeof(result)); + ASSERT_EQ(memcmp(buf, result, sizeof(buf)), 0); +} + +TEST(zboot_synthetic) { + /* Construct a minimal ZBOOT header with a gzip-compressed payload */ + _cleanup_close_ int src_fd = -EBADF, gz_fd = -EBADF, zboot_fd = -EBADF; + _cleanup_(unlink_tempfilep) char + src_path[] = "/tmp/test-kexec-zboot-src.XXXXXX", + gz_path[] = "/tmp/test-kexec-zboot-gz.XXXXXX", + zboot_path[] = "/tmp/test-kexec-zboot.XXXXXX"; + int r; + + r = dlopen_zlib(LOG_DEBUG); + if (r < 0) { + log_tests_skipped("zlib not available"); + return; + } + + /* Create and compress a payload */ + char payload[512]; + memset(payload, 'K', sizeof(payload)); + + ASSERT_OK(src_fd = mkostemp_safe(src_path)); + ASSERT_OK(loop_write(src_fd, payload, sizeof(payload))); + ASSERT_OK_ERRNO(lseek(src_fd, 0, SEEK_SET)); + + ASSERT_OK(gz_fd = mkostemp_safe(gz_path)); + ASSERT_OK(compress_stream(COMPRESSION_GZIP, src_fd, gz_fd, UINT64_MAX, NULL)); + + /* Read the compressed data */ + struct stat st; + ASSERT_OK_ERRNO(fstat(gz_fd, &st)); + size_t compressed_size = st.st_size; + _cleanup_free_ void *compressed = malloc(compressed_size); + ASSERT_NOT_NULL(compressed); + ASSERT_OK_EQ_ERRNO(pread(gz_fd, compressed, compressed_size, 0), (ssize_t) compressed_size); + + /* Build the ZBOOT header: + * 0x00: "MZ" + * 0x04: "zimg" + * 0x08: payload offset (LE32) + * 0x0C: payload size (LE32) + * 0x18: "gzip\0" */ + uint8_t header[0x40] = {}; + uint32_t payload_offset = sizeof(header); + + header[0] = 'M'; + header[1] = 'Z'; + memcpy(header + 0x04, "zimg", 4); + unaligned_write_le32(header + 0x08, payload_offset); + unaligned_write_le32(header + 0x0C, (uint32_t) compressed_size); + memcpy(header + 0x18, "gzip", 5); + + ASSERT_OK(zboot_fd = mkostemp_safe(zboot_path)); + ASSERT_OK(loop_write(zboot_fd, header, sizeof(header))); + ASSERT_OK(loop_write(zboot_fd, compressed, compressed_size)); + ASSERT_OK_ERRNO(lseek(zboot_fd, 0, SEEK_SET)); + + /* Test extraction */ + _cleanup_close_ int kernel_fd = -EBADF, initrd_fd = -EBADF; + ASSERT_OK_POSITIVE(kexec_maybe_decompress_kernel(zboot_path, zboot_fd, &kernel_fd, &initrd_fd)); + ASSERT_GE(kernel_fd, 0); + + /* Verify decompressed content matches original payload */ + char result[512]; + ASSERT_OK_EQ_ERRNO(pread(kernel_fd, result, sizeof(result), 0), (ssize_t) sizeof(result)); + ASSERT_EQ(memcmp(payload, result, sizeof(payload)), 0); +} + +TEST(system_kernel) { + _cleanup_free_ char *path = NULL; + _cleanup_close_ int fd = -EBADF; + int r; + + r = find_kernel_image(&path); + if (r < 0) { + log_tests_skipped_errno(r, "No kernel image found on this system"); + return; + } + + log_info("Found kernel image: %s", path); + + fd = open(path, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + log_tests_skipped_errno(errno, "Cannot open kernel image '%s'", path); + return; + } + + _cleanup_close_ int kernel_fd = -EBADF, initrd_fd = -EBADF; + ASSERT_OK(r = kexec_maybe_decompress_kernel(path, fd, &kernel_fd, &initrd_fd)); + + if (r == 0) { + log_info("Kernel image was not compressed (passed through as-is)."); + return; + } + + log_info("Kernel image was decompressed/extracted successfully."); + ASSERT_GE(kernel_fd, 0); + + /* Verify the decompressed result is non-empty and looks plausible */ + struct stat st; + ASSERT_OK_ERRNO(fstat(kernel_fd, &st)); + ASSERT_GT(st.st_size, 0); + log_info("Decompressed kernel size: %zu bytes", (size_t) st.st_size); + + /* Read the first bytes and check for known kernel magic */ + uint8_t magic[8]; + ASSERT_OK_EQ_ERRNO(pread(kernel_fd, magic, sizeof(magic), 0), (ssize_t) sizeof(magic)); + + if (magic[0] == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F') + log_info("Decompressed kernel is an ELF image."); + else if (magic[0] == 'M' && magic[1] == 'Z') + log_info("Decompressed kernel is a PE image."); + else + log_info("Decompressed kernel magic: %02x %02x %02x %02x %02x %02x %02x %02x", + magic[0], magic[1], magic[2], magic[3], + magic[4], magic[5], magic[6], magic[7]); + + /* If a UKI initrd was extracted, verify it too */ + if (initrd_fd >= 0) { + ASSERT_OK_ERRNO(fstat(initrd_fd, &st)); + ASSERT_GT(st.st_size, 0); + log_info("Extracted initrd size: %zu bytes", (size_t) st.st_size); + } +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-load-fragment.c b/src/test/test-load-fragment.c index 892e471d8ab0c..3e318a36c91d3 100644 --- a/src/test/test-load-fragment.c +++ b/src/test/test-load-fragment.c @@ -37,7 +37,7 @@ static char *runtime_dir = NULL; STATIC_DESTRUCTOR_REGISTER(runtime_dir, rm_rf_physical_and_freep); /* For testing type compatibility. */ -_unused_ ConfigPerfItemLookup unused_lookup = load_fragment_gperf_lookup; +_unused_ static ConfigPerfItemLookup unused_lookup = load_fragment_gperf_lookup; TEST_RET(unit_file_get_list) { int r; @@ -972,7 +972,7 @@ TEST(config_parse_log_filter_patterns) { TEST_PATTERN("~foobar", 0, 1), }; - if (ERRNO_IS_NOT_SUPPORTED(dlopen_pcre2())) + if (ERRNO_IS_NOT_SUPPORTED(dlopen_pcre2(LOG_DEBUG))) return (void) log_tests_skipped("PCRE2 support is not available"); FOREACH_ELEMENT(test, regex_tests) { diff --git a/src/test/test-log.c b/src/test/test-log.c index f77ed205a7086..fb23776b211d2 100644 --- a/src/test/test-log.c +++ b/src/test/test-log.c @@ -104,9 +104,9 @@ static void test_log_format_iovec_sentinel( for (size_t i = 0; i < n; i++) if (i < m) - ASSERT_EQ(iovec_memcmp(&iovec[i], &IOVEC_MAKE_STRING(v[i])), 0); + ASSERT_TRUE(iovec_equal(&iovec[i], &IOVEC_MAKE_STRING(v[i]))); else { - ASSERT_EQ(iovec_memcmp(&iovec[i], &IOVEC_MAKE_STRING(expected[i - m])), 0); + ASSERT_TRUE(iovec_equal(&iovec[i], &IOVEC_MAKE_STRING(expected[i - m]))); free(iovec[i].iov_base); } @@ -122,12 +122,12 @@ static void test_log_format_iovec_sentinel( for (size_t i = 0; i < n; i++) if (i < m) - ASSERT_EQ(iovec_memcmp(&iovec[i], &IOVEC_MAKE_STRING(v[i])), 0); + ASSERT_TRUE(iovec_equal(&iovec[i], &IOVEC_MAKE_STRING(v[i]))); else if ((i - m) % 2 == 0) { - ASSERT_EQ(iovec_memcmp(&iovec[i], &IOVEC_MAKE_STRING(expected[(i - m) / 2])), 0); + ASSERT_TRUE(iovec_equal(&iovec[i], &IOVEC_MAKE_STRING(expected[(i - m) / 2]))); free(iovec[i].iov_base); } else - ASSERT_EQ(iovec_memcmp(&iovec[i], &IOVEC_MAKE_STRING("\n")), 0); + ASSERT_TRUE(iovec_equal(&iovec[i], &IOVEC_MAKE_STRING("\n"))); } #define test_log_format_iovec_one(...) \ @@ -261,13 +261,12 @@ static void test_log_context(void) { IOVEC_MAKE_STRING("ABC=def"), IOVEC_MAKE_STRING("GHI=jkl"), }; - _cleanup_free_ struct iovec_wrapper *iovw = NULL; - ASSERT_NOT_NULL(iovw = iovw_new()); - ASSERT_OK(iovw_consume(iovw, strdup("MNO=pqr"), STRLEN("MNO=pqr") + 1)); + struct iovec_wrapper iovw = {}; + ASSERT_OK(iovw_consume(&iovw, strdup("MNO=pqr"), STRLEN("MNO=pqr") + 1)); LOG_CONTEXT_PUSH_IOV(iov, ELEMENTSOF(iov)); LOG_CONTEXT_PUSH_IOV(iov, ELEMENTSOF(iov)); - LOG_CONTEXT_CONSUME_IOV(iovw->iovec, iovw->count); + LOG_CONTEXT_CONSUME_IOV(iovw.iovec, iovw.count); LOG_CONTEXT_PUSH("STU=vwx"); ASSERT_EQ(log_context_num_contexts(), 3U); diff --git a/src/test/test-loop-block.c b/src/test/test-loop-util.c similarity index 51% rename from src/test/test-loop-block.c rename to src/test/test-loop-util.c index 76046f98ada19..f90bf0e1998fb 100644 --- a/src/test/test-loop-block.c +++ b/src/test/test-loop-util.c @@ -9,12 +9,12 @@ #include #include "alloc-util.h" +#include "argv-util.h" #include "capability-util.h" #include "dissect-image.h" #include "fd-util.h" #include "gpt.h" #include "loop-util.h" -#include "main-func.h" #include "mkfs-util.h" #include "mount-util.h" #include "namespace-util.h" @@ -35,14 +35,14 @@ static usec_t arg_timeout = 0; static usec_t end = 0; static void verify_dissected_image(DissectedImage *dissected) { - assert_se(dissected->partitions[PARTITION_ESP].found); - assert_se(dissected->partitions[PARTITION_ESP].node); - assert_se(dissected->partitions[PARTITION_XBOOTLDR].found); - assert_se(dissected->partitions[PARTITION_XBOOTLDR].node); - assert_se(dissected->partitions[PARTITION_ROOT].found); - assert_se(dissected->partitions[PARTITION_ROOT].node); - assert_se(dissected->partitions[PARTITION_HOME].found); - assert_se(dissected->partitions[PARTITION_HOME].node); + ASSERT_TRUE(dissected->partitions[PARTITION_ESP].found); + ASSERT_NOT_NULL(dissected->partitions[PARTITION_ESP].node); + ASSERT_TRUE(dissected->partitions[PARTITION_XBOOTLDR].found); + ASSERT_NOT_NULL(dissected->partitions[PARTITION_XBOOTLDR].node); + ASSERT_TRUE(dissected->partitions[PARTITION_ROOT].found); + ASSERT_NOT_NULL(dissected->partitions[PARTITION_ROOT].node); + ASSERT_TRUE(dissected->partitions[PARTITION_HOME].found); + ASSERT_NOT_NULL(dissected->partitions[PARTITION_HOME].node); } static void verify_dissected_image_harder(DissectedImage *dissected) { @@ -56,7 +56,6 @@ static void verify_dissected_image_harder(DissectedImage *dissected) { static void* thread_func(void *ptr) { int fd = PTR_TO_FD(ptr); - int r; for (unsigned i = 0; i < arg_n_iterations; i++) { _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; @@ -70,28 +69,22 @@ static void* thread_func(void *ptr) { log_notice("> Thread iteration #%u.", i); - assert_se(mkdtemp_malloc(NULL, &mounted) >= 0); + ASSERT_OK(mkdtemp_malloc(NULL, &mounted)); - r = loop_device_make(fd, O_RDONLY, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_SH, &loop); - if (r < 0) - log_error_errno(r, "Failed to allocate loopback device: %m"); - assert_se(r >= 0); - assert_se(loop->dev); - assert_se(loop->backing_file); + ASSERT_OK(loop_device_make(fd, O_RDONLY, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_SH, &loop)); + ASSERT_NOT_NULL(loop->dev); + ASSERT_NOT_NULL(loop->backing_file); log_notice("Acquired loop device %s, will mount on %s", loop->node, mounted); - r = dissect_loop_device( + ASSERT_OK(dissect_loop_device( loop, /* verity= */ NULL, /* mount_options= */ NULL, /* image_policy= */ NULL, /* image_filter= */ NULL, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, - &dissected); - if (r < 0) - log_error_errno(r, "Failed to dissect loopback device %s: %m", loop->node); - assert_se(r >= 0); + &dissected)); log_info("Dissected loop device %s", loop->node); @@ -107,19 +100,17 @@ static void* thread_func(void *ptr) { verify_dissected_image(dissected); - r = dissected_image_mount( + ASSERT_OK(dissected_image_mount( dissected, mounted, /* uid_shift= */ UID_INVALID, /* uid_range= */ UID_INVALID, /* userns_fd= */ -EBADF, - DISSECT_IMAGE_READ_ONLY); - log_notice_errno(r, "Mounted %s → %s: %m", loop->node, mounted); - assert_se(r >= 0); + DISSECT_IMAGE_READ_ONLY)); /* Now the block device is mounted, we don't need no manual lock anymore, the devices are now * pinned by the mounts. */ - assert_se(loop_device_flock(loop, LOCK_UN) >= 0); + ASSERT_OK(loop_device_flock(loop, LOCK_UN)); log_notice("Unmounting %s", mounted); mounted = umount_and_rmdir_and_free(mounted); @@ -147,47 +138,36 @@ static bool have_root_gpt_type(void) { #endif } -static int run(int argc, char *argv[]) { -#if HAVE_BLKID - _cleanup_(dissected_image_unrefp) DissectedImage *dissected = NULL; - _cleanup_(umount_and_rmdir_and_freep) char *mounted = NULL; - pthread_t threads[arg_n_threads]; - sd_id128_t id; -#endif - _cleanup_free_ char *p = NULL, *cmd = NULL; - _cleanup_pclose_ FILE *sfdisk = NULL; - _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; - _cleanup_close_ int fd = -EBADF; +static int intro(void) { int r; - test_setup_logging(LOG_DEBUG); log_show_tid(true); log_show_time(true); log_show_color(true); - if (argc >= 2) { - r = safe_atou(argv[1], &arg_n_threads); + if (saved_argc >= 2) { + r = safe_atou(saved_argv[1], &arg_n_threads); if (r < 0) - return log_error_errno(r, "Failed to parse first argument (number of threads): %s", argv[1]); + return log_error_errno(r, "Failed to parse first argument (number of threads): %s", saved_argv[1]); if (arg_n_threads <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Number of threads must be at least 1, refusing."); } - if (argc >= 3) { - r = safe_atou(argv[2], &arg_n_iterations); + if (saved_argc >= 3) { + r = safe_atou(saved_argv[2], &arg_n_iterations); if (r < 0) - return log_error_errno(r, "Failed to parse second argument (number of iterations): %s", argv[2]); + return log_error_errno(r, "Failed to parse second argument (number of iterations): %s", saved_argv[2]); if (arg_n_iterations <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Number of iterations must be at least 1, refusing."); } - if (argc >= 4) { - r = parse_sec(argv[3], &arg_timeout); + if (saved_argc >= 4) { + r = parse_sec(saved_argv[3], &arg_timeout); if (r < 0) - return log_error_errno(r, "Failed to parse third argument (timeout): %s", argv[3]); + return log_error_errno(r, "Failed to parse third argument (timeout): %s", saved_argv[3]); } - if (argc >= 5) + if (saved_argc >= 5) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments (expected 3 at max)."); if (!have_root_gpt_type()) @@ -197,13 +177,27 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_tests_skipped_errno(r, "Could not find sfdisk command"); - assert_se(tempfn_random_child("/var/tmp", "sfdisk", &p) >= 0); - fd = open(p, O_CREAT|O_EXCL|O_RDWR|O_CLOEXEC|O_NOFOLLOW, 0666); - assert_se(fd >= 0); - assert_se(ftruncate(fd, 256*1024*1024) >= 0); + return EXIT_SUCCESS; +} - assert_se(cmd = strjoin("sfdisk ", p)); - assert_se(sfdisk = popen(cmd, "we")); +TEST(loop_block) { +#if HAVE_BLKID + _cleanup_(dissected_image_unrefp) DissectedImage *dissected = NULL; + _cleanup_(umount_and_rmdir_and_freep) char *mounted = NULL; + pthread_t threads[arg_n_threads]; + sd_id128_t id; +#endif + _cleanup_free_ char *p = NULL, *cmd = NULL; + _cleanup_pclose_ FILE *sfdisk = NULL; + _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; + _cleanup_close_ int fd = -EBADF; + + ASSERT_OK(tempfn_random_child("/var/tmp", "sfdisk", &p)); + fd = ASSERT_OK_ERRNO(open(p, O_CREAT|O_EXCL|O_RDWR|O_CLOEXEC|O_NOFOLLOW, 0666)); + ASSERT_OK_ERRNO(ftruncate(fd, 256*1024*1024)); + + cmd = ASSERT_NOT_NULL(strjoin("sfdisk ", p)); + sfdisk = ASSERT_NOT_NULL(popen(cmd, "we")); /* A reasonably complex partition table that fits on a 64K disk */ fputs("label: gpt\n" @@ -221,62 +215,63 @@ static int run(int argc, char *argv[]) { fputs("\n" "size=32M, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915\n", sfdisk); - assert_se(pclose(sfdisk) == 0); + ASSERT_EQ(pclose(sfdisk), 0); sfdisk = NULL; #if HAVE_BLKID - assert_se(dissect_image_file( + ASSERT_OK(dissect_image_file( p, /* verity= */ NULL, /* mount_options= */ NULL, /* image_policy= */ NULL, /* image_filter= */ NULL, /* flags= */ 0, - &dissected) >= 0); + &dissected)); verify_dissected_image(dissected); dissected = dissected_image_unref(dissected); #endif - if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) - return log_tests_skipped("not running privileged"); + if (have_effective_cap(CAP_SYS_ADMIN) <= 0) { + log_tests_skipped("not running privileged"); + return; + } - if (detect_container() != 0 || running_in_chroot() != 0) - return log_tests_skipped("Test not supported in a container/chroot, requires udev/uevent notifications"); + if (detect_container() != 0 || running_in_chroot() != 0) { + log_tests_skipped("Test not supported in a container/chroot, requires udev/uevent notifications"); + return; + } - assert_se(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_EX, &loop) >= 0); + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_EX, &loop)); #if HAVE_BLKID - assert_se(dissect_loop_device( + ASSERT_OK(dissect_loop_device( loop, /* verity= */ NULL, /* mount_options= */ NULL, /* image_policy= */ NULL, /* image_filter= */ NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, - &dissected) >= 0); + &dissected)); verify_dissected_image(dissected); FOREACH_STRING(fs, "vfat", "ext4") { - r = mkfs_exists(fs); - assert_se(r >= 0); - if (!r) { + if (ASSERT_OK(mkfs_exists(fs)) == 0) { log_tests_skipped("mkfs.{vfat|ext4} not installed"); - return 0; + return; } } - assert_se(r >= 0); - assert_se(sd_id128_randomize(&id) >= 0); - assert_se(make_filesystem(dissected->partitions[PARTITION_ESP].node, "vfat", "EFI", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0); + ASSERT_OK(sd_id128_randomize(&id)); + ASSERT_OK(make_filesystem(dissected->partitions[PARTITION_ESP].node, "vfat", "EFI", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL)); - assert_se(sd_id128_randomize(&id) >= 0); - assert_se(make_filesystem(dissected->partitions[PARTITION_XBOOTLDR].node, "vfat", "xbootldr", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0); + ASSERT_OK(sd_id128_randomize(&id)); + ASSERT_OK(make_filesystem(dissected->partitions[PARTITION_XBOOTLDR].node, "vfat", "xbootldr", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL)); - assert_se(sd_id128_randomize(&id) >= 0); - assert_se(make_filesystem(dissected->partitions[PARTITION_ROOT].node, "ext4", "root", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0); + ASSERT_OK(sd_id128_randomize(&id)); + ASSERT_OK(make_filesystem(dissected->partitions[PARTITION_ROOT].node, "ext4", "root", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL)); - assert_se(sd_id128_randomize(&id) >= 0); - assert_se(make_filesystem(dissected->partitions[PARTITION_HOME].node, "ext4", "home", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0); + ASSERT_OK(sd_id128_randomize(&id)); + ASSERT_OK(make_filesystem(dissected->partitions[PARTITION_HOME].node, "ext4", "home", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL)); dissected = dissected_image_unref(dissected); @@ -285,62 +280,62 @@ static int run(int argc, char *argv[]) { * hence what was written via the partition device might not appear on the whole block device * yet. Let's hence explicitly flush the whole block device, so that the read-back definitely * works. */ - assert_se(ioctl(loop->fd, BLKFLSBUF, 0) >= 0); + ASSERT_OK_ERRNO(ioctl(loop->fd, BLKFLSBUF, 0)); /* Try to read once, without pinning or adding partitions, i.e. by only accessing the whole block * device. */ - assert_se(dissect_loop_device( + ASSERT_OK(dissect_loop_device( loop, /* verity= */ NULL, /* mount_options= */ NULL, /* image_policy= */ NULL, /* image_filter= */ NULL, /* flags= */ 0, - &dissected) >= 0); + &dissected)); verify_dissected_image_harder(dissected); dissected = dissected_image_unref(dissected); /* Now go via the loopback device after all, but this time add/pin, because now we want to mount it. */ - assert_se(dissect_loop_device( + ASSERT_OK(dissect_loop_device( loop, /* verity= */ NULL, /* mount_options= */ NULL, /* image_policy= */ NULL, /* image_filter= */ NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, - &dissected) >= 0); + &dissected)); verify_dissected_image_harder(dissected); - assert_se(mkdtemp_malloc(NULL, &mounted) >= 0); + ASSERT_OK(mkdtemp_malloc(NULL, &mounted)); /* We are particularly correct here, and now downgrade LOCK → LOCK_SH. That's because we are done * with formatting the file systems, so we don't need the exclusive lock anymore. From now on a * shared one is fine. This way udev can now probe the device if it wants, but still won't call * BLKRRPART on it, and that's good, because that would destroy our partition table while we are at * it. */ - assert_se(loop_device_flock(loop, LOCK_SH) >= 0); + ASSERT_OK(loop_device_flock(loop, LOCK_SH)); /* This is a test for the loopback block device setup code and it's use by the image dissection * logic: since the kernel APIs are hard use and prone to races, let's test this in a heavy duty * test: we open a bunch of threads and repeatedly allocate and deallocate loopback block devices in * them in parallel, with an image file with a number of partitions. */ - assert_se(detach_mount_namespace() >= 0); + ASSERT_OK(detach_mount_namespace()); /* This first (writable) mount will initialize the mount point dirs, so that the subsequent read-only ones can work */ - assert_se(dissected_image_mount( + ASSERT_OK(dissected_image_mount( dissected, mounted, /* uid_shift= */ UID_INVALID, /* uid_range= */ UID_INVALID, /* userns_fd= */ -EBADF, - 0) >= 0); + 0)); /* Now we mounted everything, the partitions are pinned. Now it's fine to release the lock * fully. This means udev could now issue BLKRRPART again, but that's OK given this will fail because * we now mounted the device. */ - assert_se(loop_device_flock(loop, LOCK_UN) >= 0); + ASSERT_OK(loop_device_flock(loop, LOCK_UN)); - assert_se(umount_recursive(mounted, 0) >= 0); + ASSERT_OK(umount_recursive(mounted, 0)); loop = loop_device_unref(loop); log_notice("Threads are being started now"); @@ -353,7 +348,7 @@ static int run(int argc, char *argv[]) { if (arg_n_threads > 1) for (unsigned i = 0; i < arg_n_threads; i++) - assert_se(pthread_create(threads + i, NULL, thread_func, FD_TO_PTR(fd)) == 0); + ASSERT_EQ(pthread_create(threads + i, NULL, thread_func, FD_TO_PTR(fd)), 0); log_notice("All threads started now."); @@ -364,8 +359,8 @@ static int run(int argc, char *argv[]) { log_notice("Joining thread #%u.", i); void *k; - assert_se(pthread_join(threads[i], &k) == 0); - assert_se(!k); + ASSERT_EQ(pthread_join(threads[i], &k), 0); + ASSERT_NULL(k); log_notice("Joined thread #%u.", i); } @@ -374,7 +369,173 @@ static int run(int argc, char *argv[]) { #else log_notice("Cutting test short, since we do not have libblkid."); #endif +} + +static int make_test_image(int *ret_fd) { + _cleanup_free_ char *p = NULL, *cmd = NULL; + _cleanup_pclose_ FILE *sfdisk = NULL; + + ASSERT_OK(tempfn_random_child("/var/tmp", "sfdisk", &p)); + int fd = ASSERT_OK_ERRNO(open(p, O_CREAT|O_EXCL|O_RDWR|O_CLOEXEC|O_NOFOLLOW, 0666)); + ASSERT_OK_ERRNO(ftruncate(fd, 256*1024*1024)); + + cmd = ASSERT_NOT_NULL(strjoin("sfdisk ", p)); + sfdisk = ASSERT_NOT_NULL(popen(cmd, "we")); + + fputs("label: gpt\n" + "size=32M, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B\n", sfdisk); + + ASSERT_EQ(pclose(sfdisk), 0); + sfdisk = NULL; + + (void) unlink(p); + + *ret_fd = fd; return 0; } -DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); +TEST(sector_size_regular_file) { + _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; + _cleanup_close_ int fd = -EBADF; + + if (have_effective_cap(CAP_SYS_ADMIN) <= 0) { + log_tests_skipped("not running privileged"); + return; + } + + if (detect_container() != 0 || running_in_chroot() != 0) { + log_tests_skipped("Test not supported in a container/chroot"); + return; + } + + ASSERT_OK(make_test_image(&fd)); + + /* sector_size=0 on regular file: should default to 512 */ + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, 0, LOCK_EX, &loop)); + ASSERT_EQ(loop->sector_size, 512u); + loop = loop_device_unref(loop); + + /* sector_size=UINT32_MAX on regular file with GPT: should probe and find 512 */ + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, UINT32_MAX, 0, LOCK_EX, &loop)); + ASSERT_EQ(loop->sector_size, 512u); + loop = loop_device_unref(loop); + + /* Explicit sector_size=512 on regular file */ + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 512, 0, LOCK_EX, &loop)); + ASSERT_EQ(loop->sector_size, 512u); + loop = loop_device_unref(loop); + + /* Explicit sector_size=4096 on regular file */ + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 4096, 0, LOCK_EX, &loop)); + ASSERT_EQ(loop->sector_size, 4096u); + loop = loop_device_unref(loop); +} + +TEST(sector_size_block_device) { + _cleanup_(loop_device_unrefp) LoopDevice *block_loop = NULL, *loop = NULL; + _cleanup_close_ int fd = -EBADF; + + if (have_effective_cap(CAP_SYS_ADMIN) <= 0) { + log_tests_skipped("not running privileged"); + return; + } + + if (detect_container() != 0 || running_in_chroot() != 0) { + log_tests_skipped("Test not supported in a container/chroot, requires udev/uevent notifications"); + return; + } + + ASSERT_OK(make_test_image(&fd)); + + /* Create a loop device to use as our block device */ + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_EX, &block_loop)); + ASSERT_FALSE(LOOP_DEVICE_IS_FOREIGN(block_loop)); + ASSERT_OK(loop_device_flock(block_loop, LOCK_SH)); + + uint32_t device_ssz = block_loop->sector_size; + + /* sector_size=0 on block device: should use device directly */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 0, 0, LOCK_SH, &loop)); + ASSERT_FALSE(loop->created); + ASSERT_EQ(loop->sector_size, device_ssz); + loop = loop_device_unref(loop); + + /* sector_size=UINT32_MAX on block device: should probe, match device, use directly */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, UINT32_MAX, 0, LOCK_SH, &loop)); + ASSERT_FALSE(loop->created); + ASSERT_EQ(loop->sector_size, device_ssz); + loop = loop_device_unref(loop); + + /* sector_size=UINT32_MAX with LO_FLAGS_PARTSCAN: should probe, match, use directly */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, UINT32_MAX, LO_FLAGS_PARTSCAN, LOCK_SH, &loop)); + ASSERT_FALSE(loop->created); + ASSERT_EQ(loop->sector_size, device_ssz); + loop = loop_device_unref(loop); + + /* Explicit sector_size matching device: should use device directly */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, device_ssz, 0, LOCK_SH, &loop)); + ASSERT_FALSE(loop->created); + ASSERT_EQ(loop->sector_size, device_ssz); + loop = loop_device_unref(loop); + + /* Explicit sector_size=4096 (differs from device 512): should create a real loop device */ + if (device_ssz != 4096) { + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 4096, 0, LOCK_SH, &loop)); + ASSERT_TRUE(loop->created); + ASSERT_EQ(loop->sector_size, 4096u); + loop = loop_device_unref(loop); + } +} + +TEST(sector_size_mismatch) { + _cleanup_(loop_device_unrefp) LoopDevice *block_loop = NULL, *loop = NULL; + _cleanup_close_ int fd = -EBADF; + + if (have_effective_cap(CAP_SYS_ADMIN) <= 0) { + log_tests_skipped("not running privileged"); + return; + } + + if (detect_container() != 0 || running_in_chroot() != 0) { + log_tests_skipped("Test not supported in a container/chroot"); + return; + } + + /* Create an image with a GPT written at 512-byte sectors, then create a loop device with + * 4096-byte sectors on top. This simulates the CD-ROM scenario where the device has large + * blocks but the GPT uses 512-byte sectors. */ + ASSERT_OK(make_test_image(&fd)); + + /* Create a loop device with 4096-byte sector size — GPT was written at 512 */ + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 4096, 0, LOCK_EX, &block_loop)); + ASSERT_TRUE(block_loop->created); + ASSERT_EQ(block_loop->sector_size, 4096u); + ASSERT_OK(loop_device_flock(block_loop, LOCK_SH)); + + /* sector_size=0: no preference, should use block device directly despite GPT mismatch */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 0, 0, LOCK_SH, &loop)); + ASSERT_FALSE(loop->created); + ASSERT_EQ(loop->sector_size, 4096u); + loop = loop_device_unref(loop); + + /* sector_size=UINT32_MAX: should probe GPT at 512, detect mismatch with device 4096, + * and create a new loop device with 512-byte sectors */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, UINT32_MAX, 0, LOCK_SH, &loop)); + ASSERT_TRUE(loop->created); + ASSERT_EQ(loop->sector_size, 512u); + loop = loop_device_unref(loop); + + /* Explicit sector_size=512: differs from device 4096, should create a new loop device */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 512, 0, LOCK_SH, &loop)); + ASSERT_TRUE(loop->created); + ASSERT_EQ(loop->sector_size, 512u); + loop = loop_device_unref(loop); + + /* Explicit sector_size=4096: matches device, should use directly */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 4096, 0, LOCK_SH, &loop)); + ASSERT_FALSE(loop->created); + ASSERT_EQ(loop->sector_size, 4096u); + loop = loop_device_unref(loop); +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-macro.c b/src/test/test-macro.c index 7f7bf1ce8dbda..9a9a1fa2dac7f 100644 --- a/src/test/test-macro.c +++ b/src/test/test-macro.c @@ -130,6 +130,13 @@ TEST(MAX) { assert_se(CLAMP(CLAMP(0, -10, 10), CLAMP(-5, 10, 20), CLAMP(100, -5, 20)) == 10); } +TEST(ABS_DIFF) { + ASSERT_EQ(ABS_DIFF(5, 3), 2); + ASSERT_EQ(ABS_DIFF(3, 5), 2); + ASSERT_EQ(ABS_DIFF(5llu, 2llu), 3llu); + ASSERT_EQ(ABS_DIFF(3llu, 5llu), 2llu); +} + #pragma GCC diagnostic push #ifdef __clang__ # pragma GCC diagnostic ignored "-Waddress-of-packed-member" diff --git a/src/test/test-mempress.c b/src/test/test-mempress.c deleted file mode 100644 index 817eaa421f1ad..0000000000000 --- a/src/test/test-mempress.c +++ /dev/null @@ -1,307 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include -#include -#include -#include -#include - -#include "sd-bus.h" -#include "sd-event.h" - -#include "bus-locator.h" -#include "bus-wait-for-jobs.h" -#include "event-util.h" -#include "fd-util.h" -#include "format-util.h" -#include "hashmap.h" -#include "path-util.h" -#include "pidref.h" -#include "process-util.h" -#include "random-util.h" -#include "rm-rf.h" -#include "signal-util.h" -#include "socket-util.h" -#include "tests.h" -#include "time-util.h" -#include "tmpfile-util.h" -#include "unit-def.h" - -struct fake_pressure_context { - int fifo_fd; - int socket_fd; -}; - -static void *fake_pressure_thread(void *p) { - _cleanup_free_ struct fake_pressure_context *c = ASSERT_PTR(p); - _cleanup_close_ int cfd = -EBADF; - - usleep_safe(150); - - assert_se(write(c->fifo_fd, &(const char) { 'x' }, 1) == 1); - - usleep_safe(150); - - cfd = accept4(c->socket_fd, NULL, NULL, SOCK_CLOEXEC); - assert_se(cfd >= 0); - char buf[STRLEN("hello")+1] = {}; - assert_se(read(cfd, buf, sizeof(buf)-1) == sizeof(buf)-1); - ASSERT_STREQ(buf, "hello"); - assert_se(write(cfd, &(const char) { 'z' }, 1) == 1); - - return NULL; -} - -static int fake_pressure_callback(sd_event_source *s, void *userdata) { - int *value = userdata; - const char *d; - - assert_se(s); - assert_se(sd_event_source_get_description(s, &d) >= 0); - - *value *= d[0]; - - log_notice("memory pressure event: %s", d); - - if (*value == 7 * 'f' * 's') - assert_se(sd_event_exit(sd_event_source_get_event(s), 0) >= 0); - - return 0; -} - -TEST(fake_pressure) { - _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *ef = NULL; - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - _cleanup_free_ char *j = NULL, *k = NULL; - _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; - _cleanup_close_ int fifo_fd = -EBADF, socket_fd = -EBADF; - union sockaddr_union sa; - pthread_t th; - int value = 7; - - assert_se(sd_event_default(&e) >= 0); - - assert_se(mkdtemp_malloc(NULL, &tmp) >= 0); - - assert_se(j = path_join(tmp, "fifo")); - assert_se(mkfifo(j, 0600) >= 0); - fifo_fd = open(j, O_CLOEXEC|O_RDWR|O_NONBLOCK); - assert_se(fifo_fd >= 0); - - assert_se(k = path_join(tmp, "sock")); - socket_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); - assert_se(socket_fd >= 0); - assert_se(sockaddr_un_set_path(&sa.un, k) >= 0); - assert_se(bind(socket_fd, &sa.sa, sockaddr_un_len(&sa.un)) >= 0); - assert_se(listen(socket_fd, 1) >= 0); - - /* Ideally we'd just allocate this on the stack, but AddressSanitizer doesn't like it if threads - * access each other's stack */ - struct fake_pressure_context *fp = new(struct fake_pressure_context, 1); - assert_se(fp); - *fp = (struct fake_pressure_context) { - .fifo_fd = fifo_fd, - .socket_fd = socket_fd, - }; - - assert_se(pthread_create(&th, NULL, fake_pressure_thread, TAKE_PTR(fp)) == 0); - - assert_se(setenv("MEMORY_PRESSURE_WATCH", j, /* override= */ true) >= 0); - assert_se(unsetenv("MEMORY_PRESSURE_WRITE") >= 0); - - assert_se(sd_event_add_memory_pressure(e, &es, fake_pressure_callback, &value) >= 0); - assert_se(sd_event_source_set_description(es, "fifo event source") >= 0); - - assert_se(setenv("MEMORY_PRESSURE_WATCH", k, /* override= */ true) >= 0); - assert_se(setenv("MEMORY_PRESSURE_WRITE", "aGVsbG8K", /* override= */ true) >= 0); - - assert_se(sd_event_add_memory_pressure(e, &ef, fake_pressure_callback, &value) >= 0); - assert_se(sd_event_source_set_description(ef, "socket event source") >= 0); - - assert_se(sd_event_loop(e) >= 0); - - assert_se(value == 7 * 'f' * 's'); - - assert_se(pthread_join(th, NULL) == 0); -} - -struct real_pressure_context { - sd_event_source *pid; -}; - -static int real_pressure_callback(sd_event_source *s, void *userdata) { - struct real_pressure_context *c = ASSERT_PTR(userdata); - const char *d; - - assert_se(s); - assert_se(sd_event_source_get_description(s, &d) >= 0); - - log_notice("real_memory pressure event: %s", d); - - sd_event_trim_memory(); - - assert_se(c->pid); - assert_se(sd_event_source_send_child_signal(c->pid, SIGKILL, NULL, 0) >= 0); - c->pid = NULL; - - return 0; -} - -#define MMAP_SIZE (10 * 1024 * 1024) - -_noreturn_ static void real_pressure_eat_memory(int pipe_fd) { - size_t ate = 0; - - /* Allocates and touches 10M at a time, until runs out of memory */ - - char x; - assert_se(read(pipe_fd, &x, 1) == 1); /* Wait for the GO! */ - - for (;;) { - void *p; - - p = mmap(NULL, MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); - assert_se(p != MAP_FAILED); - - log_info("Eating another %s.", FORMAT_BYTES(MMAP_SIZE)); - - memset(p, random_u32() & 0xFF, MMAP_SIZE); - ate += MMAP_SIZE; - - log_info("Ate %s in total.", FORMAT_BYTES(ate)); - - usleep_safe(50 * USEC_PER_MSEC); - } -} - -static int real_pressure_child_callback(sd_event_source *s, const siginfo_t *si, void *userdata) { - assert_se(s); - assert_se(si); - - log_notice("child dead"); - - assert_se(si->si_signo == SIGCHLD); - assert_se(si->si_status == SIGKILL); - assert_se(si->si_code == CLD_KILLED); - - assert_se(sd_event_exit(sd_event_source_get_event(s), 31) >= 0); - return 0; -} - -TEST(real_pressure) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *cs = NULL; - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_close_pair_ int pipe_fd[2] = EBADF_PAIR; - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - _cleanup_free_ char *scope = NULL; - const char *object; - int r; - - r = sd_bus_open_system(&bus); - if (r < 0) - return (void) log_tests_skipped_errno(r, "can't connect to system bus"); - - assert_se(bus_wait_for_jobs_new(bus, &w) >= 0); - - assert_se(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit") >= 0); - assert_se(asprintf(&scope, "test-%" PRIu64 ".scope", random_u64()) >= 0); - assert_se(sd_bus_message_append(m, "ss", scope, "fail") >= 0); - assert_se(sd_bus_message_open_container(m, 'a', "(sv)") >= 0); - assert_se(sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, 0) >= 0); - assert_se(sd_bus_message_append(m, "(sv)", "MemoryAccounting", "b", true) >= 0); - assert_se(sd_bus_message_close_container(m) >= 0); - assert_se(sd_bus_message_append(m, "a(sa(sv))", 0) >= 0); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return (void) log_tests_skipped_errno(r, "can't issue transient unit call"); - - assert_se(sd_bus_message_read(reply, "o", &object) >= 0); - - assert_se(bus_wait_for_jobs_one(w, object, /* flags= */ BUS_WAIT_JOBS_LOG_ERROR, /* extra_args= */ NULL) >= 0); - - assert_se(sd_event_default(&e) >= 0); - - assert_se(pipe2(pipe_fd, O_CLOEXEC) >= 0); - - _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; - r = pidref_safe_fork("(eat-memory)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &pidref); - assert_se(r >= 0); - if (r == 0) { - real_pressure_eat_memory(pipe_fd[0]); - _exit(EXIT_SUCCESS); - } - - assert_se(event_add_child_pidref(e, &cs, &pidref, WEXITED, real_pressure_child_callback, NULL) >= 0); - assert_se(sd_event_source_set_child_process_own(cs, true) >= 0); - - assert_se(unsetenv("MEMORY_PRESSURE_WATCH") >= 0); - assert_se(unsetenv("MEMORY_PRESSURE_WRITE") >= 0); - - struct real_pressure_context context = { - .pid = cs, - }; - - r = sd_event_add_memory_pressure(e, &es, real_pressure_callback, &context); - if (r < 0) - return (void) log_tests_skipped_errno(r, "can't allocate memory pressure fd"); - - assert_se(sd_event_source_set_description(es, "real pressure event source") >= 0); - assert_se(sd_event_source_set_memory_pressure_type(es, "some") == 0); - assert_se(sd_event_source_set_memory_pressure_type(es, "full") > 0); - assert_se(sd_event_source_set_memory_pressure_type(es, "full") == 0); - assert_se(sd_event_source_set_memory_pressure_type(es, "some") > 0); - assert_se(sd_event_source_set_memory_pressure_type(es, "some") == 0); - assert_se(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, USEC_PER_SEC) > 0); - assert_se(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, USEC_PER_SEC) == 0); - assert_se(sd_event_source_set_enabled(es, SD_EVENT_ONESHOT) >= 0); - - _cleanup_free_ char *uo = NULL; - assert_se(uo = unit_dbus_path_from_name(scope)); - - uint64_t mcurrent = UINT64_MAX; - assert_se(sd_bus_get_property_trivial(bus, "org.freedesktop.systemd1", uo, "org.freedesktop.systemd1.Scope", "MemoryCurrent", &error, 't', &mcurrent) >= 0); - - printf("current: %" PRIu64 "\n", mcurrent); - if (mcurrent == UINT64_MAX) - return (void) log_tests_skipped_errno(r, "memory accounting not available"); - - m = sd_bus_message_unref(m); - - assert_se(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "SetUnitProperties") >= 0); - assert_se(sd_bus_message_append(m, "sb", scope, true) >= 0); - assert_se(sd_bus_message_open_container(m, 'a', "(sv)") >= 0); - assert_se(sd_bus_message_append(m, "(sv)", "MemoryHigh", "t", mcurrent + (15 * 1024 * 1024)) >= 0); - assert_se(sd_bus_message_append(m, "(sv)", "MemoryMax", "t", mcurrent + (50 * 1024 * 1024)) >= 0); - assert_se(sd_bus_message_close_container(m) >= 0); - - assert_se(sd_bus_call(bus, m, 0, NULL, NULL) >= 0); - - /* Generate some memory allocations via mempool */ -#define NN (1024) - Hashmap **h = new(Hashmap*, NN); - for (int i = 0; i < NN; i++) - h[i] = hashmap_new(NULL); - for (int i = 0; i < NN; i++) - hashmap_free(h[i]); - free(h); - - /* Now start eating memory */ - assert_se(write(pipe_fd[1], &(const char) { 'x' }, 1) == 1); - - assert_se(sd_event_loop(e) >= 0); - int ex = 0; - assert_se(sd_event_get_exit_code(e, &ex) >= 0); - assert_se(ex == 31); -} - -static int outro(void) { - hashmap_trim_pools(); - return 0; -} - -DEFINE_TEST_MAIN_FULL(LOG_DEBUG, NULL, outro); diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c index 899f8f635103c..7497596cc56fc 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -213,7 +213,7 @@ TEST(protect_kernel_logs) { return; } - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { (void) log_tests_skipped("libmount support not compiled in"); return; diff --git a/src/test/test-netlink-manual.c b/src/test/test-netlink-manual.c index 7934aa879d78e..d6f140e0aac7d 100644 --- a/src/test/test-netlink-manual.c +++ b/src/test/test-netlink-manual.c @@ -15,9 +15,9 @@ static int load_module(const char *mod_name) { struct kmod_list *l; int r; - r = dlopen_libkmod(); + r = dlopen_libkmod(LOG_ERR); if (r < 0) - return log_error_errno(r, "Failed to load libkmod: %m"); + return r; ctx = sym_kmod_new(NULL, NULL); if (!ctx) diff --git a/src/test/test-options.c b/src/test/test-options.c new file mode 100644 index 0000000000000..49e1b3069fc91 --- /dev/null +++ b/src/test/test-options.c @@ -0,0 +1,1354 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "options.h" +#include "strv.h" +#include "tests.h" + +typedef struct Entry { + const char *long_code; + const char *argument; + char short_code; +} Entry; + +static void test_option_parse_one( + char **argv, + const Option options[], + const Entry *entries, + char **remaining, + OptionParserMode mode) { + + _cleanup_free_ char *joined = strv_join(argv, ", "); + log_debug("/* %s(%s) */", __func__, joined); + + _cleanup_free_ char *saved_argv0 = NULL; + ASSERT_NOT_NULL(saved_argv0 = strdup(argv[0])); + + int argc = strv_length(argv); + size_t i = 0, n_options = 0, n_entries = 0; + + for (const Option *o = options; o->short_code != 0 || o->long_code; o++) + n_options++; + + for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) + n_entries++; + + OptionParser state = { argc, argv, mode }; + const Option *opt; + const char *arg; + for (int c; (c = option_parse(options, options + n_options, &state, &opt, &arg)) != 0; ) { + ASSERT_OK(c); + ASSERT_NOT_NULL(opt); + + log_debug("%c %s: %s=%s", + opt->short_code != 0 ? opt->short_code : ' ', + opt->long_code ?: "", + strnull(opt->metavar), strnull(arg)); + + ASSERT_LT(i, n_entries); + if (entries[i].long_code) + ASSERT_TRUE(streq_ptr(opt->long_code, entries[i].long_code)); + if (entries[i].short_code != 0) + ASSERT_EQ(opt->short_code, entries[i].short_code); + ASSERT_TRUE(streq_ptr(arg, entries[i].argument)); + i++; + } + + ASSERT_EQ(i, n_entries); + + char **args = option_parser_get_args(&state); + ASSERT_TRUE(strv_equal(args, remaining)); + ASSERT_STREQ(argv[0], saved_argv0); + + ASSERT_EQ(option_parser_get_n_args(&state), strv_length(remaining)); +} + +static void test_option_invalid_one( + char **argv, + const Option options[static 1]) { + + _cleanup_free_ char *joined = strv_join(argv, ", "); + log_debug("/* %s(%s) */", __func__, joined); + + _cleanup_free_ char *saved_argv0 = NULL; + ASSERT_NOT_NULL(saved_argv0 = strdup(argv[0])); + + int argc = strv_length(argv); + + size_t n_options = 0; + for (const Option *o = options; o->short_code != 0 || o->long_code; o++) + n_options++; + + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + + int c = option_parse(options, options + n_options, &state, &opt, &arg); + ASSERT_ERROR(c, EINVAL); +} + +TEST(option_parse) { + static const Option options[] = { + { 1, .short_code = 'h', .long_code = "help" }, + { 2, .long_code = "version" }, + { 3, .short_code = 'r', .long_code = "required1", .metavar = "ARG" }, + { 4, .long_code = "required2", .metavar = "ARG" }, + { 5, .short_code = 'o', .long_code = "optional1", .metavar = "ARG", .flags = OPTION_OPTIONAL_ARG }, + { 6, .long_code = "optional2", .metavar = "ARG", .flags = OPTION_OPTIONAL_ARG }, + {} + }; + + test_option_parse_one(STRV_MAKE("arg0"), + options, + NULL, + NULL, + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "string3", + "string4"), + options, + NULL, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "--", + "string1", + "--help", + "-h", + "string4"), + options, + NULL, + STRV_MAKE("string1", + "--help", + "-h", + "string4"), + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "--", + "--", + "string4"), + options, + NULL, + STRV_MAKE("string1", + "string2", + "--", + "string4"), + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "string3", + "string4", + "--"), + options, + NULL, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "--help"), + options, + (Entry[]) { + { "help" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "--help", + "string1", + "--help"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "--help"), + OPTION_PARSER_STOP_AT_FIRST_NONOPTION); + + test_option_parse_one(STRV_MAKE("arg0", + "-h"), + options, + (Entry[]) { + { "help" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "--help", + "string1", + "string2", + "string3", + "string4"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "-h", + "string1", + "string2", + "string3", + "string4"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "--help", + "string3", + "string4"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "-h", + "string3", + "string4"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "string3", + "string4", + "--help"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "string3", + "string4", + "-h"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "--required1", "reqarg1"), + options, + (Entry[]) { + { "required1", "reqarg1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "-r", "reqarg1"), + options, + (Entry[]) { + { "required1", "reqarg1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "-r", "reqarg1"), + options, + (Entry[]) { + { "required1", "reqarg1" }, + {} + }, + STRV_MAKE("string1", + "string2"), + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "-r", "reqarg1"), + options, + NULL, + STRV_MAKE("string1", + "string2", + "-r", "reqarg1"), + OPTION_PARSER_STOP_AT_FIRST_NONOPTION); + + test_option_parse_one(STRV_MAKE("arg0", + "--optional1=optarg1"), + options, + (Entry[]) { + { "optional1", "optarg1" }, + {} + }, + NULL, + OPTION_PARSER_STOP_AT_FIRST_NONOPTION); + + test_option_parse_one(STRV_MAKE("arg0", + "--optional1", "string1"), + options, + (Entry[]) { + { "optional1", NULL }, + {} + }, + STRV_MAKE("string1"), + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "-ooptarg1"), + options, + (Entry[]) { + { "optional1", "optarg1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "-o", "string1"), + options, + (Entry[]) { + { "optional1", NULL }, + {} + }, + STRV_MAKE("string1"), + OPTION_PARSER_NORMAL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "--help", + "--version", + "string2", + "--required1", "reqarg1", + "--required2", "reqarg2", + "--required1=reqarg3", + "--required2=reqarg4", + "string3", + "--optional1", "string4", + "--optional2", "string5", + "--optional1=optarg1", + "--optional2=optarg2", + "-h", + "-r", "reqarg5", + "-rreqarg6", + "-ooptarg3", + "-o", + "string6", + "-o", + "-h", + "-o", + "--help", + "string7", + "-hooptarg4", + "-hrreqarg6", + "--", + "--help", + "--required1", + "--optional1"), + options, + (Entry[]) { + { "help" }, + { "version" }, + { "required1", "reqarg1" }, + { "required2", "reqarg2" }, + { "required1", "reqarg3" }, + { "required2", "reqarg4" }, + { "optional1", NULL }, + { "optional2", NULL, }, + { "optional1", "optarg1" }, + { "optional2", "optarg2" }, + { "help" }, + { "required1", "reqarg5" }, + { "required1", "reqarg6" }, + { "optional1", "optarg3" }, + { "optional1", NULL }, + { "optional1", NULL }, + { "help" }, + { "optional1", NULL }, + { "help" }, + { "help" }, + { "optional1", "optarg4" }, + { "help" }, + { "required1", "reqarg6" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4", + "string5", + "string6", + "string7", + "--help", + "--required1", + "--optional1"), + OPTION_PARSER_NORMAL); +} + +TEST(option_stops_parsing) { + static const Option options[] = { + { 1, .short_code = 'h', .long_code = "help" }, + { 2, .long_code = "version" }, + { 3, .short_code = 'r', .long_code = "required", .metavar = "ARG" }, + { 4, .long_code = "exec", .flags = OPTION_STOPS_PARSING }, + {} + }; + + /* --exec stops parsing, subsequent --help is positional */ + test_option_parse_one(STRV_MAKE("arg0", + "--exec", + "--help", + "foo"), + options, + (Entry[]) { + { "exec" }, + {} + }, + STRV_MAKE("--help", + "foo"), + OPTION_PARSER_NORMAL); + + /* Options before --exec are still parsed */ + test_option_parse_one(STRV_MAKE("arg0", + "--help", + "--exec", + "--version", + "bar"), + options, + (Entry[]) { + { "help" }, + { "exec" }, + {} + }, + STRV_MAKE("--version", + "bar"), + OPTION_PARSER_NORMAL); + + /* --exec with no trailing args */ + test_option_parse_one(STRV_MAKE("arg0", + "--exec"), + options, + (Entry[]) { + { "exec" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* --exec after positional args */ + test_option_parse_one(STRV_MAKE("arg0", + "pos1", + "--exec", + "--help", + "--required", "val"), + options, + (Entry[]) { + { "exec" }, + {} + }, + STRV_MAKE("pos1", + "--help", + "--required", + "val"), + OPTION_PARSER_NORMAL); + + /* "--" after --exec: "--" is still consumed as end-of-options marker. This is needed for + * backwards compatibility, systemd-dissect implemented this behaviour. But also, it makes + * sense: we're unlikely to ever want to specify "--" as the first argument of whatever + * sequence, but the user may want to specify it for clarity. */ + test_option_parse_one(STRV_MAKE("arg0", + "--exec", + "--", + "--help"), + options, + (Entry[]) { + { "exec" }, + {} + }, + STRV_MAKE("--help"), + OPTION_PARSER_NORMAL); + + /* "--" before --exec: "--" terminates first, --exec is positional */ + test_option_parse_one(STRV_MAKE("arg0", + "--", + "--exec", + "--help"), + options, + NULL, + STRV_MAKE("--exec", + "--help"), + OPTION_PARSER_NORMAL); + + /* Multiple options then --exec then more option-like args */ + test_option_parse_one(STRV_MAKE("arg0", + "--help", + "-r", "val1", + "--exec", + "-h", + "--required", "val2"), + options, + (Entry[]) { + { "help" }, + { "required", "val1" }, + { "exec" }, + {} + }, + STRV_MAKE("-h", + "--required", + "val2"), + OPTION_PARSER_NORMAL); +} + +TEST(option_group_marker) { + static const Option options[] = { + { 1, .short_code = 'h', .long_code = "help" }, + { 2, .long_code = "version" }, + { 0, .long_code = "AdvancedGroup", .flags = OPTION_GROUP_MARKER }, + { 3, .long_code = "debug" }, + { 4, .long_code = "Advance" }, /* prefix match with the group */ + { 5, .long_code = "defilbrilate" }, + {} + }; + + /* Group markers are skipped by the parser — only real options are returned */ + test_option_parse_one(STRV_MAKE("arg0", + "--help", + "--debug"), + options, + (Entry[]) { + { "help" }, + { "debug" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* Check that group marker name is ignored */ + test_option_parse_one(STRV_MAKE("arg0", + "--debug", + "--version"), + options, + (Entry[]) { + { "debug" }, + { "version" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* Verify that the group marker is not mistaken for an option */ + test_option_invalid_one(STRV_MAKE("arg0", + "--AdvancedGroup"), + options); + + /* Verify that the group marker is not mistaken for an option */ + test_option_invalid_one(STRV_MAKE("arg0", + "--AdvancedGroup=2"), + options); + + /* Verify that the group marker is not mistaken for an option, prefix match */ + test_option_invalid_one(STRV_MAKE("arg0", + "--Advanced"), + options); + + /* Check that group marker name is ignored */ + test_option_parse_one(STRV_MAKE("arg0", + "--Advance", + "--Advan"), /* prefix match with unique prefix */ + options, + (Entry[]) { + { "Advance" }, + { "Advance" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* Partial match with multiple candidates */ + test_option_invalid_one(STRV_MAKE("arg0", + "--de"), + options); +} + +TEST(option_optional_arg) { + static const Option options[] = { + { 1, .short_code = 'o', .long_code = "output", .metavar = "FILE", .flags = OPTION_OPTIONAL_ARG }, + { 2, .short_code = 'h', .long_code = "help" }, + {} + }; + + /* Long option with = gets the argument */ + test_option_parse_one(STRV_MAKE("arg0", + "--output=foo.txt"), + options, + (Entry[]) { + { "output", "foo.txt" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* Long option without = does NOT consume the next arg */ + test_option_parse_one(STRV_MAKE("arg0", + "--output", "foo.txt"), + options, + (Entry[]) { + { "output", NULL }, + {} + }, + STRV_MAKE("foo.txt"), + OPTION_PARSER_NORMAL); + + /* Short option with inline arg */ + test_option_parse_one(STRV_MAKE("arg0", + "-ofoo.txt"), + options, + (Entry[]) { + { "output", "foo.txt" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* Short option without inline arg does NOT consume the next arg */ + test_option_parse_one(STRV_MAKE("arg0", + "-o", "foo.txt"), + options, + (Entry[]) { + { "output", NULL }, + {} + }, + STRV_MAKE("foo.txt"), + OPTION_PARSER_NORMAL); + + /* Optional arg option at end of argv */ + test_option_parse_one(STRV_MAKE("arg0", + "--output"), + options, + (Entry[]) { + { "output", NULL }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* Mixed: optional arg with other options */ + test_option_parse_one(STRV_MAKE("arg0", + "--help", + "--output=bar", + "--help"), + options, + (Entry[]) { + { "help" }, + { "output", "bar" }, + { "help" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* Short combo: -ho (h then o with no arg) */ + test_option_parse_one(STRV_MAKE("arg0", + "-ho", "pos1"), + options, + (Entry[]) { + { "help" }, + { "output", NULL }, + {} + }, + STRV_MAKE("pos1"), + OPTION_PARSER_NORMAL); + + /* Short combo: -hobar (h then o with inline arg "bar") */ + test_option_parse_one(STRV_MAKE("arg0", + "-hobar"), + options, + (Entry[]) { + { "help" }, + { "output", "bar" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); +} + +/* Test the OPTION, OPTION_LONG, OPTION_SHORT, OPTION_FULL, OPTION_GROUP macros + * by using them in a FOREACH_OPTION_FULL switch, as they would be used in real code. */ + +static void test_macros_parse_one( + char **argv, + const Entry *entries, + char **remaining, + OptionParserMode mode) { + + _cleanup_free_ char *joined = strv_join(argv, ", "); + log_debug("/* %s(%s) */", __func__, joined); + + _cleanup_free_ char *saved_argv0 = NULL; + ASSERT_NOT_NULL(saved_argv0 = strdup(argv[0])); + + int argc = strv_length(argv); + size_t i = 0, n_entries = 0; + + for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) + n_entries++; + + OptionParser state = { argc, argv, mode }; + const Option *opt; + const char *arg; + + FOREACH_OPTION_FULL(&state, c, &opt, &arg, ASSERT_TRUE(false)) { + log_debug("%c %s: %s=%s", + opt->short_code != 0 ? opt->short_code : ' ', + opt->long_code ?: "", + strnull(opt->metavar), strnull(arg)); + + ASSERT_LT(i, n_entries); + if (entries[i].long_code) + ASSERT_TRUE(streq_ptr(opt->long_code, entries[i].long_code)); + if (entries[i].short_code != 0) + ASSERT_EQ(opt->short_code, entries[i].short_code); + ASSERT_TRUE(streq_ptr(arg, entries[i].argument)); + + if (streq_ptr(entries[i].long_code, "optional2")) + ASSERT_EQ(opt->data, 666u); + else + ASSERT_EQ(opt->data, 0u); + + i++; + + switch (c) { + + /* OPTION: short + long, no arg */ + OPTION('h', "help", NULL, "Show this help"): + break; + + /* OPTION_LONG: long only, no arg */ + OPTION_LONG("version", NULL, "Show package version"): + break; + + /* OPTION_SHORT: short only, no arg */ + OPTION_SHORT('v', NULL, "Enable verbose mode"): + break; + + /* OPTION: short + long, required arg */ + OPTION('r', "required", "ARG", "Required arg option"): + break; + + /* OPTION_FULL: optional arg */ + OPTION_FULL(OPTION_OPTIONAL_ARG, 'o', "optional", "ARG", "Optional arg option"): + break; + + /* OPTION_FULL_DATA: optional arg */ + OPTION_FULL_DATA(OPTION_OPTIONAL_ARG, 'O', "optional2", "ARG", 666, "Optional arg option"): + break; + + /* OPTION_FULL: stops parsing */ + OPTION_FULL(OPTION_STOPS_PARSING, 0, "exec", NULL, "Stop parsing after this"): + break; + + /* OPTION_GROUP: group marker (never returned by parser) */ + OPTION_GROUP("Advanced"): {} + + /* OPTION_LONG: long only, in the "Advanced" group */ + OPTION_LONG("debug", NULL, "Enable debug mode"): + break; + + OPTION_POSITIONAL: + break; + + default: + log_error("Unexpected option id: %d", c); + ASSERT_TRUE(false); + } + } + + ASSERT_EQ(i, n_entries); + + char **args = option_parser_get_args(&state); + ASSERT_TRUE(strv_equal(args, remaining)); + ASSERT_STREQ(argv[0], saved_argv0); +} + +TEST(option_macros) { + /* OPTION: long form */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help"), + (Entry[]) { + { "help" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* OPTION: short form */ + test_macros_parse_one(STRV_MAKE("arg0", + "-h"), + (Entry[]) { + { "help" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* OPTION_LONG: only accessible via long form */ + test_macros_parse_one(STRV_MAKE("arg0", + "--version"), + (Entry[]) { + { "version" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* OPTION_SHORT: only accessible via short form */ + test_macros_parse_one(STRV_MAKE("arg0", + "-v"), + (Entry[]) { + { .short_code = 'v' }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* OPTION with required arg: long --required=ARG */ + test_macros_parse_one(STRV_MAKE("arg0", + "--required=val1"), + (Entry[]) { + { "required", "val1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* OPTION with required arg: long --required ARG */ + test_macros_parse_one(STRV_MAKE("arg0", + "--required", "val1"), + (Entry[]) { + { "required", "val1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* OPTION with required arg: short -r ARG */ + test_macros_parse_one(STRV_MAKE("arg0", + "-r", "val1"), + (Entry[]) { + { "required", "val1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* OPTION with required arg: short -rARG */ + test_macros_parse_one(STRV_MAKE("arg0", + "-rval1"), + (Entry[]) { + { "required", "val1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* OPTION_FULL with OPTION_OPTIONAL_ARG: long with = */ + test_macros_parse_one(STRV_MAKE("arg0", + "--optional=val1"), + (Entry[]) { + { "optional", "val1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* OPTION_FULL with OPTION_OPTIONAL_ARG: long without = doesn't consume next */ + test_macros_parse_one(STRV_MAKE("arg0", + "--optional", "pos1"), + (Entry[]) { + { "optional", NULL }, + {} + }, + STRV_MAKE("pos1"), + OPTION_PARSER_NORMAL); + + /* OPTION_FULL with OPTION_OPTIONAL_ARG: short inline */ + test_macros_parse_one(STRV_MAKE("arg0", + "-oval1"), + (Entry[]) { + { "optional", "val1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* OPTION_FULL with OPTION_OPTIONAL_ARG: short without inline */ + test_macros_parse_one(STRV_MAKE("arg0", + "-o", "pos1"), + (Entry[]) { + { "optional", NULL }, + {} + }, + STRV_MAKE("pos1"), + OPTION_PARSER_NORMAL); + + /* OPTION_FULL with OPTION_STOPS_PARSING: stops further option parsing */ + test_macros_parse_one(STRV_MAKE("arg0", + "--exec", + "--help", + "--version"), + (Entry[]) { + { "exec" }, + {} + }, + STRV_MAKE("--help", + "--version"), + OPTION_PARSER_NORMAL); + + /* OPTION_STOPS_PARSING: options before are still parsed */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help", + "--exec", + "-h", + "--debug"), + (Entry[]) { + { "help" }, + { "exec" }, + {} + }, + STRV_MAKE("-h", + "--debug"), + OPTION_PARSER_NORMAL); + + /* OPTION_STOPS_PARSING with "--": "--" after exec is still consumed */ + test_macros_parse_one(STRV_MAKE("arg0", + "--exec", + "--", + "--help"), + (Entry[]) { + { "exec" }, + {} + }, + STRV_MAKE("--help"), + OPTION_PARSER_NORMAL); + + /* OPTION_STOPS_PARSING with "--": "--" before exec takes precedence */ + test_macros_parse_one(STRV_MAKE("arg0", + "--", + "--exec", + "--help"), + (Entry[]) { + {} + }, + STRV_MAKE("--exec", + "--help"), + OPTION_PARSER_NORMAL); + + /* OPTION_GROUP: group marker is transparent to parsing, --debug in Advanced group works */ + test_macros_parse_one(STRV_MAKE("arg0", + "--debug"), + (Entry[]) { + { "debug" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* Mixed: all macro types together */ + test_macros_parse_one(STRV_MAKE("arg0", + "pos1", + "-h", + "--version", + "-v", + "--required=rval", + "--optional=oval", + "--optional2=oval", + "--debug", + "pos2", + "-o", + "--help"), + (Entry[]) { + { "help" }, + { "version" }, + { .short_code = 'v' }, + { "required", "rval" }, + { "optional", "oval" }, + { "optional2", "oval" }, + { "debug" }, + { "optional", NULL }, + { "help" }, + {} + }, + STRV_MAKE("pos1", + "pos2"), + OPTION_PARSER_NORMAL); + + /* Short option combos with macros: -hv (help + verbose) */ + test_macros_parse_one(STRV_MAKE("arg0", + "-hv"), + (Entry[]) { + { "help" }, + { .short_code = 'v' }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* Short option combo with required arg: -hrval (help + required with arg "val") */ + test_macros_parse_one(STRV_MAKE("arg0", + "-hrval"), + (Entry[]) { + { "help" }, + { "required", "val" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* Short option combo with optional arg: -hoval (help + optional with arg "val") */ + test_macros_parse_one(STRV_MAKE("arg0", + "-hoval"), + (Entry[]) { + { "help" }, + { "optional", "val" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* OPTION_STOPS_PARSING then "--": "--" is still consumed after exec */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help", + "--exec", + "--", + "--version", + "-h"), + (Entry[]) { + { "help" }, + { "exec" }, + {} + }, + STRV_MAKE("--version", + "-h"), + OPTION_PARSER_NORMAL); + + /* OPTION_STOPS_PARSING then later "--": "--" is not consumed */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help", + "--exec", + "--version", + "--", + "-h"), + (Entry[]) { + { "help" }, + { "exec" }, + {} + }, + STRV_MAKE("--version", + "--", + "-h"), + OPTION_PARSER_NORMAL); + + /* OPTION_STOPS_PARSING then "--" twice: second "--" is not consumed */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help", + "--exec", + "--", + "--", + "--version", + "-h"), + (Entry[]) { + { "help" }, + { "exec" }, + {} + }, + STRV_MAKE("--", + "--version", + "-h"), + OPTION_PARSER_NORMAL); + + /* Basic OPTION_POSITIONAL use */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help", + "arg1", + "--debug", + "arg2"), + (Entry[]) { + { "help" }, + { "(positional)", "arg1" }, + { "debug" }, + { "(positional)", "arg2" }, + {} + }, + NULL, + OPTION_PARSER_RETURN_POSITIONAL_ARGS); + + /* OPTION_POSITIONAL combined with OPTION_STOPS_PARSING */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help", + "arg1", + "--exec", + "arg2"), + (Entry[]) { + { "help" }, + { "(positional)", "arg1" }, + { "exec" }, + {} + }, + STRV_MAKE("arg2"), + OPTION_PARSER_RETURN_POSITIONAL_ARGS); +} + +/* Test the pattern used by nspawn's --user: an optional-arg option that also + * peeks at the next arg to handle legacy "space-separated" form. */ +TEST(option_optional_arg_consume) { + static const Option options[] = { + { 1, .short_code = 'h', .long_code = "help" }, + { 2, .long_code = "user", .metavar = "NAME", .flags = OPTION_OPTIONAL_ARG }, + { 3, .short_code = 'u', .long_code = "uid", .metavar = "USER" }, + {} + }; + + /* --user=NAME: optional arg provided via = */ + test_option_parse_one(STRV_MAKE("arg0", + "--user=root"), + options, + (Entry[]) { + { "user", "root" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* --user without arg: next arg is an option, so no consumption */ + test_option_parse_one(STRV_MAKE("arg0", + "--user", + "--help"), + options, + (Entry[]) { + { "user", NULL }, + { "help" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL); + + /* --user without arg: next arg is positional (doesn't start with -). + * The option parser returns NULL for the arg. The caller would then + * use option_parser_next_arg/consume_next_arg to grab it. */ + { + char **argv = STRV_MAKE("arg0", "--user", "someuser", "pos1"); + int argc = strv_length(argv); + + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + + ASSERT_OK_POSITIVE(option_parse(options, options + 3, &state, &opt, &arg)); + ASSERT_STREQ(opt->long_code, "user"); + ASSERT_NULL(arg); + ASSERT_STREQ(option_parser_next_arg(&state), "someuser"); + ASSERT_STREQ(option_parser_consume_next_arg(&state), "someuser"); + + ASSERT_EQ(option_parse(options, options + 3, &state, &opt, &arg), 0); + + ASSERT_TRUE(strv_equal(option_parser_get_args(&state), STRV_MAKE("pos1"))); + } + + /* --user at end of args: no next arg, so scope mode */ + { + char **argv = STRV_MAKE("arg0", "--user"); + int argc = strv_length(argv); + + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + + ASSERT_OK_POSITIVE(option_parse(options, options + 3, &state, &opt, &arg)); + ASSERT_STREQ(opt->long_code, "user"); + ASSERT_NULL(arg); + ASSERT_NULL(option_parser_next_arg(&state)); + ASSERT_NULL(option_parser_consume_next_arg(&state)); + + ASSERT_EQ(option_parse(options, options + 3, &state, &opt, &arg), 0); + + ASSERT_TRUE(strv_isempty(option_parser_get_args(&state))); + } + + /* --user followed by -u (option): scope mode, -u gets its own processing */ + { + char **argv = STRV_MAKE("arg0", "--user", "-u", "nobody"); + int argc = strv_length(argv); + + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + + ASSERT_OK_POSITIVE(option_parse(options, options + 3, &state, &opt, &arg)); + ASSERT_STREQ(opt->long_code, "user"); + ASSERT_NULL(arg); + ASSERT_STREQ(option_parser_next_arg(&state), "-u"); + + ASSERT_OK_POSITIVE(option_parse(options, options + 3, &state, &opt, &arg)); + ASSERT_STREQ(opt->long_code, "uid"); + ASSERT_STREQ(arg, "nobody"); + ASSERT_NULL(option_parser_next_arg(&state)); + ASSERT_NULL(option_parser_consume_next_arg(&state)); + + ASSERT_EQ(option_parse(options, options + 3, &state, &opt, &arg), 0); + + ASSERT_TRUE(strv_isempty(option_parser_get_args(&state))); + } + + /* "Functional test": --user followed by -u (option): scope mode, -u gets its own processing, + * handled like in a real option parser. */ + { + char **argv = STRV_MAKE("arg0", "--user", "-u", "nobody", "nogroup", "--user=nobody", "--user"); + int argc = strv_length(argv); + + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + int scope_seen = 0; + int nobody_seen = 0; + + for (int c; (c = option_parse(options, options + 3, &state, &opt, &arg)) != 0; ) { + ASSERT_OK(c); + + if (streq_ptr(opt->long_code, "user")) { + if (!arg) { + const char *t = option_parser_next_arg(&state); + if (t && t[0] != '-') + arg = option_parser_consume_next_arg(&state); + } + + if (arg) { + ASSERT_STREQ(arg, "nobody"); + nobody_seen ++; + } else + scope_seen ++; + + } else if (streq_ptr(opt->long_code, "uid")) { + ASSERT_STREQ(arg, "nobody"); + nobody_seen ++; + } + } + + ASSERT_EQ(nobody_seen, 2); + ASSERT_EQ(scope_seen, 2); + ASSERT_TRUE(strv_equal(option_parser_get_args(&state), STRV_MAKE("nogroup"))); + } +} + +static void test_option_get_synopsis_one( + const Option *opt, + const char *joiner, + bool show_metavar, + const char *expected) { + log_debug("%s", expected); + _cleanup_free_ char *s = option_get_synopsis(". ", opt, joiner, show_metavar); + ASSERT_STREQ(s, expected); +} + +TEST(option_get_synopsis) { + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "X" }, "/", true, ". -x/--xxx=X"); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "X" }, NULL, true, ". -x --xxx=X"); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "X" }, "/", false, ". -x/--xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "X" }, " ", true, ". -x --xxx=X"); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "X" }, " ", false, ". -x --xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 0, "xxx", "X" }, "+", true, ". --xxx=X" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 0, "xxx", "X" }, "+", false, ". --xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', NULL, "X" }, " ", true, ". -x X" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', NULL, "X" }, "/", false, ". -x" ); + + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "A B" }, "/", true, ". -x/--xxx='A B'"); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "A B" }, " ", true, ". -x --xxx='A B'"); + test_option_get_synopsis_one(&(const Option) { 0, 0, 0, "xxx", "A B" }, "+", true, ". --xxx='A B'" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', NULL, "A B" }, " ", true, ". -x 'A B'" ); + + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "X" }, "/", true, ". -x/--xxx[=X]"); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "X" }, NULL, true, ". -x --xxx[=X]"); + /* Note: --xxx[=] would be silly, so we show --xxx=. It's a corner case. Maybe this should change. */ + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "X" }, "/", false, ". -x/--xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "X" }, " ", true, ". -x --xxx[=X]"); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "X" }, " ", false, ". -x --xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 0, "xxx", "X" }, "+", true, ". --xxx[=X]" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 0, "xxx", "X" }, "+", false, ". --xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', NULL, "X" }, " ", true, ". -x [X]" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', NULL, "X" }, "/", false, ". -x" ); + + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "A B" }, "/", true, ". -x/--xxx[='A B']"); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "A B" }, " ", true, ". -x --xxx[='A B']"); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 0, "xxx", "A B" }, "+", true, ". --xxx[='A B']" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', NULL, "A B" }, " ", true, ". -x ['A B']" ); + + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG | OPTION_HELP_ENTRY | OPTION_STOPS_PARSING, + 'x', "xxx", "A B" }, "/", true, ". -x/--xxx[='A B']"); + + test_option_get_synopsis_one(&(const Option) { 0, OPTION_HELP_ENTRY_VERBATIM, 'u', "special special", "unused" }, "/", true, ". special special"); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_POSITIONAL_ENTRY, 'u', "(fixed)", "unused" }, "/", true, ". (fixed)"); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-path.c b/src/test/test-path.c index d282cfbf89113..8b02f5d0fffa4 100644 --- a/src/test/test-path.c +++ b/src/test/test-path.c @@ -78,7 +78,7 @@ static int _check_states(unsigned line, assert_se(m); assert_se(service); - usec_t end = now(CLOCK_MONOTONIC) + 30 * USEC_PER_SEC; + usec_t end = usec_add(now(CLOCK_MONOTONIC), 30 * USEC_PER_SEC); while (path->state != path_state || service->state != service_state || path->result != PATH_SUCCESS || service->result != SERVICE_SUCCESS) { diff --git a/src/test/test-pressure.c b/src/test/test-pressure.c new file mode 100644 index 0000000000000..318b73e4fd6cc --- /dev/null +++ b/src/test/test-pressure.c @@ -0,0 +1,603 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include +#include + +#include "sd-bus.h" +#include "sd-event.h" + +#include "bus-locator.h" +#include "bus-wait-for-jobs.h" +#include "event-util.h" +#include "fd-util.h" +#include "format-util.h" +#include "hashmap.h" +#include "path-util.h" +#include "pidref.h" +#include "process-util.h" +#include "random-util.h" +#include "rm-rf.h" +#include "signal-util.h" +#include "socket-util.h" +#include "tests.h" +#include "time-util.h" +#include "tmpfile-util.h" +#include "unit-def.h" + +/* Shared infrastructure for fake pressure tests */ + +struct fake_pressure_context { + int fifo_fd; + int socket_fd; +}; + +static void *fake_pressure_thread(void *p) { + _cleanup_free_ struct fake_pressure_context *c = ASSERT_PTR(p); + _cleanup_close_ int cfd = -EBADF; + + usleep_safe(150); + + ASSERT_EQ(write(c->fifo_fd, &(const char) { 'x' }, 1), 1); + + usleep_safe(150); + + cfd = accept4(c->socket_fd, NULL, NULL, SOCK_CLOEXEC); + ASSERT_OK_ERRNO(cfd); + char buf[STRLEN("hello")+1] = {}; + ASSERT_EQ(read(cfd, buf, sizeof(buf)-1), (ssize_t) (sizeof(buf)-1)); + ASSERT_STREQ(buf, "hello"); + ASSERT_EQ(write(cfd, &(const char) { 'z' }, 1), 1); + + return NULL; +} + +static int fake_pressure_callback(sd_event_source *s, void *userdata) { + int *value = userdata; + const char *d; + + ASSERT_NOT_NULL(s); + ASSERT_OK(sd_event_source_get_description(s, &d)); + + *value *= d[0]; + + log_notice("pressure event: %s", d); + + if (*value == 7 * 'f' * 's') + ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 0)); + + return 0; +} + +typedef int (*event_add_pressure_t)(sd_event *, sd_event_source **, sd_event_handler_t, void *); + +static void test_fake_pressure( + const char *resource, + event_add_pressure_t add_pressure) { + + _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *ef = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; + _cleanup_close_ int fifo_fd = -EBADF, socket_fd = -EBADF; + union sockaddr_union sa; + pthread_t th; + int value = 7; + + _cleanup_free_ char *resource_upper = ASSERT_NOT_NULL(strdup(resource)); + ascii_strupper(resource_upper); + + _cleanup_free_ char *env_watch = ASSERT_NOT_NULL(strjoin(resource_upper, "_PRESSURE_WATCH")), + *env_write = ASSERT_NOT_NULL(strjoin(resource_upper, "_PRESSURE_WRITE")); + + ASSERT_OK(sd_event_default(&e)); + + ASSERT_OK(mkdtemp_malloc(NULL, &tmp)); + + _cleanup_free_ char *j = ASSERT_NOT_NULL(path_join(tmp, "fifo")); + ASSERT_OK_ERRNO(mkfifo(j, 0600)); + fifo_fd = open(j, O_CLOEXEC|O_RDWR|O_NONBLOCK); + ASSERT_OK_ERRNO(fifo_fd); + + _cleanup_free_ char *k = ASSERT_NOT_NULL(path_join(tmp, "sock")); + socket_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + ASSERT_OK_ERRNO(socket_fd); + ASSERT_OK(sockaddr_un_set_path(&sa.un, k)); + ASSERT_OK_ERRNO(bind(socket_fd, &sa.sa, sockaddr_un_len(&sa.un))); + ASSERT_OK_ERRNO(listen(socket_fd, 1)); + + /* Ideally we'd just allocate this on the stack, but AddressSanitizer doesn't like it if threads + * access each other's stack */ + struct fake_pressure_context *fp = new(struct fake_pressure_context, 1); + ASSERT_NOT_NULL(fp); + *fp = (struct fake_pressure_context) { + .fifo_fd = fifo_fd, + .socket_fd = socket_fd, + }; + + ASSERT_EQ(pthread_create(&th, NULL, fake_pressure_thread, TAKE_PTR(fp)), 0); + + ASSERT_OK_ERRNO(setenv(env_watch, j, /* override= */ true)); + ASSERT_OK_ERRNO(unsetenv(env_write)); + + ASSERT_OK(add_pressure(e, &es, fake_pressure_callback, &value)); + ASSERT_OK(sd_event_source_set_description(es, "fifo event source")); + + ASSERT_OK_ERRNO(setenv(env_watch, k, /* override= */ true)); + ASSERT_OK_ERRNO(setenv(env_write, "aGVsbG8K", /* override= */ true)); + + ASSERT_OK(add_pressure(e, &ef, fake_pressure_callback, &value)); + ASSERT_OK(sd_event_source_set_description(ef, "socket event source")); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_EQ(value, 7 * 'f' * 's'); + + ASSERT_EQ(pthread_join(th, NULL), 0); +} + +static int fake_pressure_wrapper(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata) { + return sd_event_add_memory_pressure(e, ret, callback, userdata); +} + +TEST(fake_memory_pressure) { + test_fake_pressure("memory", fake_pressure_wrapper); +} + +static int fake_cpu_pressure_wrapper(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata) { + return sd_event_add_cpu_pressure(e, ret, callback, userdata); +} + +TEST(fake_cpu_pressure) { + test_fake_pressure("cpu", fake_cpu_pressure_wrapper); +} + +static int fake_io_pressure_wrapper(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata) { + return sd_event_add_io_pressure(e, ret, callback, userdata); +} + +TEST(fake_io_pressure) { + test_fake_pressure("io", fake_io_pressure_wrapper); +} + +/* Shared infrastructure for real pressure tests */ + +struct real_pressure_context { + sd_event_source *pid; +}; + +static int real_pressure_child_callback(sd_event_source *s, const siginfo_t *si, void *userdata) { + ASSERT_NOT_NULL(s); + ASSERT_NOT_NULL(si); + + log_notice("child dead"); + + ASSERT_EQ(si->si_signo, SIGCHLD); + ASSERT_EQ(si->si_status, SIGKILL); + ASSERT_EQ(si->si_code, CLD_KILLED); + + ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 31)); + return 0; +} + +/* Memory pressure real test */ + +static int real_memory_pressure_callback(sd_event_source *s, void *userdata) { + struct real_pressure_context *c = ASSERT_PTR(userdata); + const char *d; + + ASSERT_NOT_NULL(s); + ASSERT_OK(sd_event_source_get_description(s, &d)); + + log_notice("real memory pressure event: %s", d); + + sd_event_trim_memory(); + + ASSERT_NOT_NULL(c->pid); + ASSERT_OK(sd_event_source_send_child_signal(c->pid, SIGKILL, NULL, 0)); + c->pid = NULL; + + return 0; +} + +#define MMAP_SIZE (10 * 1024 * 1024) + +_noreturn_ static void real_pressure_eat_memory(int pipe_fd) { + size_t ate = 0; + + /* Allocates and touches 10M at a time, until runs out of memory */ + + char x; + ASSERT_EQ(read(pipe_fd, &x, 1), 1); /* Wait for the GO! */ + + for (;;) { + void *p; + + p = mmap(NULL, MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + ASSERT_TRUE(p != MAP_FAILED); + + log_info("Eating another %s.", FORMAT_BYTES(MMAP_SIZE)); + + memset(p, random_u32() & 0xFF, MMAP_SIZE); + ate += MMAP_SIZE; + + log_info("Ate %s in total.", FORMAT_BYTES(ate)); + + usleep_safe(50 * USEC_PER_MSEC); + } +} + +TEST(real_memory_pressure) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *cs = NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_close_pair_ int pipe_fd[2] = EBADF_PAIR; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_free_ char *scope = NULL; + const char *object; + int r; + + if (getuid() == 0) + r = sd_bus_open_system(&bus); + else + r = sd_bus_open_user(&bus); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't connect to bus"); + + ASSERT_OK(bus_wait_for_jobs_new(bus, &w)); + + ASSERT_OK(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit")); + ASSERT_OK(asprintf(&scope, "test-%" PRIu64 ".scope", random_u64())); + ASSERT_OK(sd_bus_message_append(m, "ss", scope, "fail")); + ASSERT_OK(sd_bus_message_open_container(m, 'a', "(sv)")); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, 0)); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "MemoryAccounting", "b", true)); + ASSERT_OK(sd_bus_message_close_container(m)); + ASSERT_OK(sd_bus_message_append(m, "a(sa(sv))", 0)); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't issue transient unit call"); + + ASSERT_OK(sd_bus_message_read(reply, "o", &object)); + + ASSERT_OK(bus_wait_for_jobs_one(w, object, /* flags= */ BUS_WAIT_JOBS_LOG_ERROR, /* extra_args= */ NULL)); + + ASSERT_OK(sd_event_default(&e)); + + ASSERT_OK_ERRNO(pipe2(pipe_fd, O_CLOEXEC)); + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = pidref_safe_fork("(eat-memory)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &pidref); + ASSERT_OK(r); + if (r == 0) { + real_pressure_eat_memory(pipe_fd[0]); + _exit(EXIT_SUCCESS); + } + + ASSERT_OK(event_add_child_pidref(e, &cs, &pidref, WEXITED, real_pressure_child_callback, NULL)); + ASSERT_OK(sd_event_source_set_child_process_own(cs, true)); + + ASSERT_OK_ERRNO(unsetenv("MEMORY_PRESSURE_WATCH")); + ASSERT_OK_ERRNO(unsetenv("MEMORY_PRESSURE_WRITE")); + + struct real_pressure_context context = { + .pid = cs, + }; + + r = sd_event_add_memory_pressure(e, &es, real_memory_pressure_callback, &context); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't allocate memory pressure fd"); + + ASSERT_OK(sd_event_source_set_description(es, "real pressure event source")); + ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_type(es, "some")); + ASSERT_OK_POSITIVE(sd_event_source_set_memory_pressure_type(es, "full")); + ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_type(es, "full")); + ASSERT_OK_POSITIVE(sd_event_source_set_memory_pressure_type(es, "some")); + ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_type(es, "some")); + /* Unprivileged writes require a minimum of 2s otherwise the kernel will refuse the write. */ + ASSERT_OK_POSITIVE(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); + ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); + ASSERT_OK(sd_event_source_set_enabled(es, SD_EVENT_ONESHOT)); + + _cleanup_free_ char *uo = NULL; + ASSERT_NOT_NULL(uo = unit_dbus_path_from_name(scope)); + + uint64_t mcurrent = UINT64_MAX; + ASSERT_OK(sd_bus_get_property_trivial(bus, "org.freedesktop.systemd1", uo, "org.freedesktop.systemd1.Scope", "MemoryCurrent", &error, 't', &mcurrent)); + + printf("current: %" PRIu64 "\n", mcurrent); + if (mcurrent == UINT64_MAX) + return (void) log_tests_skipped_errno(r, "memory accounting not available"); + + m = sd_bus_message_unref(m); + + ASSERT_OK(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "SetUnitProperties")); + ASSERT_OK(sd_bus_message_append(m, "sb", scope, true)); + ASSERT_OK(sd_bus_message_open_container(m, 'a', "(sv)")); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "MemoryHigh", "t", mcurrent + (15 * 1024 * 1024))); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "MemoryMax", "t", mcurrent + (50 * 1024 * 1024))); + ASSERT_OK(sd_bus_message_close_container(m)); + + ASSERT_OK(sd_bus_call(bus, m, 0, NULL, NULL)); + + /* Generate some memory allocations via mempool */ +#define NN (1024) + Hashmap **h = new(Hashmap*, NN); + for (int i = 0; i < NN; i++) + h[i] = hashmap_new(NULL); + for (int i = 0; i < NN; i++) + hashmap_free(h[i]); + free(h); + + /* Now start eating memory */ + ASSERT_EQ(write(pipe_fd[1], &(const char) { 'x' }, 1), 1); + + ASSERT_OK(sd_event_loop(e)); + int ex = 0; + ASSERT_OK(sd_event_get_exit_code(e, &ex)); + ASSERT_EQ(ex, 31); +} + +/* CPU pressure real test */ + +static int real_cpu_pressure_callback(sd_event_source *s, void *userdata) { + struct real_pressure_context *c = ASSERT_PTR(userdata); + const char *d; + + ASSERT_NOT_NULL(s); + ASSERT_OK(sd_event_source_get_description(s, &d)); + + log_notice("real cpu pressure event: %s", d); + + ASSERT_NOT_NULL(c->pid); + ASSERT_OK(sd_event_source_send_child_signal(c->pid, SIGKILL, NULL, 0)); + c->pid = NULL; + + return 0; +} + +_noreturn_ static void real_pressure_eat_cpu(int pipe_fd) { + char x; + ASSERT_EQ(read(pipe_fd, &x, 1), 1); /* Wait for the GO! */ + + /* Busy-loop to generate CPU pressure */ + for (;;) + __asm__ volatile("" ::: "memory"); /* Prevent optimization */ +} + +TEST(real_cpu_pressure) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *cs = NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_close_pair_ int pipe_fd[2] = EBADF_PAIR; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_free_ char *scope = NULL; + const char *object; + int r; + + if (getuid() == 0) + r = sd_bus_open_system(&bus); + else + r = sd_bus_open_user(&bus); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't connect to bus"); + + ASSERT_OK(bus_wait_for_jobs_new(bus, &w)); + + ASSERT_OK(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit")); + ASSERT_OK(asprintf(&scope, "test-%" PRIu64 ".scope", random_u64())); + ASSERT_OK(sd_bus_message_append(m, "ss", scope, "fail")); + ASSERT_OK(sd_bus_message_open_container(m, 'a', "(sv)")); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, 0)); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "CPUAccounting", "b", true)); + ASSERT_OK(sd_bus_message_close_container(m)); + ASSERT_OK(sd_bus_message_append(m, "a(sa(sv))", 0)); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't issue transient unit call"); + + ASSERT_OK(sd_bus_message_read(reply, "o", &object)); + + ASSERT_OK(bus_wait_for_jobs_one(w, object, /* flags= */ BUS_WAIT_JOBS_LOG_ERROR, /* extra_args= */ NULL)); + + ASSERT_OK(sd_event_default(&e)); + + ASSERT_OK_ERRNO(pipe2(pipe_fd, O_CLOEXEC)); + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = pidref_safe_fork("(eat-cpu)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &pidref); + ASSERT_OK(r); + if (r == 0) { + real_pressure_eat_cpu(pipe_fd[0]); + _exit(EXIT_SUCCESS); + } + + ASSERT_OK(event_add_child_pidref(e, &cs, &pidref, WEXITED, real_pressure_child_callback, NULL)); + ASSERT_OK(sd_event_source_set_child_process_own(cs, true)); + + ASSERT_OK_ERRNO(unsetenv("CPU_PRESSURE_WATCH")); + ASSERT_OK_ERRNO(unsetenv("CPU_PRESSURE_WRITE")); + + struct real_pressure_context context = { + .pid = cs, + }; + + r = sd_event_add_cpu_pressure(e, &es, real_cpu_pressure_callback, &context); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't allocate cpu pressure fd"); + + ASSERT_OK(sd_event_source_set_description(es, "real pressure event source")); + ASSERT_OK_ZERO(sd_event_source_set_cpu_pressure_type(es, "some")); + /* Unprivileged writes require a minimum of 2s otherwise the kernel will refuse the write. */ + ASSERT_OK_POSITIVE(sd_event_source_set_cpu_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); + ASSERT_OK_ZERO(sd_event_source_set_cpu_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); + ASSERT_OK(sd_event_source_set_enabled(es, SD_EVENT_ONESHOT)); + + m = sd_bus_message_unref(m); + + ASSERT_OK(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "SetUnitProperties")); + ASSERT_OK(sd_bus_message_append(m, "sb", scope, true)); + ASSERT_OK(sd_bus_message_open_container(m, 'a', "(sv)")); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "CPUQuotaPerSecUSec", "t", (uint64_t) 1000)); /* 0.1% CPU */ + ASSERT_OK(sd_bus_message_close_container(m)); + + ASSERT_OK(sd_bus_call(bus, m, 0, NULL, NULL)); + + /* Now start eating CPU */ + ASSERT_EQ(write(pipe_fd[1], &(const char) { 'x' }, 1), 1); + + ASSERT_OK(sd_event_loop(e)); + int ex = 0; + ASSERT_OK(sd_event_get_exit_code(e, &ex)); + ASSERT_EQ(ex, 31); +} + +/* IO pressure real test */ + +static int real_io_pressure_callback(sd_event_source *s, void *userdata) { + struct real_pressure_context *c = ASSERT_PTR(userdata); + const char *d; + + ASSERT_NOT_NULL(s); + ASSERT_OK(sd_event_source_get_description(s, &d)); + + log_notice("real io pressure event: %s", d); + + ASSERT_NOT_NULL(c->pid); + ASSERT_OK(sd_event_source_send_child_signal(c->pid, SIGKILL, NULL, 0)); + c->pid = NULL; + + return 0; +} + +_noreturn_ static void real_pressure_eat_io(int pipe_fd) { + char x; + ASSERT_EQ(read(pipe_fd, &x, 1), 1); /* Wait for the GO! */ + + /* Write and fsync in a loop to generate IO pressure */ + for (;;) { + _cleanup_close_ int fd = -EBADF; + + fd = open("/var/tmp/.io-pressure-test", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0600); + if (fd < 0) + continue; + + char buf[4096]; + memset(buf, 'x', sizeof(buf)); + for (int i = 0; i < 256; i++) + if (write(fd, buf, sizeof(buf)) < 0) + break; + (void) fsync(fd); + } +} + +TEST(real_io_pressure) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *cs = NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_close_pair_ int pipe_fd[2] = EBADF_PAIR; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_free_ char *scope = NULL; + const char *object; + int r; + + if (getuid() == 0) + r = sd_bus_open_system(&bus); + else + r = sd_bus_open_user(&bus); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't connect to bus"); + + ASSERT_OK(bus_wait_for_jobs_new(bus, &w)); + + ASSERT_OK(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit")); + ASSERT_OK(asprintf(&scope, "test-%" PRIu64 ".scope", random_u64())); + ASSERT_OK(sd_bus_message_append(m, "ss", scope, "fail")); + ASSERT_OK(sd_bus_message_open_container(m, 'a', "(sv)")); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, 0)); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "IOAccounting", "b", true)); + ASSERT_OK(sd_bus_message_close_container(m)); + ASSERT_OK(sd_bus_message_append(m, "a(sa(sv))", 0)); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't issue transient unit call"); + + ASSERT_OK(sd_bus_message_read(reply, "o", &object)); + + ASSERT_OK(bus_wait_for_jobs_one(w, object, /* flags= */ BUS_WAIT_JOBS_LOG_ERROR, /* extra_args= */ NULL)); + + ASSERT_OK(sd_event_default(&e)); + + ASSERT_OK_ERRNO(pipe2(pipe_fd, O_CLOEXEC)); + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = pidref_safe_fork("(eat-io)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &pidref); + ASSERT_OK(r); + if (r == 0) { + real_pressure_eat_io(pipe_fd[0]); + _exit(EXIT_SUCCESS); + } + + ASSERT_OK(event_add_child_pidref(e, &cs, &pidref, WEXITED, real_pressure_child_callback, NULL)); + ASSERT_OK(sd_event_source_set_child_process_own(cs, true)); + + ASSERT_OK_ERRNO(unsetenv("IO_PRESSURE_WATCH")); + ASSERT_OK_ERRNO(unsetenv("IO_PRESSURE_WRITE")); + + struct real_pressure_context context = { + .pid = cs, + }; + + r = sd_event_add_io_pressure(e, &es, real_io_pressure_callback, &context); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't allocate io pressure fd"); + + ASSERT_OK(sd_event_source_set_description(es, "real pressure event source")); + ASSERT_OK_ZERO(sd_event_source_set_io_pressure_type(es, "some")); + /* Unprivileged writes require a minimum of 2s otherwise the kernel will refuse the write. */ + ASSERT_OK_POSITIVE(sd_event_source_set_io_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); + ASSERT_OK_ZERO(sd_event_source_set_io_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); + ASSERT_OK(sd_event_source_set_enabled(es, SD_EVENT_ONESHOT)); + + m = sd_bus_message_unref(m); + + ASSERT_OK(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "SetUnitProperties")); + ASSERT_OK(sd_bus_message_append(m, "sb", scope, true)); + ASSERT_OK(sd_bus_message_open_container(m, 'a', "(sv)")); + ASSERT_OK(sd_bus_message_open_container(m, 'r', "sv")); + ASSERT_OK(sd_bus_message_append(m, "s", "IOWriteBandwidthMax")); + ASSERT_OK(sd_bus_message_open_container(m, 'v', "a(st)")); + ASSERT_OK(sd_bus_message_append(m, "a(st)", 1, "/var/tmp", (uint64_t) 1024*1024)); /* 1M/s */ + ASSERT_OK(sd_bus_message_close_container(m)); + ASSERT_OK(sd_bus_message_close_container(m)); + ASSERT_OK(sd_bus_message_close_container(m)); + + ASSERT_OK(sd_bus_call(bus, m, 0, NULL, NULL)); + + /* Now start eating IO */ + ASSERT_EQ(write(pipe_fd[1], &(const char) { 'x' }, 1), 1); + + ASSERT_OK(sd_event_loop(e)); + int ex = 0; + ASSERT_OK(sd_event_get_exit_code(e, &ex)); + ASSERT_EQ(ex, 31); +} + +static int outro(void) { + (void) unlink("/var/tmp/.io-pressure-test"); + hashmap_trim_pools(); + return 0; +} + +DEFINE_TEST_MAIN_FULL(LOG_DEBUG, NULL, outro); diff --git a/src/test/test-qmp-client-qemu.c b/src/test/test-qmp-client-qemu.c new file mode 100644 index 0000000000000..813b9c5687a50 --- /dev/null +++ b/src/test/test-qmp-client-qemu.c @@ -0,0 +1,337 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* Integration test for the QMP client library against a real QEMU instance. + * + * Launches QEMU with -machine none (no bootable image needed) to get a live QMP monitor, then exercises the + * client library against it. Validates the blocking handshake, large response buffering (~200KB for + * query-qmp-schema), response correlation by id, and async command execution. + * + * Skipped automatically if QEMU is not installed. */ + +#include +#include +#include + +#include "sd-event.h" +#include "sd-json.h" + +#include "fd-util.h" +#include "pidref.h" +#include "process-util.h" +#include "qmp-client.h" +#include "string-util.h" +#include "tests.h" +#include "time-util.h" +#include "vmspawn-util.h" + +static int start_qemu(const char *qemu_binary, int fd, PidRef *ret) { + _cleanup_free_ char *chardev_arg = NULL; + int r; + + assert(qemu_binary); + assert(fd >= 0); + assert(ret); + + if (asprintf(&chardev_arg, "socket,id=qmp,fd=%d", fd) < 0) + return -ENOMEM; + + r = pidref_safe_fork_full( + "(qemu)", + (const int[3]) { STDIN_FILENO, -EBADF, -EBADF }, + &fd, 1, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_CLOEXEC_OFF, + ret); + if (r < 0) + return r; + if (r == 0) { + /* Child */ + execl(qemu_binary, qemu_binary, + "-machine", "none", + "-nographic", + "-nodefaults", + "-chardev", chardev_arg, + "-mon", "chardev=qmp,mode=control", + NULL); + log_error_errno(errno, "Failed to exec %s: %m", qemu_binary); + _exit(EXIT_FAILURE); + } + + return 0; +} + +/* Test helper: tracks an async QMP command result and signals completion. */ +typedef struct { + sd_json_variant *result; + int error; + bool done; +} QmpTestResult; + +static int on_test_result( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + QmpTestResult *t = ASSERT_PTR(userdata); + + t->error = error; + if (result) + t->result = sd_json_variant_ref(result); + t->done = true; + return 0; +} + +static void qmp_test_wait(sd_event *event, QmpTestResult *t) { + assert(event); + assert(t); + + usec_t deadline = usec_add(now(CLOCK_MONOTONIC), 5 * USEC_PER_MINUTE); + + while (!t->done) { + usec_t n = now(CLOCK_MONOTONIC); + ASSERT_LT(n, deadline); + ASSERT_OK(sd_event_run(event, usec_sub_unsigned(deadline, n))); + } +} + +static void qmp_test_result_done(QmpTestResult *t) { + assert(t); + + sd_json_variant_unref(t->result); + *t = (QmpTestResult) {}; +} + +TEST(qmp_client_qemu_handshake_and_schema) { + _cleanup_free_ char *qemu = NULL; + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; + QmpTestResult t = {}; + _cleanup_close_pair_ int qmp_fds[2] = EBADF_PAIR; + int r; + + if (find_qemu_binary(&qemu) < 0) { + log_tests_skipped("QEMU not found"); + return; + } + log_info("Using QEMU: %s", qemu); + + ASSERT_OK(sd_event_new(&event)); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + ASSERT_OK(start_qemu(qemu, qmp_fds[1], &pidref)); + qmp_fds[1] = safe_close(qmp_fds[1]); + + r = qmp_client_connect_fd(&client, qmp_fds[0]); + if (r < 0) { + log_tests_skipped_errno(r, "QMP connect failed (QEMU may not support -machine none)"); + return; + } + TAKE_FD(qmp_fds[0]); + + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + /* query-qmp-schema returns ~200KB -- validates the buffered reader handles large multi-read() + * responses correctly. The handshake completes transparently inside invoke(). */ + r = qmp_client_invoke(client, /* ret_slot= */ NULL, "query-qmp-schema", NULL, on_test_result, &t); + if (r < 0) { + log_tests_skipped_errno(r, "QMP invoke failed (handshake or send)"); + return; + } + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + ASSERT_NOT_NULL(t.result); + ASSERT_TRUE(sd_json_variant_is_array(t.result)); + ASSERT_GT(sd_json_variant_elements(t.result), (size_t) 0); + log_info("query-qmp-schema returned %zu entries", sd_json_variant_elements(t.result)); + + /* Smoke-test the schema walker against the real schema. node-name is on every BlockdevOptions* + * object since blockdev-add was introduced. Don't assert discard-no-unref — CI may have QEMU < 8.1. */ + ASSERT_TRUE(qmp_schema_has_member(t.result, "node-name")); + ASSERT_FALSE(qmp_schema_has_member(t.result, "definitely-not-a-real-field")); + + qmp_test_result_done(&t); + + /* Clean shutdown */ + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "quit", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + qmp_test_result_done(&t); + + siginfo_t si = {}; + ASSERT_OK(pidref_wait_for_terminate(&pidref, &si)); + ASSERT_EQ(si.si_code, CLD_EXITED); + ASSERT_EQ(si.si_status, EXIT_SUCCESS); + pidref_done(&pidref); +} + +TEST(qmp_client_qemu_query_status) { + _cleanup_free_ char *qemu = NULL; + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; + QmpTestResult t = {}; + sd_json_variant *running, *status; + _cleanup_close_pair_ int qmp_fds[2] = EBADF_PAIR; + int r; + + if (find_qemu_binary(&qemu) < 0) { + log_tests_skipped("QEMU not found"); + return; + } + + ASSERT_OK(sd_event_new(&event)); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + ASSERT_OK(start_qemu(qemu, qmp_fds[1], &pidref)); + qmp_fds[1] = safe_close(qmp_fds[1]); + + r = qmp_client_connect_fd(&client, qmp_fds[0]); + if (r < 0) { + log_tests_skipped_errno(r, "QMP connect failed"); + return; + } + TAKE_FD(qmp_fds[0]); + + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + /* query-status validates response parsing against real QEMU output format. + * The handshake completes transparently inside invoke(). */ + r = qmp_client_invoke(client, /* ret_slot= */ NULL, "query-status", NULL, on_test_result, &t); + if (r < 0) { + log_tests_skipped_errno(r, "QMP invoke failed (handshake or send)"); + return; + } + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + ASSERT_NOT_NULL(t.result); + + status = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "status")); + ASSERT_TRUE(sd_json_variant_is_string(status)); + + running = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "running")); + ASSERT_TRUE(sd_json_variant_is_boolean(running)); + + log_info("QEMU status: %s, running: %s", + sd_json_variant_string(status), + true_false(sd_json_variant_boolean(running))); + + qmp_test_result_done(&t); + + /* Test stop + cont to exercise command sequencing and id correlation */ + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "stop", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + qmp_test_result_done(&t); + + /* Verify status changed */ + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "query-status", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + ASSERT_NOT_NULL(t.result); + + running = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "running")); + ASSERT_FALSE(sd_json_variant_boolean(running)); + log_info("After stop: running=%s", true_false(sd_json_variant_boolean(running))); + + qmp_test_result_done(&t); + + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "cont", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + qmp_test_result_done(&t); + + /* Clean shutdown */ + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "quit", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + qmp_test_result_done(&t); + + siginfo_t si = {}; + ASSERT_OK(pidref_wait_for_terminate(&pidref, &si)); + ASSERT_EQ(si.si_code, CLD_EXITED); + ASSERT_EQ(si.si_status, EXIT_SUCCESS); + pidref_done(&pidref); +} + +TEST(qmp_client_qemu_add_fd) { + _cleanup_free_ char *qemu = NULL; + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + _cleanup_close_ int fd_to_pass = -EBADF; + QmpTestResult t = {}; + _cleanup_close_pair_ int qmp_fds[2] = EBADF_PAIR; + int r; + + if (find_qemu_binary(&qemu) < 0) { + log_tests_skipped("QEMU not found"); + return; + } + + ASSERT_OK(sd_event_new(&event)); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + ASSERT_OK(start_qemu(qemu, qmp_fds[1], &pidref)); + qmp_fds[1] = safe_close(qmp_fds[1]); + + r = qmp_client_connect_fd(&client, qmp_fds[0]); + if (r < 0) { + log_tests_skipped_errno(r, "QMP connect failed"); + return; + } + TAKE_FD(qmp_fds[0]); + + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + fd_to_pass = eventfd(0, EFD_CLOEXEC); + ASSERT_OK_ERRNO(fd_to_pass); + + ASSERT_OK(sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0))); + + /* Pass an fd via SCM_RIGHTS on the very first invoke against a fresh client: + * add-fd lands right after the eagerly-enqueued qmp_capabilities. QEMU processes cap + * first (no fd needed), then add-fd, popping the fd from its FIFO receive queue. */ + r = qmp_client_invoke(client, /* ret_slot= */ NULL, "add-fd", + QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd_to_pass)), + on_test_result, &t); + if (r < 0) { + log_tests_skipped_errno(r, "QMP add-fd invoke failed"); + return; + } + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + ASSERT_NOT_NULL(t.result); + + sd_json_variant *fdset_id = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "fdset-id")); + sd_json_variant *fd_v = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "fd")); + ASSERT_EQ(sd_json_variant_unsigned(fdset_id), (uint64_t) 0); + log_info("add-fd returned fdset-id=%" PRIu64 ", fd=%" PRIu64, + sd_json_variant_unsigned(fdset_id), + sd_json_variant_unsigned(fd_v)); + + qmp_test_result_done(&t); + + /* Clean shutdown */ + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "quit", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + qmp_test_result_done(&t); + + siginfo_t si = {}; + ASSERT_OK(pidref_wait_for_terminate(&pidref, &si)); + ASSERT_EQ(si.si_code, CLD_EXITED); + ASSERT_EQ(si.si_status, EXIT_SUCCESS); + pidref_done(&pidref); +} + +static int intro(void) { + /* QEMU dies between our last write and read on the QMP socket — without this we'd + * get killed by the SIGPIPE the kernel raises on write-after-EOF. */ + ASSERT_TRUE(signal(SIGPIPE, SIG_IGN) != SIG_ERR); + return 0; +} + +DEFINE_TEST_MAIN_FULL(LOG_DEBUG, intro, NULL); diff --git a/src/test/test-qmp-client.c b/src/test/test-qmp-client.c new file mode 100644 index 0000000000000..befee02484588 --- /dev/null +++ b/src/test/test-qmp-client.c @@ -0,0 +1,756 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "sd-event.h" +#include "sd-json.h" + +#include "errno-util.h" +#include "fd-util.h" +#include "json-stream.h" +#include "pidref.h" +#include "process-util.h" +#include "qmp-client.h" +#include "string-util.h" +#include "tests.h" + +/* Mock QMP server: runs in the child process of a fork, communicates via one end of a socketpair. + * Uses JsonStream as the transport so framing (CRLF delimiter, message queuing, SCM_RIGHTS) is + * handled the same way as on the client side — individual recv() syscalls may coalesce multiple + * messages, and the parser must re-emit each one on its own. */ + +/* We drive the stream manually via read/parse/wait; always report READING so json_stream_wait() + * asks for POLLIN. */ +static JsonStreamPhase mock_qmp_phase(void *userdata) { + return JSON_STREAM_PHASE_READING; +} + +/* Never reached — we don't wire the mock stream up to sd-event — but required at init. */ +static int mock_qmp_dispatch(void *userdata) { + return 0; +} + +static void mock_qmp_init(JsonStream *s, int fd) { + static const JsonStreamParams params = { + .delimiter = "\r\n", + .phase = mock_qmp_phase, + .dispatch = mock_qmp_dispatch, + }; + + ASSERT_OK(json_stream_init(s, ¶ms)); + ASSERT_OK(json_stream_connect_fd_pair(s, fd, fd)); +} + +/* Read one complete JSON message, blocking until available. Handles the case where multiple + * client messages arrived coalesced into a single recv(): the parser walks the input buffer + * one CRLF-delimited message at a time. */ +static void mock_qmp_recv(JsonStream *s, sd_json_variant **ret) { + int r; + + for (;;) { + r = ASSERT_OK(json_stream_parse(s, ret)); + if (r > 0) + return; + + r = ASSERT_OK(json_stream_read(s)); + if (r > 0) + continue; + + ASSERT_OK(json_stream_wait(s, USEC_INFINITY)); + } +} + +/* Enqueue one JSON variant and block until it has been fully written. */ +static void mock_qmp_send(JsonStream *s, sd_json_variant *v) { + ASSERT_OK(json_stream_enqueue(s, v)); + ASSERT_OK(json_stream_flush(s)); +} + +/* Parse a literal JSON string and send it. Used for fixed greetings and unsolicited events. */ +static void mock_qmp_send_literal(JsonStream *s, const char *msg) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + ASSERT_OK(sd_json_parse(msg, 0, &v, NULL, NULL)); + mock_qmp_send(s, v); +} + +/* Read a command from the client, verify it contains the expected command name, and send a + * reply carrying the same id. If reply_data is NULL, an empty return object is sent. */ +static void mock_qmp_expect_and_reply(JsonStream *s, const char *expected_command, sd_json_variant *reply_data) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd = NULL, *reply_obj = NULL, *response = NULL; + + mock_qmp_recv(s, &cmd); + + sd_json_variant *execute = ASSERT_NOT_NULL(sd_json_variant_by_key(cmd, "execute")); + ASSERT_STREQ(sd_json_variant_string(execute), expected_command); + + sd_json_variant *id = ASSERT_NOT_NULL(sd_json_variant_by_key(cmd, "id")); + + if (!reply_data) + ASSERT_OK(sd_json_variant_new_object(&reply_obj, NULL, 0)); + + ASSERT_OK(sd_json_buildo( + &response, + SD_JSON_BUILD_PAIR("return", SD_JSON_BUILD_VARIANT(reply_data ?: reply_obj)), + SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(id)))); + + mock_qmp_send(s, response); +} + +/* Same shape as mock_qmp_expect_and_reply() but replies with a QMP error object. */ +static void mock_qmp_expect_and_reply_error(JsonStream *s, const char *expected_command, const char *error_desc) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd = NULL, *error_obj = NULL, *response = NULL; + + mock_qmp_recv(s, &cmd); + + sd_json_variant *execute = ASSERT_NOT_NULL(sd_json_variant_by_key(cmd, "execute")); + ASSERT_STREQ(sd_json_variant_string(execute), expected_command); + + sd_json_variant *id = ASSERT_NOT_NULL(sd_json_variant_by_key(cmd, "id")); + + ASSERT_OK(sd_json_buildo( + &error_obj, + SD_JSON_BUILD_PAIR_STRING("class", "GenericError"), + SD_JSON_BUILD_PAIR_STRING("desc", error_desc))); + + ASSERT_OK(sd_json_buildo( + &response, + SD_JSON_BUILD_PAIR("error", SD_JSON_BUILD_VARIANT(error_obj)), + SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(id)))); + + mock_qmp_send(s, response); +} + +static _noreturn_ void mock_qmp_server(int fd) { + _cleanup_(json_stream_done) JsonStream s = {}; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *status_return = NULL; + + mock_qmp_init(&s, fd); + + /* Send QMP greeting */ + mock_qmp_send_literal(&s, + "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 2, \"major\": 9}}, \"capabilities\": [\"oob\"]}}"); + + /* Accept qmp_capabilities */ + mock_qmp_expect_and_reply(&s, "qmp_capabilities", NULL); + + /* Accept query-status, reply with running state */ + ASSERT_OK(sd_json_buildo( + &status_return, + SD_JSON_BUILD_PAIR_BOOLEAN("running", true), + SD_JSON_BUILD_PAIR_STRING("status", "running"))); + mock_qmp_expect_and_reply(&s, "query-status", status_return); + + /* Accept stop */ + mock_qmp_expect_and_reply(&s, "stop", NULL); + + /* Send a STOP event */ + mock_qmp_send_literal(&s, + "{\"event\": \"STOP\", \"timestamp\": {\"seconds\": 1234, \"microseconds\": 5678}}"); + + /* Accept cont */ + mock_qmp_expect_and_reply(&s, "cont", NULL); + + /* json_stream_done() on cleanup closes our fd and signals EOF. */ + _exit(EXIT_SUCCESS); +} + +/* Test helper: tracks an async QMP command result and signals completion. */ +typedef struct { + sd_json_variant *result; + char *error_desc; + int error; + bool done; +} QmpTestResult; + +static int on_test_result( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + QmpTestResult *t = ASSERT_PTR(userdata); + + t->error = error; + if (result) + t->result = sd_json_variant_ref(result); + if (error_desc) + t->error_desc = strdup(error_desc); + t->done = true; + return 0; +} + +/* Run the event loop until the test result callback fires. */ +static void qmp_test_wait(sd_event *event, QmpTestResult *t) { + assert(event); + assert(t); + + while (!t->done) + ASSERT_OK(sd_event_run(event, UINT64_MAX)); +} + +static void qmp_test_result_done(QmpTestResult *t) { + assert(t); + + sd_json_variant_unref(t->result); + free(t->error_desc); + *t = (QmpTestResult) {}; +} + +static int test_event_callback( + QmpClient *client, + const char *event, + sd_json_variant *data, + void *userdata) { + + bool *event_received = ASSERT_PTR(userdata); + + /* We may also receive a synthetic SHUTDOWN event when the mock server closes the connection; + * only validate the STOP event we actually care about. */ + if (streq(event, "STOP")) + *event_received = true; + + return 0; +} + +TEST(qmp_client_basic) { + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; + QmpTestResult t = {}; + sd_json_variant *running, *status; + int qmp_fds[2]; + int r; + + ASSERT_OK(sd_event_new(&event)); + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + r = ASSERT_OK(pidref_safe_fork("(mock-qmp)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); + + if (r == 0) { + safe_close(qmp_fds[0]); + mock_qmp_server(qmp_fds[1]); + } + + safe_close(qmp_fds[1]); + + /* Connect then attach to event loop — handshake completes transparently + * inside the first call()/invoke(). */ + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + /* Set event callback to catch STOP event during cont */ + bool event_received = false; + qmp_client_bind_event(client, test_event_callback, &event_received); + + /* Execute query-status */ + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "query-status", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + ASSERT_NOT_NULL(t.result); + + running = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "running")); + ASSERT_TRUE(sd_json_variant_boolean(running)); + + status = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "status")); + ASSERT_STREQ(sd_json_variant_string(status), "running"); + + qmp_test_result_done(&t); + + /* Execute stop */ + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "stop", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + qmp_test_result_done(&t); + + /* Execute cont -- the STOP event should be dispatched by the IO callback */ + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "cont", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + qmp_test_result_done(&t); + + /* Verify the STOP event was received */ + ASSERT_TRUE(event_received); + + /* Wait for child and verify clean exit */ + siginfo_t si = {}; + ASSERT_OK(pidref_wait_for_terminate(&pid, &si)); + ASSERT_EQ(si.si_code, CLD_EXITED); + ASSERT_EQ(si.si_status, EXIT_SUCCESS); +} + +TEST(qmp_client_eof) { + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; + QmpTestResult t = {}; + int qmp_fds[2]; + int r; + + ASSERT_OK(sd_event_new(&event)); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + r = ASSERT_OK(pidref_safe_fork("(mock-qmp-eof)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); + + if (r == 0) { + _cleanup_(json_stream_done) JsonStream s = {}; + + safe_close(qmp_fds[0]); + mock_qmp_init(&s, qmp_fds[1]); + + /* Send greeting and accept capabilities, then die */ + mock_qmp_send_literal(&s, + "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); + + mock_qmp_expect_and_reply(&s, "qmp_capabilities", NULL); + + /* _exit() closes our fd via kernel teardown, signalling EOF to the peer. */ + _exit(EXIT_SUCCESS); + } + + safe_close(qmp_fds[1]); + + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + /* Executing a command should fail with a disconnect error because the server + * closed. The handshake may succeed or fail inside invoke() — either way the + * invoke itself or the async callback should report a disconnect. */ + r = qmp_client_invoke(client, /* ret_slot= */ NULL, "query-status", NULL, on_test_result, &t); + if (r < 0) + ASSERT_TRUE(ERRNO_IS_NEG_DISCONNECT(r)); + else { + qmp_test_wait(event, &t); + ASSERT_TRUE(ERRNO_IS_NEG_DISCONNECT(t.error)); + qmp_test_result_done(&t); + } + + siginfo_t si = {}; + ASSERT_OK(pidref_wait_for_terminate(&pid, &si)); + ASSERT_EQ(si.si_code, CLD_EXITED); + ASSERT_EQ(si.si_status, EXIT_SUCCESS); +} + +/* Mock QMP server for the fd-passing test. Drives the wire dance: + * greeting → recv qmp_capabilities → reply → recv add-fd → reply + * Asserts that exactly one SCM_RIGHTS fd arrives total across the two recvs. We can't + * require the fd to come attached to add-fd specifically: AF_UNIX coalesces the client's + * non-SCM cap sendmsg forward into the SCM-bearing add-fd sendmsg, so the fd may surface + * with either recv depending on kernel scheduling. QEMU's FIFO fd queue doesn't care. */ +static _noreturn_ void mock_qmp_server_fd(int fd) { + _cleanup_(json_stream_done) JsonStream s = {}; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *cap_cmd = NULL, + *addfd_cmd = NULL, + *cap_reply = NULL, + *addfd_return = NULL, + *addfd_reply = NULL; + + mock_qmp_init(&s, fd); + ASSERT_OK(json_stream_set_allow_fd_passing_input(&s, true, /* with_sockopt= */ true)); + + /* Greeting */ + mock_qmp_send_literal(&s, + "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); + + /* Receive qmp_capabilities (may or may not carry the fd depending on coalescing). */ + mock_qmp_recv(&s, &cap_cmd); + size_t n_fds_total = json_stream_get_n_input_fds(&s); + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(cap_cmd, "execute")), "qmp_capabilities"); + json_stream_close_input_fds(&s); + + sd_json_variant *cap_id = ASSERT_NOT_NULL(sd_json_variant_by_key(cap_cmd, "id")); + ASSERT_OK(sd_json_buildo( + &cap_reply, + SD_JSON_BUILD_PAIR("return", SD_JSON_BUILD_EMPTY_OBJECT), + SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(cap_id)))); + mock_qmp_send(&s, cap_reply); + + /* Receive add-fd (fd may already have been consumed with cap's recv). */ + mock_qmp_recv(&s, &addfd_cmd); + n_fds_total += json_stream_get_n_input_fds(&s); + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(addfd_cmd, "execute")), "add-fd"); + json_stream_close_input_fds(&s); + + ASSERT_EQ(n_fds_total, (size_t) 1); + + sd_json_variant *addfd_id = ASSERT_NOT_NULL(sd_json_variant_by_key(addfd_cmd, "id")); + ASSERT_OK(sd_json_buildo( + &addfd_return, + SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("fd", 42))); + ASSERT_OK(sd_json_buildo( + &addfd_reply, + SD_JSON_BUILD_PAIR("return", SD_JSON_BUILD_VARIANT(addfd_return)), + SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(addfd_id)))); + mock_qmp_send(&s, addfd_reply); + + _exit(EXIT_SUCCESS); +} + +/* End-to-end fd-passing through qmp_client_invoke() with QMP_CLIENT_ARGS_FD(): open a real + * fd, send add-fd, confirm the mock received a single SCM_RIGHTS fd and replied successfully. */ +TEST(qmp_client_invoke_with_fd) { + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + _cleanup_close_ int fd_to_pass = -EBADF; + QmpTestResult t = {}; + int qmp_fds[2]; + int r; + + ASSERT_OK(sd_event_new(&event)); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + r = ASSERT_OK(pidref_safe_fork("(mock-qmp-fd)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); + + if (r == 0) { + safe_close(qmp_fds[0]); + mock_qmp_server_fd(qmp_fds[1]); + } + + safe_close(qmp_fds[1]); + + /* Open a real fd to pass — /dev/null is universally available. */ + fd_to_pass = open("/dev/null", O_RDWR|O_CLOEXEC); + ASSERT_OK(fd_to_pass); + + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + ASSERT_OK(sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0))); + + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "add-fd", + QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd_to_pass)), + on_test_result, &t)); + + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + ASSERT_NOT_NULL(t.result); + qmp_test_result_done(&t); + + /* Wait for the mock. If its fd-count assertion tripped, si.si_status is non-zero. */ + siginfo_t si = {}; + ASSERT_OK(pidref_wait_for_terminate(&pid, &si)); + ASSERT_EQ(si.si_code, CLD_EXITED); + ASSERT_EQ(si.si_status, EXIT_SUCCESS); +} + +/* Regression: the caller-supplied fds — already TAKE_FD()'d through QMP_CLIENT_ARGS_FD() — + * must never leak, regardless of whether the invoke reaches the wire. Verified here via a + * dead peer: invoke enqueues (non-blocking), the queue item owns the fd, and client teardown + * must close it. */ +TEST(qmp_client_invoke_failure_closes_fds) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + _cleanup_close_ int fd_to_pass = -EBADF; + QmpClient *client = NULL; + QmpTestResult t = {}; + int qmp_fds[2]; + int saved_fd_value; + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + /* Close the peer end immediately so any write attempt sees EPIPE. */ + safe_close(qmp_fds[1]); + + fd_to_pass = open("/dev/null", O_RDWR|O_CLOEXEC); + ASSERT_OK(fd_to_pass); + saved_fd_value = fd_to_pass; /* remember the int value for the closed-check */ + + ASSERT_OK(sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0))); + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + + /* invoke no longer blocks on the handshake — it just enqueues. The fd is now + * owned by the underlying JsonStream output queue. */ + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "add-fd", + QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd_to_pass)), + on_test_result, &t)); + ASSERT_EQ(fd_to_pass, -EBADF); /* TAKE_FD cleared our local handle */ + + /* The fd is still open here (held in JsonStream's queue). */ + ASSERT_OK_ERRNO(fcntl(saved_fd_value, F_GETFD)); + + /* Client teardown (json_stream_done) must close queued output fds, otherwise the + * saved fd number would still be valid. */ + client = qmp_client_unref(client); + ASSERT_EQ(fcntl(saved_fd_value, F_GETFD), -1); + ASSERT_EQ(errno, EBADF); +} + +/* Mock for the slot lifecycle + cancel tests: greets, accepts capabilities, then accepts + * query-status and stop, replying with dummy returns. A cancelled query-status still gets + * sent on the wire (cancel merely removes the pending slot), so the server must be prepared + * to read and reply to it. */ +static _noreturn_ void mock_qmp_server_slot(int fd) { + _cleanup_(json_stream_done) JsonStream s = {}; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *status_return = NULL; + + mock_qmp_init(&s, fd); + + mock_qmp_send_literal(&s, + "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); + + mock_qmp_expect_and_reply(&s, "qmp_capabilities", NULL); + + ASSERT_OK(sd_json_buildo( + &status_return, + SD_JSON_BUILD_PAIR_BOOLEAN("running", true), + SD_JSON_BUILD_PAIR_STRING("status", "running"))); + mock_qmp_expect_and_reply(&s, "query-status", status_return); + + mock_qmp_expect_and_reply(&s, "stop", NULL); + + _exit(EXIT_SUCCESS); +} + +/* Verify that when qmp_client_invoke() returns a slot, qmp_slot_get_client() tracks the + * connection state: the client pointer is reported while the call is in flight, and flipped + * back to NULL once the reply has been dispatched. The caller must still be able to drop its + * ref safely after that. */ +TEST(qmp_client_invoke_slot_lifecycle) { + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL; + _cleanup_(qmp_slot_unrefp) QmpSlot *slot = NULL; + QmpTestResult t = {}; + int qmp_fds[2]; + int r; + + ASSERT_OK(sd_event_new(&event)); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + r = ASSERT_OK(pidref_safe_fork("(mock-qmp-slot-life)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); + if (r == 0) { + safe_close(qmp_fds[0]); + mock_qmp_server_slot(qmp_fds[1]); + } + safe_close(qmp_fds[1]); + + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + ASSERT_OK(qmp_client_invoke(client, &slot, "query-status", NULL, on_test_result, &t)); + + /* While in flight the slot still references its client. */ + ASSERT_NOT_NULL(slot); + ASSERT_PTR_EQ(qmp_slot_get_client(slot), client); + + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + ASSERT_NOT_NULL(t.result); + + /* Once dispatched, the slot is disconnected from the client but still owned by us. */ + ASSERT_NULL(qmp_slot_get_client(slot)); + + qmp_test_result_done(&t); + + /* Drop our ref explicitly (out of order w.r.t. cleanup) to exercise the + * already-disconnected path in qmp_slot_free(). */ + slot = qmp_slot_unref(slot); + ASSERT_NULL(slot); +} + +/* Verify that dropping the only reference on a pending slot before the reply arrives cancels + * the callback. The command is already enqueued on the stream at that point, so the server + * still sees it and replies — but the reply lands on an unknown id and is discarded. */ +TEST(qmp_client_invoke_slot_cancel) { + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL; + QmpTestResult t_cancelled = {}; + QmpSlot *slot = NULL; + int qmp_fds[2]; + int r; + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + r = ASSERT_OK(pidref_safe_fork("(mock-qmp-slot-cancel)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); + if (r == 0) { + safe_close(qmp_fds[0]); + mock_qmp_server_slot(qmp_fds[1]); + } + safe_close(qmp_fds[1]); + + /* Drive without an event loop so the subsequent qmp_client_call() owns all pumping; + * it serializes write→read round-trips, which avoids the mock server seeing the + * cancelled query-status and the follow-up stop concatenated into a single recv(). */ + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + + ASSERT_OK(qmp_client_invoke(client, &slot, "query-status", NULL, on_test_result, &t_cancelled)); + ASSERT_NOT_NULL(slot); + + /* Drop our sole ref → slot disconnects itself from the client's pending set. The + * enqueued query-status is still on the wire; when its reply arrives, dispatch_reply + * won't find a matching slot and will log-and-discard it. */ + slot = qmp_slot_unref(slot); + ASSERT_NULL(slot); + + /* Synchronous call drives its own process+wait pump: it first drains the already- + * enqueued query-status write, consumes (and discards) its reply, then sends stop + * and waits for that reply. Any improper fire of the cancelled callback would have + * happened during that process() pass. */ + ASSERT_EQ(qmp_client_call(client, "stop", NULL, NULL, NULL), 1); + + /* The cancelled callback must never have fired. */ + ASSERT_FALSE(t_cancelled.done); + ASSERT_NULL(t_cancelled.result); + ASSERT_NULL(t_cancelled.error_desc); +} + +/* Drives a small wire dance for the sync call test: greeting, capabilities, one successful + * command reply, and two error replies (one for the ret_error_desc path, one for the -EIO + * path). */ +static _noreturn_ void mock_qmp_server_call(int fd) { + _cleanup_(json_stream_done) JsonStream s = {}; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *status_return = NULL; + + mock_qmp_init(&s, fd); + + mock_qmp_send_literal(&s, + "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); + + mock_qmp_expect_and_reply(&s, "qmp_capabilities", NULL); + + ASSERT_OK(sd_json_buildo( + &status_return, + SD_JSON_BUILD_PAIR_BOOLEAN("running", true), + SD_JSON_BUILD_PAIR_STRING("status", "running"))); + mock_qmp_expect_and_reply(&s, "query-status", status_return); + + mock_qmp_expect_and_reply_error(&s, "stop", "not running"); + mock_qmp_expect_and_reply_error(&s, "stop", "still not running"); + + _exit(EXIT_SUCCESS); +} + +TEST(qmp_client_call) { + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL; + int qmp_fds[2]; + int r; + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + r = ASSERT_OK(pidref_safe_fork("(mock-qmp-call)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); + if (r == 0) { + safe_close(qmp_fds[0]); + mock_qmp_server_call(qmp_fds[1]); + } + safe_close(qmp_fds[1]); + + /* qmp_client_call() drives its own process()+wait() pump, so no event loop needed. */ + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + + /* Successful call: borrowed result pointer is valid until the next call. */ + sd_json_variant *result = NULL; + const char *error_desc = NULL; + ASSERT_EQ(qmp_client_call(client, "query-status", NULL, &result, &error_desc), 1); + ASSERT_NULL(error_desc); + ASSERT_NOT_NULL(result); + + sd_json_variant *running = ASSERT_NOT_NULL(sd_json_variant_by_key(result, "running")); + ASSERT_TRUE(sd_json_variant_boolean(running)); + sd_json_variant *status = ASSERT_NOT_NULL(sd_json_variant_by_key(result, "status")); + ASSERT_STREQ(sd_json_variant_string(status), "running"); + + /* QMP error with ret_error_desc provided: returns 1, result NULL, desc set. */ + result = (sd_json_variant*) 0x1; /* poison to catch lack-of-write */ + error_desc = NULL; + ASSERT_EQ(qmp_client_call(client, "stop", NULL, &result, &error_desc), 1); + ASSERT_NULL(result); + ASSERT_STREQ(error_desc, "not running"); + + /* QMP error without ret_error_desc: surfaces as -EIO. */ + ASSERT_EQ(qmp_client_call(client, "stop", NULL, NULL, NULL), -EIO); +} + +/* Server variant for the sync-call disconnect test: greets, accepts capabilities, reads one + * command without replying, then closes the socket so the client sees EOF mid-wait. */ +static _noreturn_ void mock_qmp_server_call_disconnect(int fd) { + _cleanup_(json_stream_done) JsonStream s = {}; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *stop_cmd = NULL; + + mock_qmp_init(&s, fd); + + mock_qmp_send_literal(&s, + "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); + + mock_qmp_expect_and_reply(&s, "qmp_capabilities", NULL); + + /* Consume the stop command but don't reply — json_stream_done() on cleanup closes + * our fd, triggering EOF while the client is blocked in qmp_client_call()'s + * process+wait pump. */ + mock_qmp_recv(&s, &stop_cmd); + + _exit(EXIT_SUCCESS); +} + +TEST(qmp_client_call_disconnect) { + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL; + int qmp_fds[2]; + int r; + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + r = ASSERT_OK(pidref_safe_fork("(mock-qmp-call-disc)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); + if (r == 0) { + safe_close(qmp_fds[0]); + mock_qmp_server_call_disconnect(qmp_fds[1]); + } + safe_close(qmp_fds[1]); + + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + + /* The server reads our stop command and closes without replying. qmp_client_call() + * is driving its own pump, so it must notice the EOF, transition to DISCONNECTED, + * and return a disconnect error rather than hanging. */ + r = qmp_client_call(client, "stop", NULL, NULL, NULL); + ASSERT_TRUE(r < 0); + ASSERT_TRUE(ERRNO_IS_NEG_DISCONNECT(r)); +} + +TEST(qmp_schema_has_member) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *schema = NULL; + + /* QEMU introspection uses opaque numeric type ids ("0", "1", ...) — only member names are + * the actual QAPI strings. Verify we walk all object entries and find the member by name. */ + ASSERT_OK(sd_json_build(&schema, + SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("name", "0"), + SD_JSON_BUILD_PAIR_STRING("meta-type", "object"), + SD_JSON_BUILD_PAIR("members", SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("name", "offset"), + SD_JSON_BUILD_PAIR_STRING("type", "int"))))), + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("name", "SomeEnum"), + SD_JSON_BUILD_PAIR_STRING("meta-type", "enum")), + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("name", "1"), + SD_JSON_BUILD_PAIR_STRING("meta-type", "object"), + SD_JSON_BUILD_PAIR("members", SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("name", "lazy-refcounts"), + SD_JSON_BUILD_PAIR_STRING("type", "bool")), + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("name", "discard-no-unref"), + SD_JSON_BUILD_PAIR_STRING("type", "bool")))))))); + + ASSERT_TRUE(qmp_schema_has_member(schema, "discard-no-unref")); + ASSERT_TRUE(qmp_schema_has_member(schema, "offset")); + ASSERT_FALSE(qmp_schema_has_member(schema, "definitely-not-a-real-field")); + ASSERT_FALSE(qmp_schema_has_member(NULL, "discard-no-unref")); +} + +static int intro(void) { + /* Ignore SIGPIPE so that write() to a closed socket returns EPIPE instead of killing us */ + ASSERT_TRUE(signal(SIGPIPE, SIG_IGN) != SIG_ERR); + return 0; +} + +DEFINE_TEST_MAIN_FULL(LOG_DEBUG, intro, NULL); diff --git a/src/test/test-seccomp.c b/src/test/test-seccomp.c index d40abf24e4dc6..44682b4a2c023 100644 --- a/src/test/test-seccomp.c +++ b/src/test/test-seccomp.c @@ -1095,20 +1095,23 @@ static void test_seccomp_suppress_sync_child(void) { _cleanup_close_ int fd = -EBADF; ASSERT_OK(tempfn_random("/tmp/seccomp_suppress_sync", NULL, &path)); - ASSERT_OK_ERRNO(fd = open(path, O_RDWR | O_CREAT | O_SYNC | O_CLOEXEC, 0666)); - fd = safe_close(fd); - - ASSERT_ERROR_ERRNO(fdatasync(-1), EBADF); - ASSERT_ERROR_ERRNO(fsync(-1), EBADF); - ASSERT_ERROR_ERRNO(syncfs(-1), EBADF); - - ASSERT_ERROR_ERRNO(fdatasync(INT_MAX), EBADF); - ASSERT_ERROR_ERRNO(fsync(INT_MAX), EBADF); - ASSERT_ERROR_ERRNO(syncfs(INT_MAX), EBADF); + fd = open(path, O_RDWR | O_CREAT | O_SYNC | O_CLOEXEC, 0666); + /* We might be running in an environment where sync() is already suppressed. */ + if (fd >= 0) { + ASSERT_ERROR_ERRNO(fdatasync(-1), EBADF); + ASSERT_ERROR_ERRNO(fsync(-1), EBADF); + ASSERT_ERROR_ERRNO(syncfs(-1), EBADF); + + ASSERT_ERROR_ERRNO(fdatasync(INT_MAX), EBADF); + ASSERT_ERROR_ERRNO(fsync(INT_MAX), EBADF); + ASSERT_ERROR_ERRNO(syncfs(INT_MAX), EBADF); + } else if (errno != EINVAL) + ASSERT_OK_ERRNO(fd); ASSERT_OK(seccomp_suppress_sync()); - ASSERT_ERROR_ERRNO(fd = open(path, O_RDWR | O_CREAT | O_SYNC | O_CLOEXEC, 0666), EINVAL); + fd = safe_close(fd); + fd = ASSERT_ERROR_ERRNO(open(path, O_RDWR | O_CREAT | O_SYNC | O_CLOEXEC, 0666), EINVAL); ASSERT_OK_ERRNO(fdatasync(INT_MAX)); ASSERT_OK_ERRNO(fsync(INT_MAX)); diff --git a/src/test/test-specifier.c b/src/test/test-specifier.c index 850f961bfedcf..6c6c00f53f08a 100644 --- a/src/test/test-specifier.c +++ b/src/test/test-specifier.c @@ -156,7 +156,7 @@ TEST(specifiers_assorted) { const sd_id128_t id = SD_ID128_ALLF; const uint64_t llu = UINT64_MAX; const Specifier table[] = { - /* Used in src/partition/repart.c */ + /* Used in src/repart/repart.c */ { 'a', specifier_uuid, &id }, { 'b', specifier_uint64, &llu }, {} diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index 4f12ec710c11e..6e07d727636ee 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -325,6 +325,8 @@ TEST(strrep) { ASSERT_STREQ(onea, "waldo"); ASSERT_STREQ(threea, "waldowaldowaldo"); + + ASSERT_NULL(strrep("waldo", SIZE_MAX - 1)); } TEST(string_has_cc) { @@ -533,6 +535,29 @@ TEST(endswith_no_case) { assert_se(!endswith_no_case("foobar", "FOOBARFOOFOO")); } +TEST(strrstr_no_case) { + ASSERT_STREQ(strrstr_no_case("fooBARfoobar", "bar"), "bar"); + ASSERT_STREQ(strrstr_no_case("fooBARfoobar", "BAR"), "bar"); + ASSERT_STREQ(strrstr_no_case("fooBARfoobar", "bAR"), "bar"); + ASSERT_STREQ(strrstr_no_case("fooBARfoobar", "FOO"), "foobar"); + ASSERT_STREQ(strrstr_no_case("fooBARfoobar", "foo"), "foobar"); + ASSERT_STREQ(strrstr_no_case("fooBARfoobar", "FoO"), "foobar"); + ASSERT_STREQ(strrstr_no_case("aXaXa", "x"), "Xa"); + ASSERT_STREQ(strrstr_no_case("aXaXa", "X"), "Xa"); + ASSERT_STREQ(strrstr_no_case("xHello", "hello"), "Hello"); + ASSERT_STREQ(strrstr_no_case("Hello", "l"), "lo"); + ASSERT_STREQ(strrstr_no_case("Hello", ""), ""); + ASSERT_STREQ(strrstr_no_case("", ""), ""); + ASSERT_STREQ(strrstr_no_case("FOO", "foo"), "FOO"); + ASSERT_STREQ(strrstr_no_case("hello", "hello"), "hello"); + ASSERT_STREQ(strrstr_no_case("X", "x"), "X"); + + ASSERT_NULL(strrstr_no_case("hello", "xyz")); + ASSERT_NULL(strrstr_no_case("", "x")); + ASSERT_NULL(strrstr_no_case(NULL, "x")); + ASSERT_NULL(strrstr_no_case("x", NULL)); +} + TEST(delete_chars) { char *s, input[] = " hello, waldo. abc"; @@ -617,6 +642,28 @@ TEST(split_pair) { ASSERT_STREQ(b, "="); } +TEST(empty_to_null) { + const char *s = "asdf", *n = NULL, *e = ""; + char *t = (char*) "asdf"; + const char p[] = "asdf"; + char q[] = "asdf"; + + /* empty_to_null cannot be used with constant strings, e.g. + * empty_to_null("") fails with 'error: cast specifies array type'. */ + + ASSERT_NULL(empty_to_null(NULL)); + ASSERT_NULL(empty_to_null(n)); + ASSERT_NULL(empty_to_null(e)); + ASSERT_STREQ(empty_to_null(s), "asdf"); + ASSERT_NULL(empty_to_null(s + 4)); + ASSERT_STREQ(empty_to_null(t), "asdf"); + ASSERT_NULL(empty_to_null(t + 4)); + ASSERT_STREQ(empty_to_null(&p[0]), "asdf"); + ASSERT_NULL(empty_to_null(&p[0] + 4)); + ASSERT_STREQ(empty_to_null(&q[0]), "asdf"); + ASSERT_NULL(empty_to_null(&q[0] + 4)); +} + TEST(first_word) { assert_se(first_word("Hello", "")); assert_se(first_word("Hello", "Hello")); @@ -1448,4 +1495,146 @@ TEST(str_common_prefix) { ASSERT_EQ(str_common_prefix("systemd-networkd", ""), 0U); } +TEST(string_is_safe) { + /* NULL is always rejected, regardless of flags. */ + ASSERT_FALSE(string_is_safe(NULL, 0)); + ASSERT_FALSE(string_is_safe(NULL, STRING_ALLOW_EMPTY)); + ASSERT_FALSE(string_is_safe(NULL, STRING_ASCII)); + ASSERT_FALSE(string_is_safe(NULL, STRING_ALLOW_BACKSLASHES)); + ASSERT_FALSE(string_is_safe(NULL, STRING_ALLOW_QUOTES)); + ASSERT_FALSE(string_is_safe(NULL, STRING_ALLOW_GLOBS)); + ASSERT_FALSE(string_is_safe(NULL, STRING_FILENAME)); + + /* Baseline (flags=0): rejects empty, backslashes, quotes, globs, control chars and invalid UTF-8. + * Plain alphanumerics/whitespace and valid UTF-8 accepted. */ + ASSERT_TRUE(string_is_safe("hello", 0)); + ASSERT_TRUE(string_is_safe("hello world", 0)); + ASSERT_TRUE(string_is_safe("über", 0)); /* valid UTF-8 allowed */ + ASSERT_TRUE(string_is_safe("ünïcödé", 0)); + + ASSERT_FALSE(string_is_safe("", 0)); /* empty rejected by default */ + ASSERT_FALSE(string_is_safe("a\\b", 0)); /* backslash rejected by default */ + ASSERT_FALSE(string_is_safe("\"", 0)); /* double quote rejected by default */ + ASSERT_FALSE(string_is_safe("'", 0)); /* single quote rejected by default */ + ASSERT_FALSE(string_is_safe("*", 0)); /* glob rejected by default */ + ASSERT_FALSE(string_is_safe("?", 0)); /* glob rejected by default */ + ASSERT_FALSE(string_is_safe("[", 0)); /* glob rejected by default */ + ASSERT_FALSE(string_is_safe("abc\x01", 0)); /* control char */ + ASSERT_FALSE(string_is_safe("\t", 0)); + ASSERT_FALSE(string_is_safe("\n", 0)); + ASSERT_FALSE(string_is_safe("abc\x1f", 0)); + ASSERT_FALSE(string_is_safe("abc\x7f", 0)); /* DEL */ + ASSERT_FALSE(string_is_safe("ab\xc3\x28", 0)); /* invalid UTF-8 continuation */ + ASSERT_FALSE(string_is_safe("\xff", 0)); /* not valid UTF-8 */ + + /* STRING_ALLOW_EMPTY. */ + ASSERT_TRUE(string_is_safe("", STRING_ALLOW_EMPTY)); + ASSERT_TRUE(string_is_safe("x", STRING_ALLOW_EMPTY)); + ASSERT_TRUE(string_is_safe("hello", STRING_ALLOW_EMPTY)); + ASSERT_FALSE(string_is_safe(NULL, STRING_ALLOW_EMPTY)); + + /* STRING_ASCII: high bytes rejected, low ASCII accepted, control chars still rejected. + * Empty is still rejected by default; backslashes/quotes/globs still rejected by default. */ + ASSERT_TRUE(string_is_safe("hello", STRING_ASCII)); + ASSERT_TRUE(string_is_safe("hello world 123!@#$%^&()", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("über", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("\x80", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("\xff", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("abc\x01", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("abc\x7f", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("a\\b", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("a\"b", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("a*b", STRING_ASCII)); + + /* STRING_ALLOW_BACKSLASHES: backslashes allowed, quotes/globs still rejected. */ + ASSERT_TRUE(string_is_safe("hello", STRING_ALLOW_BACKSLASHES)); + ASSERT_TRUE(string_is_safe("hello world", STRING_ALLOW_BACKSLASHES)); + ASSERT_TRUE(string_is_safe("\\", STRING_ALLOW_BACKSLASHES)); + ASSERT_TRUE(string_is_safe("a\\b", STRING_ALLOW_BACKSLASHES)); + ASSERT_TRUE(string_is_safe("foo\\", STRING_ALLOW_BACKSLASHES)); + ASSERT_TRUE(string_is_safe("\\foo", STRING_ALLOW_BACKSLASHES)); + ASSERT_TRUE(string_is_safe("foo\\nbar", STRING_ALLOW_BACKSLASHES)); /* literal backslash, not newline */ + ASSERT_FALSE(string_is_safe("\"", STRING_ALLOW_BACKSLASHES)); /* quotes still rejected */ + ASSERT_FALSE(string_is_safe("*", STRING_ALLOW_BACKSLASHES)); /* globs still rejected */ + + /* STRING_ALLOW_QUOTES: quotes allowed, backslashes/globs still rejected. */ + ASSERT_TRUE(string_is_safe("hello", STRING_ALLOW_QUOTES)); + ASSERT_TRUE(string_is_safe("hello world", STRING_ALLOW_QUOTES)); + ASSERT_TRUE(string_is_safe("\"", STRING_ALLOW_QUOTES)); + ASSERT_TRUE(string_is_safe("'", STRING_ALLOW_QUOTES)); + ASSERT_TRUE(string_is_safe("hello\"world", STRING_ALLOW_QUOTES)); + ASSERT_TRUE(string_is_safe("it's", STRING_ALLOW_QUOTES)); + ASSERT_FALSE(string_is_safe("a\\b", STRING_ALLOW_QUOTES)); /* backslashes still rejected */ + ASSERT_FALSE(string_is_safe("*", STRING_ALLOW_QUOTES)); /* globs still rejected */ + + /* STRING_ALLOW_GLOBS: globs allowed, backslashes/quotes still rejected. */ + ASSERT_TRUE(string_is_safe("hello", STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("ab]c", STRING_ALLOW_GLOBS)); /* ']' is not in GLOB_CHARS anyway */ + ASSERT_TRUE(string_is_safe("*", STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("?", STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("[", STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("foo*bar", STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("foo?bar", STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("foo[bar", STRING_ALLOW_GLOBS)); + ASSERT_FALSE(string_is_safe("\"", STRING_ALLOW_GLOBS)); /* quotes still rejected */ + ASSERT_FALSE(string_is_safe("a\\b", STRING_ALLOW_GLOBS)); /* backslashes still rejected */ + + /* STRING_FILENAME: rejects empty, ".", "..", and strings with '/'. */ + ASSERT_TRUE(string_is_safe("hello", STRING_FILENAME)); + ASSERT_TRUE(string_is_safe("hello.txt", STRING_FILENAME)); + ASSERT_TRUE(string_is_safe("...", STRING_FILENAME)); + ASSERT_TRUE(string_is_safe(".hidden", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe("", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe(".", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe("..", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe("/", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe("/foo", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe("foo/bar", STRING_FILENAME)); + + /* Pairwise combinations. */ + ASSERT_TRUE(string_is_safe("", STRING_ALLOW_EMPTY | STRING_ASCII)); + ASSERT_FALSE(string_is_safe("über", STRING_ALLOW_EMPTY | STRING_ASCII)); + ASSERT_TRUE(string_is_safe("hello", STRING_ALLOW_EMPTY | STRING_ASCII)); + + ASSERT_TRUE(string_is_safe("ab\"cd", STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("ab*cd", STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("ab'*cd", STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS)); + ASSERT_FALSE(string_is_safe("ab\\cd", STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS)); /* backslash still rejected */ + + ASSERT_TRUE(string_is_safe("hello.txt", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe("", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe("foo/bar", STRING_FILENAME)); + + ASSERT_TRUE(string_is_safe("foo?bar", STRING_ASCII | STRING_ALLOW_GLOBS)); + ASSERT_FALSE(string_is_safe("foo\"bar", STRING_ASCII | STRING_ALLOW_GLOBS)); /* quotes still rejected */ + ASSERT_FALSE(string_is_safe("über", STRING_ASCII | STRING_ALLOW_GLOBS)); + + ASSERT_TRUE(string_is_safe("foo\\bar", STRING_ALLOW_BACKSLASHES)); + ASSERT_FALSE(string_is_safe("foo\"bar", STRING_ALLOW_BACKSLASHES)); /* quotes still rejected */ + ASSERT_FALSE(string_is_safe("foo*bar", STRING_ALLOW_BACKSLASHES)); /* globs still rejected */ + ASSERT_TRUE(string_is_safe("foo\\\"bar", STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES)); + ASSERT_TRUE(string_is_safe("foo\\bar", STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES)); + ASSERT_TRUE(string_is_safe("foo\"bar", STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES)); + + /* All allow flags combined: only baseline (control chars, invalid UTF-8) and STRING_FILENAME apply. */ + StringSafeFlags all = STRING_ALLOW_EMPTY | STRING_ASCII | STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS | STRING_FILENAME; + ASSERT_TRUE(string_is_safe("hello.txt", all)); + ASSERT_TRUE(string_is_safe("foo-bar_baz.conf", all)); + ASSERT_TRUE(string_is_safe("a", all)); + ASSERT_TRUE(string_is_safe("foo\\bar", all)); /* backslash allowed */ + ASSERT_TRUE(string_is_safe("foo\"bar", all)); /* quote allowed */ + ASSERT_TRUE(string_is_safe("foo'bar", all)); /* quote allowed */ + ASSERT_TRUE(string_is_safe("foo*bar", all)); /* glob allowed */ + ASSERT_TRUE(string_is_safe("foo?bar", all)); /* glob allowed */ + ASSERT_TRUE(string_is_safe("foo[bar", all)); /* glob allowed */ + ASSERT_FALSE(string_is_safe("", all)); /* fails STRING_FILENAME */ + ASSERT_FALSE(string_is_safe("über", all)); /* fails STRING_ASCII */ + ASSERT_FALSE(string_is_safe("foo/bar", all)); /* fails STRING_FILENAME */ + ASSERT_FALSE(string_is_safe(".", all)); /* fails STRING_FILENAME */ + ASSERT_FALSE(string_is_safe("..", all)); /* fails STRING_FILENAME */ + ASSERT_FALSE(string_is_safe("foo\x01""bar", all)); /* fails baseline control-char check */ + ASSERT_FALSE(string_is_safe(NULL, all)); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-strv.c b/src/test/test-strv.c index b3468b1cfac62..283ae5c865ace 100644 --- a/src/test/test-strv.c +++ b/src/test/test-strv.c @@ -799,14 +799,14 @@ TEST(strv_foreach) { TEST(strv_foreach_backwards) { _cleanup_strv_free_ char **a; - unsigned i = 2; + unsigned i = 3; a = strv_new("one", "two", "three"); assert_se(a); STRV_FOREACH_BACKWARDS(check, a) - ASSERT_STREQ(*check, input_table_multiple[i--]); + ASSERT_STREQ(*check, input_table_multiple[--i]); STRV_FOREACH_BACKWARDS(check, (char**) NULL) assert_not_reached(); diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c index 4e2b751ad2627..8ccde2341c847 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -36,35 +36,34 @@ TEST(read_one_char) { bool need_nl; _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-read_one_char.XXXXXX"; - assert_se(fmkostemp_safe(name, "r+", &file) == 0); + ASSERT_OK_ZERO(fmkostemp_safe(name, "r+", &file)); - assert_se(fputs("c\n", file) >= 0); + ASSERT_OK_ERRNO(fputs("c\n", file)); rewind(file); - assert_se(read_one_char(file, &r, 1000000, /* echo= */ true, &need_nl) >= 0); - assert_se(!need_nl); - assert_se(r == 'c'); - assert_se(read_one_char(file, &r, 1000000, /* echo= */ true, &need_nl) < 0); + ASSERT_OK(read_one_char(file, &r, 1000000, /* echo= */ true, &need_nl)); + ASSERT_FALSE(need_nl); + ASSERT_EQ(r, 'c'); + ASSERT_FAIL(read_one_char(file, &r, 1000000, /* echo= */ true, &need_nl)); rewind(file); - assert_se(fputs("foobar\n", file) >= 0); + ASSERT_OK_ERRNO(fputs("foobar\n", file)); rewind(file); - assert_se(read_one_char(file, &r, 1000000, /* echo= */ true, &need_nl) < 0); + ASSERT_FAIL(read_one_char(file, &r, 1000000, /* echo= */ true, &need_nl)); rewind(file); - assert_se(fputs("\n", file) >= 0); + ASSERT_OK_ERRNO(fputs("\n", file)); rewind(file); - assert_se(read_one_char(file, &r, 1000000, /* echo= */ true, &need_nl) < 0); + ASSERT_FAIL(read_one_char(file, &r, 1000000, /* echo= */ true, &need_nl)); } TEST(getttyname_malloc) { _cleanup_free_ char *ttyname = NULL; - _cleanup_close_ int master = -EBADF; - assert_se((master = posix_openpt(O_RDWR|O_NOCTTY)) >= 0); - assert_se(getttyname_malloc(master, &ttyname) >= 0); + _cleanup_close_ int master = ASSERT_OK_ERRNO(posix_openpt(O_RDWR|O_NOCTTY)); + ASSERT_OK(getttyname_malloc(master, &ttyname)); log_info("ttyname = %s", ttyname); - assert_se(PATH_IN_SET(ttyname, "ptmx", "pts/ptmx")); + ASSERT_TRUE(PATH_IN_SET(ttyname, "ptmx", "pts/ptmx")); } typedef struct { @@ -152,8 +151,8 @@ TEST(get_ctty) { if (S_ISCHR(st.st_mode) && st.st_rdev == devnr) { _cleanup_free_ char *stdin_name = NULL; - assert_se(getttyname_malloc(STDIN_FILENO, &stdin_name) >= 0); - assert_se(path_equal(stdin_name, ctty)); + ASSERT_OK(getttyname_malloc(STDIN_FILENO, &stdin_name)); + ASSERT_TRUE(path_equal(stdin_name, ctty)); } else log_notice("Not invoked with stdin == ctty, cutting get_ctty() test short"); } @@ -172,12 +171,12 @@ TEST(get_default_background_color) { log_notice("R=%g G=%g B=%g", red, green, blue); } -TEST(terminal_get_size_by_csi18) { +TEST(terminal_get_size_csi18) { unsigned rows, columns; int r; usec_t n = now(CLOCK_MONOTONIC); - r = terminal_get_size_by_csi18(STDIN_FILENO, STDOUT_FILENO, &rows, &columns); + r = terminal_get_size(STDIN_FILENO, STDOUT_FILENO, &rows, &columns, /* try_dsr= */ false, /* try_csi18= */ true); log_info("%s took %s", __func__+5, FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), n), USEC_PER_MSEC)); if (r < 0) @@ -193,12 +192,12 @@ TEST(terminal_get_size_by_csi18) { log_notice("terminal size via ioctl: rows=%u columns=%u", ws.ws_row, ws.ws_col); } -TEST(terminal_get_size_by_dsr) { +TEST(terminal_get_size_dsr) { unsigned rows, columns; int r; usec_t n = now(CLOCK_MONOTONIC); - r = terminal_get_size_by_dsr(STDIN_FILENO, STDOUT_FILENO, &rows, &columns); + r = terminal_get_size(STDIN_FILENO, STDOUT_FILENO, &rows, &columns, /* try_dsr= */ true, /* try_csi18= */ false); log_info("%s took %s", __func__+5, FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), n), USEC_PER_MSEC)); if (r < 0) @@ -279,23 +278,19 @@ TEST(query_term_for_tty) { } TEST(terminal_is_pty_fd) { - _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF; int r; - fd1 = openpt_allocate(O_RDWR, /* ret_peer_path= */ NULL); - assert_se(fd1 >= 0); - assert_se(terminal_is_pty_fd(fd1) > 0); + _cleanup_close_ int fd1 = ASSERT_OK(openpt_allocate(O_RDWR, /* ret_peer_path= */ NULL)); + ASSERT_OK_POSITIVE(terminal_is_pty_fd(fd1)); - fd2 = pty_open_peer(fd1, O_RDWR|O_CLOEXEC|O_NOCTTY); - assert_se(fd2 >= 0); - assert_se(terminal_is_pty_fd(fd2) > 0); + _cleanup_close_ int fd2 = ASSERT_OK(pty_open_peer(fd1, O_RDWR|O_CLOEXEC|O_NOCTTY)); + ASSERT_OK_POSITIVE(terminal_is_pty_fd(fd2)); fd1 = safe_close(fd1); fd2 = safe_close(fd2); - fd1 = open("/dev/null", O_RDONLY|O_CLOEXEC); - assert_se(fd1 >= 0); - assert_se(terminal_is_pty_fd(fd1) == 0); + fd1 = ASSERT_OK_ERRNO(open("/dev/null", O_RDONLY|O_CLOEXEC)); + ASSERT_OK_ZERO(terminal_is_pty_fd(fd1)); /* In container managers real tty devices might be weird, avoid them. */ r = path_is_read_only_fs("/sys"); @@ -313,7 +308,7 @@ TEST(terminal_is_pty_fd) { continue; } - assert_se(terminal_is_pty_fd(tfd) <= 0); + ASSERT_LE(terminal_is_pty_fd(tfd), 0); } } @@ -338,13 +333,17 @@ TEST(get_color_mode) { test_get_color_mode_with_env("SYSTEMD_COLORS", "auto-256", terminal_is_dumb() ? COLOR_OFF : COLOR_256); test_get_color_mode_with_env("SYSTEMD_COLORS", "auto-24bit", terminal_is_dumb() ? COLOR_OFF : COLOR_24BIT); ASSERT_OK_ERRNO(setenv("COLORTERM", "truecolor", true)); - test_get_color_mode_with_env("SYSTEMD_COLORS", "1", terminal_is_dumb() ? COLOR_OFF : COLOR_24BIT); - test_get_color_mode_with_env("SYSTEMD_COLORS", "yes", terminal_is_dumb() ? COLOR_OFF : COLOR_24BIT); + /* SYSTEMD_COLORS=1/yes/true all map to COLOR_TRUE and must force colors on + * even when stdout is not a TTY (piped). With COLORTERM=truecolor, we get 24bit. */ + test_get_color_mode_with_env("SYSTEMD_COLORS", "1", COLOR_24BIT); + test_get_color_mode_with_env("SYSTEMD_COLORS", "yes", COLOR_24BIT); ASSERT_OK_ERRNO(unsetenv("COLORTERM")); - test_get_color_mode_with_env("SYSTEMD_COLORS", "true", terminal_is_dumb() ? COLOR_OFF : COLOR_256); + /* Without COLORTERM, COLOR_TRUE still bypasses the TTY check but autodetects depth. */ + test_get_color_mode_with_env("SYSTEMD_COLORS", "true", COLOR_256); ASSERT_OK_ERRNO(setenv("NO_COLOR", "1", true)); - test_get_color_mode_with_env("SYSTEMD_COLORS", "true", terminal_is_dumb() ? COLOR_OFF : COLOR_256); + /* COLOR_TRUE also bypasses NO_COLOR. */ + test_get_color_mode_with_env("SYSTEMD_COLORS", "true", COLOR_256); test_get_color_mode_with_env("SYSTEMD_COLORS", "auto-16", COLOR_OFF); test_get_color_mode_with_env("SYSTEMD_COLORS", "auto-256", COLOR_OFF); test_get_color_mode_with_env("SYSTEMD_COLORS", "auto-24bit", COLOR_OFF); @@ -370,31 +369,27 @@ TEST(terminal_reset_defensive) { } TEST(pty_open_peer) { - _cleanup_close_ int pty_fd = -EBADF, peer_fd = -EBADF; _cleanup_free_ char *pty_path = NULL; - pty_fd = openpt_allocate(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK, &pty_path); - assert_se(pty_fd >= 0); - assert_se(pty_path); + _cleanup_close_ int pty_fd = ASSERT_OK(openpt_allocate(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK, &pty_path)); + ASSERT_NOT_NULL(pty_path); - peer_fd = pty_open_peer(pty_fd, O_RDWR|O_NOCTTY|O_CLOEXEC); - assert_se(peer_fd >= 0); + _cleanup_close_ int peer_fd = ASSERT_OK(pty_open_peer(pty_fd, O_RDWR|O_NOCTTY|O_CLOEXEC)); static const char x[] = { 'x', '\n' }; - assert_se(write(pty_fd, x, sizeof(x)) == 2); + ASSERT_OK_EQ_ERRNO(write(pty_fd, x, sizeof(x)), (ssize_t) sizeof(x)); char buf[3]; - assert_se(read(peer_fd, &buf, sizeof(buf)) == sizeof(x)); - assert_se(buf[0] == x[0]); - assert_se(buf[1] == x[1]); + ASSERT_OK_EQ_ERRNO(read(peer_fd, &buf, sizeof(buf)), (ssize_t) sizeof(x)); + ASSERT_EQ(buf[0], x[0]); + ASSERT_EQ(buf[1], x[1]); } TEST(terminal_new_session) { - _cleanup_close_ int pty_fd = -EBADF, peer_fd = -EBADF; int r; - ASSERT_OK(pty_fd = openpt_allocate(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK, NULL)); - ASSERT_OK(peer_fd = pty_open_peer(pty_fd, O_RDWR|O_NOCTTY|O_CLOEXEC)); + _cleanup_close_ int pty_fd = ASSERT_OK(openpt_allocate(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK, NULL)); + _cleanup_close_ int peer_fd = ASSERT_OK(pty_open_peer(pty_fd, O_RDWR|O_NOCTTY|O_CLOEXEC)); r = pidref_safe_fork_full( "test-term-session", diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c index d5d4992f827b4..8250a03e29876 100644 --- a/src/test/test-time-util.c +++ b/src/test/test-time-util.c @@ -439,7 +439,7 @@ static void test_format_timestamp_impl(usec_t x) { * Also, the same may happen on MSK timezone (e.g. Europe/Volgograd or Europe/Kirov). */ bool ignore = (streq_ptr(getenv("TZ"), "Africa/Windhoek") || - streq_ptr(get_tzname(/* dst= */ false), "MSK")) && + STRPTR_IN_SET(get_tzname(/* dst= */ false), "CAT", "EAT", "MSK", "WET")) && (x_sec > y_sec ? x_sec - y_sec : y_sec - x_sec) == 3600; log_full(ignore ? LOG_WARNING : LOG_ERR, @@ -1113,6 +1113,8 @@ TEST(usec_shift_clock) { assert_se(usec_shift_clock(USEC_INFINITY, CLOCK_REALTIME, CLOCK_MONOTONIC) == USEC_INFINITY); + /* Silence static analyzers */ + assert_cc(9 * USEC_PER_HOUR <= USEC_INFINITY); assert_similar(usec_shift_clock(rt + USEC_PER_HOUR, CLOCK_REALTIME, CLOCK_MONOTONIC), mn + USEC_PER_HOUR); assert_similar(usec_shift_clock(rt + 2*USEC_PER_HOUR, CLOCK_REALTIME, CLOCK_BOOTTIME), bt + 2*USEC_PER_HOUR); assert_se(usec_shift_clock(rt + 3*USEC_PER_HOUR, CLOCK_REALTIME, CLOCK_REALTIME_ALARM) == rt + 3*USEC_PER_HOUR); @@ -1281,4 +1283,62 @@ static int intro(void) { return EXIT_SUCCESS; } +TEST(parse_calendar_date) { + usec_t usec; + + /* Valid dates */ + ASSERT_OK(parse_calendar_date("2000-01-01", &usec)); + ASSERT_OK(parse_calendar_date("1970-01-01", &usec)); + ASSERT_EQ(usec, 0u); /* epoch */ + ASSERT_OK(parse_calendar_date("2000-02-29", &usec)); /* leap year */ + + /* NULL ret is allowed (validation only) */ + ASSERT_OK(parse_calendar_date("2000-06-15", NULL)); + + /* Non-normalized dates */ + ASSERT_ERROR(parse_calendar_date("2023-02-29", &usec), EINVAL); /* not a leap year */ + ASSERT_ERROR(parse_calendar_date("2023-04-31", &usec), EINVAL); /* April has 30 days */ + ASSERT_ERROR(parse_calendar_date("2023-13-01", &usec), EINVAL); /* month 13 */ + ASSERT_ERROR(parse_calendar_date("2023-00-01", &usec), EINVAL); /* month 0 */ + + /* Malformed input */ + ASSERT_ERROR(parse_calendar_date("", &usec), EINVAL); + ASSERT_ERROR(parse_calendar_date("not-a-date", &usec), EINVAL); + ASSERT_ERROR(parse_calendar_date("2023-06-15T00:00:00", &usec), EINVAL); /* trailing time */ + ASSERT_ERROR(parse_calendar_date("2023/06/15", &usec), EINVAL); /* wrong separator */ + ASSERT_ERROR(parse_calendar_date("06-15-2023", &usec), EINVAL); /* wrong order */ +} + +TEST(parse_birth_date) { + struct tm tm; + + /* Valid dates */ + ASSERT_OK(parse_birth_date("2000-06-15", &tm)); + ASSERT_EQ(tm.tm_year, 100); /* 2000 - 1900 */ + ASSERT_EQ(tm.tm_mon, 5); /* June, 0-indexed */ + ASSERT_EQ(tm.tm_mday, 15); + + /* Pre-epoch dates */ + ASSERT_OK(parse_birth_date("1960-03-25", &tm)); + ASSERT_EQ(tm.tm_year, 60); + ASSERT_EQ(tm.tm_mon, 2); + ASSERT_EQ(tm.tm_mday, 25); + + /* NULL ret is allowed (validation only) */ + ASSERT_OK(parse_birth_date("2000-01-01", NULL)); + + /* Non-date fields should not be relied upon */ + ASSERT_OK(parse_birth_date("2000-06-15", &tm)); + ASSERT_FALSE(BIRTH_DATE_IS_SET(BIRTH_DATE_UNSET)); + + /* Non-normalized dates */ + ASSERT_ERROR(parse_birth_date("2023-02-29", &tm), EINVAL); + ASSERT_ERROR(parse_birth_date("2023-04-31", &tm), EINVAL); + + /* Malformed input */ + ASSERT_ERROR(parse_birth_date("", &tm), EINVAL); + ASSERT_ERROR(parse_birth_date("not-a-date", &tm), EINVAL); + ASSERT_ERROR(parse_birth_date("2023-06-15T00:00:00", &tm), EINVAL); +} + DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c index a6164f2677d52..c9a2e0a9bb80b 100644 --- a/src/test/test-tpm2.c +++ b/src/test/test-tpm2.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "architecture.h" +#include "crypto-util.h" #include "hexdecoct.h" #include "tests.h" #include "tpm2-util.h" @@ -782,25 +783,25 @@ TEST(tpm2b_public_to_openssl_pkey) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_rsa = NULL; assert_se(tpm2_tpm2b_public_to_openssl_pkey(&public, &pkey_rsa) >= 0); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx_rsa = EVP_PKEY_CTX_new(pkey_rsa, NULL); + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx_rsa = sym_EVP_PKEY_CTX_new(pkey_rsa, NULL); assert_se(ctx_rsa); - assert_se(EVP_PKEY_verify_init(ctx_rsa) == 1); - assert_se(EVP_PKEY_CTX_set_signature_md(ctx_rsa, EVP_sha256()) > 0); + assert_se(sym_EVP_PKEY_verify_init(ctx_rsa) == 1); + assert_se(sym_EVP_PKEY_CTX_set_signature_md(ctx_rsa, sym_EVP_sha256()) > 0); DEFINE_HEX_PTR(sig_rsa, "9f70a9e68911be3ec464cae91126328307bf355872127e042d6c61e0a80982872c151033bcf727abfae5fc9500c923120011e7ef4aa5fc690a59a034697b6022c141b4b209e2df6f4b282288cd9181073fbe7158ce113c79d87623423c1f3996ff931e59cc91db74f8e8656215b1436fc93ddec0f1f8fa8510826e674b250f047e6cba94c95ff98072a286baca94646b577974a1e00d56c21944e38960d8ee90511a2f938e5cf1ac7b7cc7ff8e3ac001d321254d3e4f988b90e9f6f873c26ecd0a12a626b3474833cdbb9e9f793238f6c97ee5b75a1a89bb7a7858d34ecfa6d34ac58d95085e6c4fbbebd47a4364be2725c2c6b3fa15d916f3c0b62a66fe76ae"); - assert_se(EVP_PKEY_verify(ctx_rsa, sig_rsa, sig_rsa_len, (unsigned char*) msg, msg_len) == 1); + assert_se(sym_EVP_PKEY_verify(ctx_rsa, sig_rsa, sig_rsa_len, (unsigned char*) msg, msg_len) == 1); /* ECC */ tpm2b_public_ecc_init(&public, TPM2_ECC_NIST_P256, "6fc0ecf3645c673ab7e86d1ec5b315afb950257c5f68ab23296160006711fac2", "8dd2ef7a2c9ecede91493ba98c8fb3f893aff325c6a1e0f752c657b2d6ca1413"); _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_ecc = NULL; assert_se(tpm2_tpm2b_public_to_openssl_pkey(&public, &pkey_ecc) >= 0); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx_ecc = EVP_PKEY_CTX_new(pkey_ecc, NULL); + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx_ecc = sym_EVP_PKEY_CTX_new(pkey_ecc, NULL); assert_se(ctx_ecc); - assert_se(EVP_PKEY_verify_init(ctx_ecc) == 1); + assert_se(sym_EVP_PKEY_verify_init(ctx_ecc) == 1); DEFINE_HEX_PTR(sig_ecc, "304602210092447ac0b5b32e90923f79bb4aba864b9c546a9900cf193a83243d35d189a2110221009a8b4df1dfa85e225eff9c606694d4d205a7a3968c9552f50bc2790209a90001"); - assert_se(EVP_PKEY_verify(ctx_ecc, sig_ecc, sig_ecc_len, (unsigned char*) msg, msg_len) == 1); + assert_se(sym_EVP_PKEY_verify(ctx_ecc, sig_ecc, sig_ecc_len, (unsigned char*) msg, msg_len) == 1); } static void get_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *ret) { @@ -1271,7 +1272,7 @@ static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) { &srk, &unsealed_secret) >= 0); - assert_se(iovec_memcmp(&secret, &unsealed_secret) == 0); + assert_se(iovec_equal(&secret, &unsealed_secret)); } static void check_seal_unseal(Tpm2Context *c) { diff --git a/src/test/test-user-record.c b/src/test/test-user-record.c index c807ffe83afd6..480a3f33eb2ef 100644 --- a/src/test/test-user-record.c +++ b/src/test/test-user-record.c @@ -96,6 +96,15 @@ TEST(self_changes) { SD_JSON_BUILD_PAIR_OBJECT("privileged", SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999))); ASSERT_TRUE(user_record_self_changes_allowed(curr, new)); + + /* birthDate is NOT self-modifiable (admin-only) */ + USER(&curr, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_STRING("birthDate", "1990-01-01")); + USER(&new, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_STRING("birthDate", "1990-06-15")); + ASSERT_FALSE(user_record_self_changes_allowed(curr, new)); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-varlink-idl-machine.c b/src/test/test-varlink-idl-machine.c new file mode 100644 index 0000000000000..1064545fae2d0 --- /dev/null +++ b/src/test/test-varlink-idl-machine.c @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "machine.h" +#include "tests.h" +#include "test-varlink-idl-util.h" +#include "varlink-io.systemd.Machine.h" + +TEST(machine_enums_idl) { + TEST_IDL_ENUM(MachineClass, machine_class, vl_type_MachineClass); + TEST_IDL_ENUM(KillWhom, kill_whom, vl_type_KillWhom); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-varlink-idl-manager.c b/src/test/test-varlink-idl-manager.c new file mode 100644 index 0000000000000..da2533b2acd7e --- /dev/null +++ b/src/test/test-varlink-idl-manager.c @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "cgroup.h" +#include "emergency-action.h" +#include "execute.h" +#include "log.h" +#include "tests.h" +#include "test-varlink-idl-util.h" +#include "unit.h" +#include "varlink-idl-common.h" +#include "varlink-io.systemd.Manager.h" + +TEST(manager_enums_idl) { + /* ManagerContext enums */ + TEST_IDL_ENUM(LogTarget, log_target, vl_type_LogTarget); + TEST_IDL_ENUM(OOMPolicy, oom_policy, vl_type_OOMPolicy); + + /* ExecOutput values like "kmsg+console" contain '+' which becomes '_' via underscorify, + * but dashify won't restore it, so from_string round-trip fails. Test to_string direction only. */ + TEST_IDL_ENUM_TO_STRING(ExecOutput, exec_output, vl_type_ExecOutputType); + TEST_IDL_ENUM(CGroupPressureWatch, cgroup_pressure_watch, vl_type_CGroupPressureWatch); + TEST_IDL_ENUM(EmergencyAction, emergency_action, vl_type_EmergencyAction); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-varlink-idl-unit.c b/src/test/test-varlink-idl-unit.c new file mode 100644 index 0000000000000..2f4bb1c9e9156 --- /dev/null +++ b/src/test/test-varlink-idl-unit.c @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "automount.h" +#include "cgroup.h" +#include "ioprio-util.h" +#include "kill.h" +#include "mount.h" +#include "numa-util.h" +#include "process-util.h" +#include "tests.h" +#include "test-varlink-idl-util.h" +#include "unit.h" +#include "varlink-idl-common.h" +#include "varlink-io.systemd.Unit.h" + +TEST(unit_enums_idl) { + /* ExecContext enums */ + TEST_IDL_ENUM(ExecInput, exec_input, vl_type_ExecInputType); + TEST_IDL_ENUM_TO_STRING(ExecOutput, exec_output, vl_type_ExecOutputType); + TEST_IDL_ENUM(ExecUtmpMode, exec_utmp_mode, vl_type_ExecUtmpMode); + TEST_IDL_ENUM(ExecPreserveMode, exec_preserve_mode, vl_type_ExecPreserveMode); + TEST_IDL_ENUM(ExecKeyringMode, exec_keyring_mode, vl_type_ExecKeyringMode); + TEST_IDL_ENUM(MemoryTHP, memory_thp, vl_type_MemoryTHP); + TEST_IDL_ENUM(ProtectProc, protect_proc, vl_type_ProtectProc); + TEST_IDL_ENUM(ProcSubset, proc_subset, vl_type_ProcSubset); + TEST_IDL_ENUM(ProtectSystem, protect_system, vl_type_ProtectSystem); + TEST_IDL_ENUM(ProtectHome, protect_home, vl_type_ProtectHome); + TEST_IDL_ENUM(PrivateTmp, private_tmp, vl_type_PrivateTmp); + TEST_IDL_ENUM(PrivateUsers, private_users, vl_type_PrivateUsers); + TEST_IDL_ENUM(ProtectHostname, protect_hostname, vl_type_ProtectHostname); + TEST_IDL_ENUM(ProtectControlGroups, protect_control_groups, vl_type_ProtectControlGroups); + TEST_IDL_ENUM(PrivatePIDs, private_pids, vl_type_PrivatePIDs); + TEST_IDL_ENUM(PrivateBPF, private_bpf, vl_type_PrivateBPF); + + /* sched_policy table has gaps (SCHED_IDLE=5, SCHED_EXT=7), so only test from_string direction */ + TEST_IDL_ENUM_FROM_STRING(int, sched_policy, vl_type_CPUSchedulingPolicy); + /* ioprio_class uses _alloc variant for to_string, so only test from_string direction */ + TEST_IDL_ENUM_FROM_STRING(int, ioprio_class, vl_type_IOSchedulingClass); + TEST_IDL_ENUM(int, mpol, vl_type_NUMAPolicy); + + /* mount_propagation_flag has non-standard from_string API, test manually */ + test_enum_to_string_name("shared", &vl_type_MountPropagationFlag); + test_enum_to_string_name("slave", &vl_type_MountPropagationFlag); + test_enum_to_string_name("private", &vl_type_MountPropagationFlag); + + /* KillContext enums */ + TEST_IDL_ENUM(KillMode, kill_mode, vl_type_KillMode); + + /* CGroupContext enums */ + TEST_IDL_ENUM(CGroupDevicePolicy, cgroup_device_policy, vl_type_CGroupDevicePolicy); + TEST_IDL_ENUM(ManagedOOMMode, managed_oom_mode, vl_type_ManagedOOMMode); + TEST_IDL_ENUM(ManagedOOMPreference, managed_oom_preference, vl_type_ManagedOOMPreference); + TEST_IDL_ENUM(CGroupPressureWatch, cgroup_pressure_watch, vl_type_CGroupPressureWatch); + TEST_IDL_ENUM(CGroupController, cgroup_controller, vl_type_CGroupController); + + /* AutomountRuntime enums */ + TEST_IDL_ENUM(AutomountResult, automount_result, vl_type_AutomountResult); + + /* MountRuntime enums */ + TEST_IDL_ENUM(MountResult, mount_result, vl_type_MountResult); + + /* UnitContext enums */ + TEST_IDL_ENUM(CollectMode, collect_mode, vl_type_CollectMode); + TEST_IDL_ENUM(EmergencyAction, emergency_action, vl_type_EmergencyAction); + TEST_IDL_ENUM(JobMode, job_mode, vl_type_JobMode); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-varlink-idl-util.h b/src/test/test-varlink-idl-util.h new file mode 100644 index 0000000000000..7a27230d89963 --- /dev/null +++ b/src/test/test-varlink-idl-util.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +#include "json-util.h" +#include "string-util.h" + +static inline void test_enum_to_string_name(const char *n, const sd_varlink_symbol *symbol) { + assert(n); + assert(symbol); + + assert(symbol->symbol_type == SD_VARLINK_ENUM_TYPE); + _cleanup_free_ char *m = ASSERT_PTR(json_underscorify(strdup(n))); + + bool found = false; + for (const sd_varlink_field *f = symbol->fields; f->name; f++) { + if (f->field_type == _SD_VARLINK_FIELD_COMMENT) + continue; + + assert(f->field_type == SD_VARLINK_ENUM_VALUE); + if (streq(m, f->name)) { + found = true; + break; + } + } + + log_debug("'%s' found in '%s': %s", m, strna(symbol->name), yes_no(found)); + assert(found); +} + +#define TEST_IDL_ENUM_TO_STRING(type, ename, symbol) \ + for (type t = 0;; t++) { \ + const char *n = ename##_to_string(t); \ + if (!n) \ + break; \ + test_enum_to_string_name(n, &(symbol)); \ + } + +#define TEST_IDL_ENUM_FROM_STRING(type, ename, symbol) \ + for (const sd_varlink_field *f = (symbol).fields; f->name; f++) { \ + if (f->field_type == _SD_VARLINK_FIELD_COMMENT) \ + continue; \ + assert(f->field_type == SD_VARLINK_ENUM_VALUE); \ + _cleanup_free_ char *m = ASSERT_PTR(json_dashify(strdup(f->name))); \ + type t = ename##_from_string(m); \ + log_debug("'%s' of '%s' translates: %s", f->name, strna((symbol).name), yes_no(t >= 0)); \ + assert(t >= 0); \ + } + +#define TEST_IDL_ENUM(type, name, symbol) \ + do { \ + TEST_IDL_ENUM_TO_STRING(type, name, symbol); \ + TEST_IDL_ENUM_FROM_STRING(type, name, symbol); \ + } while (false) diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index c6aca36677745..d0f3b914471d2 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -11,11 +11,11 @@ #include "discover-image.h" #include "fd-util.h" #include "gpt.h" -#include "json-util.h" #include "network-util.h" #include "pretty-print.h" #include "resolve-util.h" #include "tests.h" +#include "test-varlink-idl-util.h" #include "varlink-idl-util.h" #include "varlink-io.systemd.h" #include "varlink-io.systemd.AskPassword.h" @@ -24,6 +24,7 @@ #include "varlink-io.systemd.FactoryReset.h" #include "varlink-io.systemd.Hostname.h" #include "varlink-io.systemd.Import.h" +#include "varlink-io.systemd.InstanceMetadata.h" #include "varlink-io.systemd.Journal.h" #include "varlink-io.systemd.JournalAccess.h" #include "varlink-io.systemd.Login.h" @@ -190,6 +191,7 @@ TEST(parse_format) { &vl_interface_io_systemd_FactoryReset, &vl_interface_io_systemd_Hostname, &vl_interface_io_systemd_Import, + &vl_interface_io_systemd_InstanceMetadata, &vl_interface_io_systemd_Journal, &vl_interface_io_systemd_JournalAccess, &vl_interface_io_systemd_Login, @@ -334,8 +336,8 @@ TEST(validate_json) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; assert_se(sd_json_build(&v, SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("a", SD_JSON_BUILD_STRING("x")), - SD_JSON_BUILD_PAIR("b", SD_JSON_BUILD_UNSIGNED(44)), + SD_JSON_BUILD_PAIR_STRING("a", "x"), + SD_JSON_BUILD_PAIR_UNSIGNED("b", 44), SD_JSON_BUILD_PAIR("d", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_UNSIGNED(5), SD_JSON_BUILD_UNSIGNED(7), SD_JSON_BUILD_UNSIGNED(107))), SD_JSON_BUILD_PAIR("g", SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("f", SD_JSON_BUILD_REAL(0.5f)))))) >= 0); @@ -479,54 +481,6 @@ TEST(validate_method_call) { assert_se(pthread_join(t, NULL) == 0); } -static void test_enum_to_string_name(const char *n, const sd_varlink_symbol *symbol) { - assert(n); - assert(symbol); - - assert(symbol->symbol_type == SD_VARLINK_ENUM_TYPE); - _cleanup_free_ char *m = ASSERT_PTR(json_underscorify(strdup(n))); - - bool found = false; - for (const sd_varlink_field *f = symbol->fields; f->name; f++) { - if (f->field_type == _SD_VARLINK_FIELD_COMMENT) - continue; - - assert(f->field_type == SD_VARLINK_ENUM_VALUE); - if (streq(m, f->name)) { - found = true; - break; - } - } - - log_debug("'%s' found in '%s': %s", m, strna(symbol->name), yes_no(found)); - assert(found); -} - -#define TEST_IDL_ENUM_TO_STRING(type, ename, symbol) \ - for (type t = 0;; t++) { \ - const char *n = ename##_to_string(t); \ - if (!n) \ - break; \ - test_enum_to_string_name(n, &(symbol)); \ - } - -#define TEST_IDL_ENUM_FROM_STRING(type, ename, symbol) \ - for (const sd_varlink_field *f = (symbol).fields; f->name; f++) { \ - if (f->field_type == _SD_VARLINK_FIELD_COMMENT) \ - continue; \ - assert(f->field_type == SD_VARLINK_ENUM_VALUE); \ - _cleanup_free_ char *m = ASSERT_PTR(json_dashify(strdup(f->name))); \ - type t = ename##_from_string(m); \ - log_debug("'%s' of '%s' translates: %s", f->name, strna((symbol).name), yes_no(t >= 0)); \ - assert(t >= 0); \ - } - -#define TEST_IDL_ENUM(type, name, symbol) \ - do { \ - TEST_IDL_ENUM_TO_STRING(type, name, symbol); \ - TEST_IDL_ENUM_FROM_STRING(type, name, symbol); \ - } while (false) - TEST(enums_idl) { TEST_IDL_ENUM(BootEntryType, boot_entry_type, vl_type_BootEntryType); TEST_IDL_ENUM_TO_STRING(BootEntrySource, boot_entry_source, vl_type_BootEntrySource); @@ -581,4 +535,84 @@ TEST(any) { ASSERT_NULL(bad_field); } +static SD_VARLINK_DEFINE_METHOD( + ArrayTest, + SD_VARLINK_DEFINE_INPUT(arr, SD_VARLINK_INT, SD_VARLINK_ARRAY)); + +static SD_VARLINK_DEFINE_METHOD( + MapTest, + SD_VARLINK_DEFINE_INPUT(m, SD_VARLINK_STRING, SD_VARLINK_MAP)); + +TEST(null_array_element) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + /* Build an array with a null element - this should be rejected gracefully, not crash */ + ASSERT_OK(sd_json_buildo(&v, + SD_JSON_BUILD_PAIR("arr", SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_INTEGER(1), + SD_JSON_BUILD_NULL, + SD_JSON_BUILD_INTEGER(3))))); + + const char *bad_field = NULL; + ASSERT_ERROR(varlink_idl_validate_method_call(&vl_method_ArrayTest, v, /* flags= */ 0, &bad_field), EMEDIUMTYPE); + ASSERT_STREQ(bad_field, "arr"); +} + +TEST(null_map_element) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + /* Build a map with a null value - this should be rejected gracefully, not crash */ + ASSERT_OK(sd_json_buildo(&v, + SD_JSON_BUILD_PAIR("m", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("key1", "value1"), + SD_JSON_BUILD_PAIR_NULL("key2"), + SD_JSON_BUILD_PAIR_STRING("key3", "value3"))))); + + const char *bad_field = NULL; + ASSERT_ERROR(varlink_idl_validate_method_call(&vl_method_MapTest, v, /* flags= */ 0, &bad_field), EMEDIUMTYPE); + ASSERT_STREQ(bad_field, "m"); +} + +static SD_VARLINK_DEFINE_METHOD_FULL( + SupportsMoreMethod, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_DEFINE_OUTPUT(result, SD_VARLINK_STRING, 0)); + +TEST(reply_continues_with_more_flag) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + ASSERT_OK(sd_json_buildo(&v, SD_JSON_BUILD_PAIR_STRING("result", "hello"))); + + const char *bad_field = NULL; + ASSERT_OK(varlink_idl_validate_method_reply( + &vl_method_SupportsMoreMethod, v, SD_VARLINK_REPLY_CONTINUES, &bad_field)); + ASSERT_NULL(bad_field); + + ASSERT_OK(varlink_idl_validate_method_reply( + &vl_method_SupportsMoreMethod, v, /* flags= */ 0, &bad_field)); + ASSERT_NULL(bad_field); +} + +static SD_VARLINK_DEFINE_METHOD( + NoMoreMethod, + SD_VARLINK_DEFINE_OUTPUT(result, SD_VARLINK_STRING, 0)); + +TEST(reply_continues_without_more_flag) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + ASSERT_OK(sd_json_buildo(&v, SD_JSON_BUILD_PAIR_STRING("result", "hello"))); + + const char *bad_field = NULL; + /* Request a "continues" reply from a method without SD_VARLINK_SUPPORTS_MORE/REQUIRES_MORE - this + * should fail the validation with EBADE */ + ASSERT_ERROR(varlink_idl_validate_method_reply( + &vl_method_NoMoreMethod, v, SD_VARLINK_REPLY_CONTINUES, &bad_field), EBADE); + ASSERT_NULL(bad_field); + + /* Without the "continues" flag, validation should succeed */ + ASSERT_OK(varlink_idl_validate_method_reply( + &vl_method_NoMoreMethod, v, /* flags= */ 0, &bad_field)); + ASSERT_NULL(bad_field); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-varlink.c b/src/test/test-varlink.c index bf1390fba1dc4..72edc033dd068 100644 --- a/src/test/test-varlink.c +++ b/src/test/test-varlink.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "sd-event.h" @@ -11,9 +12,12 @@ #include "sd-varlink.h" #include "fd-util.h" +#include "io-util.h" #include "json-util.h" #include "memfd-util.h" +#include "path-util.h" #include "rm-rf.h" +#include "socket-util.h" #include "tests.h" #include "tmpfile-util.h" #include "varlink-util.h" @@ -45,7 +49,7 @@ static int method_something(sd_varlink *link, sd_json_variant *parameters, sd_va y = sd_json_variant_integer(b); - r = sd_json_build(&ret, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("sum", SD_JSON_BUILD_INTEGER(x + y)))); + r = sd_json_build(&ret, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("sum", x + y))); if (r < 0) return r; @@ -75,7 +79,7 @@ static int method_something_more(sd_varlink *link, sd_json_variant *parameters, for (int i = 0; i < 5; i++) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; - r = sd_json_build(&w, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("sum", SD_JSON_BUILD_INTEGER(s.x + (s.y * i))))); + r = sd_json_build(&w, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("sum", s.x + (s.y * i)))); if (r < 0) return r; @@ -84,7 +88,7 @@ static int method_something_more(sd_varlink *link, sd_json_variant *parameters, return r; } - r = sd_json_build(&ret, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("sum", SD_JSON_BUILD_INTEGER(s.x + (s.y * 5))))); + r = sd_json_build(&ret, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("sum", s.x + (s.y * 5)))); if (r < 0) return r; @@ -125,7 +129,7 @@ static int method_passfd(sd_varlink *link, sd_json_variant *parameters, sd_varli ASSERT_OK(vv = memfd_new_and_seal_string("data", "miau")); ASSERT_OK(ww = memfd_new_and_seal_string("data", "wuff")); - r = sd_json_build(&ret, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("yo", SD_JSON_BUILD_INTEGER(88)))); + r = sd_json_build(&ret, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("yo", 88))); if (r < 0) return r; @@ -222,7 +226,7 @@ static void flood_test(const char *address) { ASSERT_OK(asprintf(&t, "flood-%zu", k)); ASSERT_OK(sd_varlink_set_description(connections[k], t)); ASSERT_OK(sd_varlink_attach_event(connections[k], e, k)); - ASSERT_OK(sd_varlink_sendb(connections[k], "io.test.Rubbish", SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_INTEGER(k))))); + ASSERT_OK(sd_varlink_sendb(connections[k], "io.test.Rubbish", SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("id", k)))); } /* Then, create one more, which should fail */ @@ -253,8 +257,8 @@ static void *thread(void *arg) { const char *error_id, *e; int x = 0; - ASSERT_OK(sd_json_build(&i, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("a", SD_JSON_BUILD_INTEGER(88)), - SD_JSON_BUILD_PAIR("b", SD_JSON_BUILD_INTEGER(99))))); + ASSERT_OK(sd_json_build(&i, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("a", 88), + SD_JSON_BUILD_PAIR_INTEGER("b", 99)))); ASSERT_OK(sd_varlink_connect_address(&c, arg)); ASSERT_OK(sd_varlink_set_description(c, "thread-client")); @@ -262,8 +266,8 @@ static void *thread(void *arg) { ASSERT_OK(sd_varlink_set_allow_fd_passing_output(c, true)); /* Test that client is able to perform two sequential sd_varlink_collect calls if first resulted in an error */ - ASSERT_OK(sd_json_build(&wrong, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("a", SD_JSON_BUILD_INTEGER(88)), - SD_JSON_BUILD_PAIR("c", SD_JSON_BUILD_INTEGER(99))))); + ASSERT_OK(sd_json_build(&wrong, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("a", 88), + SD_JSON_BUILD_PAIR_INTEGER("c", 99)))); ASSERT_OK(sd_varlink_collect(c, "io.test.DoSomethingMore", wrong, &j, &error_id)); ASSERT_STREQ(error_id, "org.varlink.service.InvalidParameter"); @@ -292,7 +296,7 @@ static void *thread(void *arg) { ASSERT_OK_EQ(sd_varlink_push_fd(c, fd2), 1); ASSERT_OK_EQ(sd_varlink_push_fd(c, fd3), 2); - ASSERT_OK(sd_varlink_callb(c, "io.test.PassFD", &o, &e, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("fd", SD_JSON_BUILD_STRING("whoop"))))); + ASSERT_OK(sd_varlink_callb(c, "io.test.PassFD", &o, &e, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_STRING("fd", "whoop")))); ASSERT_NULL(e); int fd4, fd5; @@ -302,7 +306,7 @@ static void *thread(void *arg) { test_fd(fd4, "miau", 4); test_fd(fd5, "wuff", 4); - ASSERT_OK(sd_varlink_callb(c, "io.test.PassFD", &o, &e, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("fdx", SD_JSON_BUILD_STRING("whoopx"))))); + ASSERT_OK(sd_varlink_callb(c, "io.test.PassFD", &o, &e, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_STRING("fdx", "whoopx")))); ASSERT_TRUE(sd_varlink_error_is_invalid_parameter(e, o, "fd")); ASSERT_OK(sd_varlink_callb(c, "io.test.IDontExist", &o, &e, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("x", SD_JSON_BUILD_REAL(5.5))))); @@ -371,8 +375,8 @@ TEST(chat) { ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); ASSERT_OK(sd_varlink_server_set_connections_max(s, OVERLOAD_CONNECTIONS)); - ASSERT_OK(sd_json_build(&v, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("a", SD_JSON_BUILD_INTEGER(7)), - SD_JSON_BUILD_PAIR("b", SD_JSON_BUILD_INTEGER(22))))); + ASSERT_OK(sd_json_build(&v, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("a", 7), + SD_JSON_BUILD_PAIR_INTEGER("b", 22)))); ASSERT_OK(sd_varlink_connect_address(&c, sp)); ASSERT_OK(sd_varlink_set_description(c, "main-client")); @@ -443,7 +447,7 @@ TEST(invalid_parameter) { static int method_with_error_sentinel(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { /* Set an error sentinel and return without sending a reply. The sentinel error should be sent automatically. */ - ASSERT_OK(varlink_set_sentinel(link, "io.test.SentinelError")); + ASSERT_OK(sd_varlink_set_sentinel(link, "io.test.SentinelError")); return 0; } @@ -482,7 +486,7 @@ TEST(sentinel_error) { static int method_with_empty_sentinel(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { /* Set an empty sentinel and return without sending a reply. An empty reply should be sent automatically. */ - ASSERT_OK(varlink_set_sentinel(link, /* error_id= */ NULL)); + ASSERT_OK(sd_varlink_set_sentinel(link, /* error_id= */ NULL)); return 0; } @@ -522,7 +526,7 @@ TEST(sentinel_empty) { static int method_with_sentinel_but_reply(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { /* Set a sentinel but also send a reply. The sentinel should not be used. */ - ASSERT_OK(varlink_set_sentinel(link, "io.test.SentinelError")); + ASSERT_OK(sd_varlink_set_sentinel(link, "io.test.SentinelError")); return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("result", "explicit-reply")); } @@ -561,10 +565,10 @@ TEST(sentinel_with_explicit_reply) { } static int method_with_oneway_sentinel(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - /* The method was called oneway, so varlink_set_sentinel() should be a no-op and the server should + /* The method was called oneway, so sd_varlink_set_sentinel() should be a no-op and the server should * transition back to idle without sending any reply. */ ASSERT_TRUE(FLAGS_SET(flags, SD_VARLINK_METHOD_ONEWAY)); - ASSERT_OK(varlink_set_sentinel(link, "io.test.SentinelError")); + ASSERT_OK(sd_varlink_set_sentinel(link, "io.test.SentinelError")); return 0; } @@ -619,7 +623,7 @@ static int method_with_fd_sentinel(sd_varlink *link, sd_json_variant *parameters /* Set a sentinel so sd_varlink_reply() defers sending: each reply and its pushed fds are captured in * the queue, and the last one is sent as the final reply when the callback returns. */ - ASSERT_OK(varlink_set_sentinel(link, /* error_id= */ NULL)); + ASSERT_OK(sd_varlink_set_sentinel(link, /* error_id= */ NULL)); /* First reply: push one fd with "alpha" content */ ASSERT_OK(fd1 = memfd_new_and_seal_string("data", "alpha")); @@ -725,7 +729,7 @@ static int reply_notify_then_error(sd_varlink *link, sd_json_variant *parameters TEST(notify_then_error) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; - ASSERT_OK(sd_event_default(&e)); + ASSERT_OK(sd_event_new(&e)); _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; ASSERT_OK(sd_varlink_server_new(&s, 0)); @@ -752,4 +756,293 @@ TEST(notify_then_error) { ASSERT_OK(sd_event_loop(e)); } +static int method_upgrade(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + _cleanup_close_ int input_fd = -EBADF, output_fd = -EBADF; + int r; + + ASSERT_TRUE(FLAGS_SET(flags, SD_VARLINK_METHOD_UPGRADE)); + + r = sd_varlink_reply_and_upgrade(link, /* parameters= */ NULL, &input_fd, &output_fd); + if (r < 0) + return r; + + /* After upgrade, do raw I/O: read until EOF, reverse, write back. + * The client shuts down its write side after sending, so we get a clean EOF. */ + char buf[64] = {}; + ssize_t n = ASSERT_OK(loop_read(input_fd, buf, sizeof(buf) - 1, /* do_poll= */ true)); + ASSERT_GT(n, 0); + + /* Reverse the received bytes */ + for (ssize_t i = 0; i < n / 2; i++) + SWAP_TWO(buf[i], buf[n - 1 - i]); + + ASSERT_OK(loop_write(output_fd, buf, n)); + + return 0; +} + +static int method_upgrade_without_flag(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + int input_fd = -EBADF, output_fd = -EBADF; + + /* Calling reply_and_upgrade without the client requesting it should fail with -EPROTO */ + ASSERT_ERROR(sd_varlink_reply_and_upgrade(link, /* parameters= */ NULL, &input_fd, &output_fd), EPROTO); + + sd_event_exit(sd_varlink_get_event(link), EXIT_SUCCESS); + + return sd_varlink_reply(link, /* parameters= */ NULL); +} + +static void *upgrade_thread(void *arg) { + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *c = NULL; + _cleanup_close_ int input_fd = -EBADF, output_fd = -EBADF; + sd_json_variant *o = NULL; + const char *error_id = NULL; + + ASSERT_OK(sd_varlink_connect_address(&c, arg)); + ASSERT_OK(sd_varlink_set_description(c, "upgrade-client")); + + ASSERT_OK(sd_varlink_call_and_upgrade(c, "io.test.Upgrade", /* parameters= */ NULL, &o, &error_id, &input_fd, &output_fd)); + ASSERT_NULL(error_id); + ASSERT_GE(input_fd, 0); + ASSERT_GE(output_fd, 0); + ASSERT_NE(input_fd, output_fd); /* library dups for bidirectional sockets */ + + /* Send a test string, shut down write side so server sees EOF, then read the reversed reply */ + static const char msg[] = "Hello!"; + ASSERT_OK(loop_write(output_fd, msg, strlen(msg))); + ASSERT_OK_ERRNO(shutdown(output_fd, SHUT_WR)); + + char buf[64] = {}; + ssize_t n = ASSERT_OK(loop_read(input_fd, buf, strlen(msg), /* do_poll= */ true)); + ASSERT_EQ((size_t) n, strlen(msg)); + ASSERT_STREQ(buf, "!olleH"); + + /* Also test that a regular call (without upgrade flag) correctly rejects reply_and_upgrade on + * the server side, and still works as a normal call */ + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *c2 = NULL; + ASSERT_OK(sd_varlink_connect_address(&c2, arg)); + ASSERT_OK(sd_varlink_set_description(c2, "no-upgrade-client")); + ASSERT_OK(sd_varlink_call(c2, "io.test.UpgradeWithoutFlag", /* parameters= */ NULL, &o, &error_id)); + ASSERT_NULL(error_id); + + return NULL; +} + +TEST(upgrade) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + pthread_t t; + const char *sp; + + ASSERT_OK(mkdtemp_malloc("/tmp/varlink-test-XXXXXX", &tmpdir)); + sp = strjoina(tmpdir, "/socket"); + + ASSERT_OK(sd_event_new(&e)); + + ASSERT_OK(sd_varlink_server_new(&s, SD_VARLINK_SERVER_UPGRADABLE)); + ASSERT_OK(sd_varlink_server_set_description(s, "upgrade-server")); + ASSERT_OK(sd_varlink_server_bind_method(s, "io.test.Upgrade", method_upgrade)); + ASSERT_OK(sd_varlink_server_bind_method(s, "io.test.UpgradeWithoutFlag", method_upgrade_without_flag)); + ASSERT_OK(sd_varlink_server_listen_address(s, sp, 0600)); + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + + ASSERT_OK(-pthread_create(&t, NULL, upgrade_thread, (void*) sp)); + + /* Run the event loop until no more connections (the thread will disconnect when done) */ + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(-pthread_join(t, NULL)); +} + +static int method_upgrade_and_exit(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + sd_event *event = ASSERT_PTR(userdata); + + int r = method_upgrade(link, parameters, flags, /* userdata= */ NULL); + + /* Exit the event loop after the upgrade is handled. We can't use sd_varlink_get_event() + * here because the connection is already disconnected after reply_and_upgrade. */ + (void) sd_event_exit(event, r < 0 ? r : EXIT_SUCCESS); + return r; +} + +static void *upgrade_pipelining_thread(void *arg) { + union sockaddr_union sa = {}; + _cleanup_close_ int fd = -EBADF; + + /* Connect a raw socket and pipeline: upgrade JSON + \0 + raw data in a single write. + * This tests that the server's byte-by-byte reading (SD_VARLINK_SERVER_UPGRADABLE) + * doesn't consume the raw data into the varlink input buffer. */ + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + ASSERT_FD(fd); + int addrlen = sockaddr_un_set_path(&sa.un, arg); + ASSERT_OK(addrlen); + ASSERT_OK_ERRNO(connect(fd, &sa.sa, addrlen)); + + /* Build pipelined message: upgrade JSON + \0 + raw payload, all in one write */ + static const char upgrade_msg[] = "{\"method\":\"io.test.Upgrade\",\"upgrade\":true}"; + static const char raw_payload[] = "Pipelined!"; + char send_buf[sizeof(upgrade_msg) + sizeof(raw_payload)]; /* includes \0 from upgrade_msg as delimiter */ + + memcpy(send_buf, upgrade_msg, sizeof(upgrade_msg)); /* copies trailing \0 = varlink delimiter */ + memcpy(send_buf + sizeof(upgrade_msg), raw_payload, sizeof(raw_payload) - 1); + + size_t total = sizeof(upgrade_msg) + strlen(raw_payload); + ASSERT_OK(loop_write(fd, send_buf, total)); + + /* Shut down write side so server's method_upgrade sees EOF after raw payload */ + ASSERT_OK_ERRNO(shutdown(fd, SHUT_WR)); + + /* Read everything: upgrade reply (JSON + \0) + reversed raw payload. The server closes + * the connection after writing, so loop_read() reads until EOF and gets it all. */ + char buf[256] = {}; + ssize_t n = ASSERT_OK(loop_read(fd, buf, sizeof(buf) - 1, /* do_poll= */ true)); + ASSERT_GT(n, 0); + + /* Split at the \0 delimiter between JSON reply and raw payload */ + char *delim = memchr(buf, 0, n); + ASSERT_NOT_NULL(delim); + + char *raw = delim + 1; + size_t raw_size = (size_t) n - (size_t)(raw - buf); + + ASSERT_EQ(raw_size, strlen(raw_payload)); + ASSERT_STREQ(strndupa_safe(raw, raw_size), "!denilepiP"); + + return NULL; +} + +TEST(upgrade_pipelining) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + pthread_t t; + const char *sp; + + ASSERT_OK(mkdtemp_malloc("/tmp/varlink-test-XXXXXX", &tmpdir)); + sp = strjoina(tmpdir, "/socket"); + + ASSERT_OK(sd_event_new(&e)); + + ASSERT_OK(sd_varlink_server_new(&s, SD_VARLINK_SERVER_UPGRADABLE|SD_VARLINK_SERVER_INHERIT_USERDATA)); + ASSERT_OK(sd_varlink_server_set_description(s, "upgrade-pipelining-server")); + ASSERT_OK(sd_varlink_server_bind_method(s, "io.test.Upgrade", method_upgrade_and_exit)); + ASSERT_OK(sd_varlink_server_listen_address(s, sp, 0600)); + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + sd_varlink_server_set_userdata(s, e); + + ASSERT_OK(-pthread_create(&t, NULL, upgrade_pipelining_thread, (void*) sp)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(-pthread_join(t, NULL)); +} + +typedef struct ExecDirServer { + sd_varlink_server *server; + sd_event *event; + const char *name; + pthread_t thread; +} ExecDirServer; + +static int method_execute_dir_ping(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + ExecDirServer *srv = ASSERT_PTR(userdata); + + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("name", srv->name)); +} + +static void on_execute_dir_disconnect(sd_varlink_server *s, sd_varlink *link, void *userdata) { + ExecDirServer *srv = ASSERT_PTR(userdata); + + /* Only one client (from varlink_execute_directory()) connects per server — once it's gone, we're done. */ + ASSERT_OK(sd_event_exit(srv->event, 0)); +} + +static void *execute_dir_server_thread(void *arg) { + ExecDirServer *srv = arg; + + ASSERT_OK(sd_event_loop(srv->event)); + return NULL; +} + +static int execute_dir_reply(sd_varlink *link, sd_json_variant *parameters, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) { + size_t *count = ASSERT_PTR(userdata); + + ASSERT_NULL(error_id); + ASSERT_NOT_NULL(sd_json_variant_by_key(parameters, "name")); + + (*count)++; + return 0; +} + +TEST(execute_directory) { + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + static const char * const names[] = { "alpha", "beta", "gamma" }; + ExecDirServer servers[ELEMENTSOF(names)] = {}; + size_t reply_count = 0; + + ASSERT_OK(mkdtemp_malloc("/tmp/varlink-execdir-XXXXXX", &tmpdir)); + + for (size_t i = 0; i < ELEMENTSOF(names); i++) { + ExecDirServer *eds = servers + i; + servers[i].name = names[i]; + + _cleanup_free_ char *j = ASSERT_PTR(path_join(tmpdir, names[i])); + + ASSERT_OK(sd_event_new(&eds->event)); + ASSERT_OK(varlink_server_new(&eds->server, + SD_VARLINK_SERVER_INHERIT_USERDATA, + eds)); + ASSERT_OK(sd_varlink_server_bind_method(eds->server, "io.test.ExecDirPing", method_execute_dir_ping)); + ASSERT_OK(sd_varlink_server_bind_disconnect(eds->server, on_execute_dir_disconnect)); + ASSERT_OK(sd_varlink_server_listen_address(eds->server, j, 0600)); + ASSERT_OK(sd_varlink_server_attach_event(eds->server, eds->event, 0)); + + ASSERT_OK(-pthread_create(&eds->thread, NULL, execute_dir_server_thread, eds)); + } + + ASSERT_OK_EQ(varlink_execute_directory( + tmpdir, + "io.test.ExecDirPing", + /* parameters= */ NULL, + /* more= */ false, + /* timeout_usec= */ USEC_INFINITY, + execute_dir_reply, + &reply_count), (ssize_t) ELEMENTSOF(names)); + ASSERT_EQ(reply_count, ELEMENTSOF(names)); + + FOREACH_ELEMENT(eds, servers) { + ASSERT_OK(-pthread_join(eds->thread, NULL)); + eds->server = sd_varlink_server_unref(eds->server); + eds->event = sd_event_unref(eds->event); + } + + /* Calling the helper against a non-existent directory must fail. */ + _cleanup_free_ char *nope = NULL; + ASSERT_OK(asprintf(&nope, "%s/does-not-exist", tmpdir)); + ASSERT_FAIL(varlink_execute_directory( + nope, + "io.test.ExecDirPing", + /* parameters= */ NULL, + /* more= */ false, + /* timeout_usec= */ USEC_INFINITY, + execute_dir_reply, + &reply_count)); + + /* An empty directory must simply return 0 and not invoke the reply callback. */ + _cleanup_free_ char *empty = ASSERT_PTR(path_join(tmpdir, "empty")); + ASSERT_OK_ERRNO(mkdir(empty, 0755)); + + size_t count_before = reply_count; + ASSERT_OK_ZERO(varlink_execute_directory( + empty, + "io.test.ExecDirPing", + /* parameters= */ NULL, + /* more= */ false, + /* timeout_usec= */ USEC_INFINITY, + execute_dir_reply, + &reply_count)); + ASSERT_EQ(reply_count, count_before); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-verbs.c b/src/test/test-verbs.c index a28fc9b55b274..41ae5a87f3766 100644 --- a/src/test/test-verbs.c +++ b/src/test/test-verbs.c @@ -6,7 +6,7 @@ #include "tests.h" #include "verbs.h" -static int noop_dispatcher(int argc, char *argv[], void *userdata) { +static int noop_dispatcher(int argc, char *argv[], uintptr_t _data, void *userdata) { return 0; } @@ -16,14 +16,16 @@ static int noop_dispatcher(int argc, char *argv[], void *userdata) { TEST(verbs) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, - { "list", VERB_ANY, 2, VERB_DEFAULT, noop_dispatcher }, - { "status", 2, VERB_ANY, 0, noop_dispatcher }, - { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, - { "login", 2, 2, 0, noop_dispatcher }, - { "copy-to", 3, 4, 0, noop_dispatcher }, + { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, + { "list", VERB_ANY, 2, VERB_DEFAULT, noop_dispatcher }, + { "status", 2, VERB_ANY, 0, noop_dispatcher }, + { "Group2", VERB_ANY, VERB_ANY, VERB_GROUP_MARKER, NULL }, + { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, + { "Group3", 0, 0, VERB_GROUP_MARKER, NULL }, + { "login", 2, 2, 0, noop_dispatcher }, + { "copy-to", 3, 4, 0, noop_dispatcher }, {} }; @@ -44,6 +46,12 @@ TEST(verbs) { /* no verb, but a default is set */ test_dispatch_one(STRV_EMPTY, verbs, 0); + + /* the group entry shall not be found */ + test_dispatch_one(STRV_MAKE("Group2"), verbs, -EINVAL); + + /* the group entry shall not be found */ + test_dispatch_one(STRV_MAKE("Group3"), verbs, -EINVAL); } TEST(verbs_no_default) { @@ -60,14 +68,15 @@ TEST(verbs_no_default) { TEST(verbs_no_default_many) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, - { "list", VERB_ANY, 2, 0, noop_dispatcher }, - { "status", 2, VERB_ANY, 0, noop_dispatcher }, - { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, - { "login", 2, 2, 0, noop_dispatcher }, - { "copy-to", 3, 4, 0, noop_dispatcher }, + { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, + { "list", VERB_ANY, 2, 0, noop_dispatcher }, + { "status", 2, VERB_ANY, 0, noop_dispatcher }, + { "Specials", VERB_ANY, VERB_ANY, VERB_GROUP_MARKER, NULL }, + { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, + { "login", 2, 2, 0, noop_dispatcher }, + { "copy-to", 3, 4, 0, noop_dispatcher }, {} }; @@ -75,6 +84,7 @@ TEST(verbs_no_default_many) { test_dispatch_one(STRV_MAKE("hel"), verbs, -EINVAL); test_dispatch_one(STRV_MAKE("helpp"), verbs, -EINVAL); test_dispatch_one(STRV_MAKE("hgrejgoraoiosafso"), verbs, -EINVAL); + test_dispatch_one(STRV_MAKE("Specials"), verbs, -EINVAL); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index 04730de114743..7d6ca7450a6be 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -20,6 +19,7 @@ #include "in-addr-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -163,9 +163,9 @@ static int print_status_info(const StatusInfo *i) { if (r < 0) return table_log_add_error(r); - r = table_print(table, NULL); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; if (i->rtc_local) { fflush(stdout); @@ -180,7 +180,8 @@ static int print_status_info(const StatusInfo *i) { return 0; } -static int show_status(int argc, char **argv, void *userdata) { +VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, "Show current time settings"); +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { StatusInfo info = {}; static const struct bus_properties_map map[] = { { "Timezone", "s", NULL, offsetof(StatusInfo, timezone) }, @@ -212,7 +213,8 @@ static int show_status(int argc, char **argv, void *userdata) { return print_status_info(&info); } -static int show_properties(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_show, "show", "Show properties of systemd-timedated"); +static int verb_show(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -229,7 +231,8 @@ static int show_properties(int argc, char **argv, void *userdata) { return 0; } -static int set_time(int argc, char **argv, void *userdata) { +VERB(verb_set_time, "set-time", "TIME", 2, 2, 0, "Set system time"); +static int verb_set_time(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; usec_t t; @@ -254,7 +257,8 @@ static int set_time(int argc, char **argv, void *userdata) { return 0; } -static int set_timezone(int argc, char **argv, void *userdata) { +VERB(verb_set_timezone, "set-timezone", "ZONE", 2, 2, 0, "Set system time zone"); +static int verb_set_timezone(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; int r; @@ -268,7 +272,31 @@ static int set_timezone(int argc, char **argv, void *userdata) { return 0; } -static int set_local_rtc(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_list_timezones, "list-timezones", "Show known time zones"); +static int verb_list_timezones(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + _cleanup_strv_free_ char **zones = NULL; + + r = bus_call_method(bus, bus_timedate, "ListTimezones", &error, &reply, NULL); + if (r < 0) + return log_error_errno(r, "Failed to request list of time zones: %s", + bus_error_message(&error, r)); + + r = sd_bus_message_read_strv(reply, &zones); + if (r < 0) + return bus_log_parse_error(r); + + pager_open(arg_pager_flags); + strv_print(zones); + + return 0; +} + +VERB(verb_set_local_rtc, "set-local-rtc", "BOOL", 2, 2, 0, "Control whether RTC is in local time"); +static int verb_set_local_rtc(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; int r, b; @@ -299,7 +327,8 @@ static int set_local_rtc(int argc, char **argv, void *userdata) { return 0; } -static int set_ntp(int argc, char **argv, void *userdata) { +VERB(verb_set_ntp, "set-ntp", "BOOL", 2, 2, 0, "Enable or disable network time synchronization"); +static int verb_set_ntp(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; @@ -327,28 +356,6 @@ static int set_ntp(int argc, char **argv, void *userdata) { return 0; } -static int list_timezones(int argc, char **argv, void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - _cleanup_strv_free_ char **zones = NULL; - - r = bus_call_method(bus, bus_timedate, "ListTimezones", &error, &reply, NULL); - if (r < 0) - return log_error_errno(r, "Failed to request list of time zones: %s", - bus_error_message(&error, r)); - - r = sd_bus_message_read_strv(reply, &zones); - if (r < 0) - return bus_log_parse_error(r); - - pager_open(arg_pager_flags); - strv_print(zones); - - return 0; -} - typedef struct NTPStatusInfo { const char *server_name; char *server_address; @@ -443,20 +450,12 @@ static int print_ntp_status_info(NTPStatusInfo *i) { if (r < 0) return table_log_add_error(r); - r = table_print(table, NULL); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } if (i->dest < i->origin || i->trans < i->recv || i->dest - i->origin < i->trans - i->recv) { log_error("Invalid NTP response"); - r = table_print(table, NULL); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } delay = (i->dest - i->origin) - (i->trans - i->recv); @@ -536,11 +535,7 @@ static int print_ntp_status_info(NTPStatusInfo *i) { return table_log_add_error(r); } - r = table_print(table, NULL); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } static int map_server_address(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { @@ -688,7 +683,10 @@ static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error return show_timesync_status_once(sd_bus_message_get_bus(m)); } -static int show_timesync_status(int argc, char **argv, void *userdata) { +VERB_GROUP("systemd-timesyncd Commands"); + +VERB_NOARG(verb_timesync_status, "timesync-status", "Show status of systemd-timesyncd"); +static int verb_timesync_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_event_unrefp) sd_event *event = NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -792,7 +790,8 @@ static int print_timesync_property(const char *name, const char *expected_value, return 0; } -static int show_timesync(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_show_timesync, "show-timesync", "Show properties of systemd-timesyncd"); +static int verb_show_timesync(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -841,7 +840,9 @@ static int parse_ifindex_bus(sd_bus *bus, const char *str) { return i; } -static int verb_ntp_servers(int argc, char **argv, void *userdata) { +VERB(verb_ntp_servers, "ntp-servers", "INTERFACE SERVER…", 3, VERB_ANY, 0, + "Set the interface specific NTP servers"); +static int verb_ntp_servers(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; sd_bus *bus = ASSERT_PTR(userdata); @@ -872,7 +873,8 @@ static int verb_ntp_servers(int argc, char **argv, void *userdata) { return 0; } -static int verb_revert(int argc, char **argv, void *userdata) { +VERB(verb_revert, "revert", "INTERFACE", 2, 2, 0, "Revert the interface specific NTP servers"); +static int verb_revert(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int ifindex, r; @@ -892,179 +894,133 @@ static int verb_revert(int argc, char **argv, void *userdata) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *verbs2 = NULL, *options = NULL; int r; r = terminal_urlify_man("timedatectl", "1", &link); if (r < 0) return log_oom(); + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = verbs_get_help_table_group("systemd-timesyncd Commands", &verbs2); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, verbs2, options); + printf("%s [OPTIONS...] COMMAND ...\n" "\n%sQuery or change system time and date settings.%s\n" - "\nCommands:\n" - " status Show current time settings\n" - " show Show properties of systemd-timedated\n" - " set-time TIME Set system time\n" - " set-timezone ZONE Set system time zone\n" - " list-timezones Show known time zones\n" - " set-local-rtc BOOL Control whether RTC is in local time\n" - " set-ntp BOOL Enable or disable network time synchronization\n" - "\nsystemd-timesyncd Commands:\n" - " timesync-status Show status of systemd-timesyncd\n" - " show-timesync Show properties of systemd-timesyncd\n" - " ntp-servers INTERFACE SERVER…\n" - " Set the interface specific NTP servers\n" - " revert INTERFACE Revert the interface specific NTP servers\n" - "\nOptions:\n" - " -h --help Show this help message\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-ask-password Do not prompt for password\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --adjust-system-clock Adjust system clock when changing local RTC mode\n" - " --monitor Monitor status of systemd-timesyncd\n" - " -p --property=NAME Show only properties by this name\n" - " -a --all Show all properties, including empty ones\n" - " --value When showing properties, only print the value\n" - " -P NAME Equivalent to --value --property=NAME\n" - "\nSee the %s for details.\n", + "\nCommands:\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; - return 0; -} + printf("\nsystemd-timesyncd Commands:\n"); + r = table_print_or_warn(verbs2); + if (r < 0) + return r; -static int verb_help(int argc, char **argv, void *userdata) { - return help(); -} + printf("\nOptions:\n"); + r = table_print_or_warn(options); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_ADJUST_SYSTEM_CLOCK, - ARG_NO_ASK_PASSWORD, - ARG_MONITOR, - ARG_VALUE, - }; + printf("\nSee the %s for details.\n", link); + return 0; +} - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK }, - { "monitor", no_argument, NULL, ARG_MONITOR }, - { "property", required_argument, NULL, 'p' }, - { "value", no_argument, NULL, ARG_VALUE }, - { "all", no_argument, NULL, 'a' }, - {} - }; +VERB_COMMON_HELP_HIDDEN(help); - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hH:M:p:P:a", options, NULL)) >= 0) - switch (c) { + OptionParser state = { argc, argv }; + const Option *current; + const char *arg; - case 'h': + FOREACH_OPTION_FULL(&state, c, ¤t, &arg, /* on_error= */ return c) + switch (c) { + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); - if (r < 0) - return r; + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION_COMMON_HOST: + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = arg; break; - case ARG_ADJUST_SYSTEM_CLOCK: - arg_adjust_system_clock = true; + OPTION_COMMON_MACHINE: + r = parse_machine_argument(arg, &arg_host, &arg_transport); + if (r < 0) + return r; break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION_LONG("adjust-system-clock", NULL, "Adjust system clock when changing local RTC mode"): + arg_adjust_system_clock = true; break; - case ARG_MONITOR: + OPTION_LONG("monitor", NULL, "Monitor status of systemd-timesyncd"): arg_monitor = true; break; - case 'p': - case 'P': - r = strv_extend(&arg_property, optarg); + OPTION('p', "property", "NAME", "Show only properties by this name"): {} + OPTION_SHORT('P', "NAME", "Equivalent to --value --property=NAME"): + r = strv_extend(&arg_property, arg); if (r < 0) return log_oom(); /* If the user asked for a particular property, show it to them, even if empty. */ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); - if (c == 'p') - break; - _fallthrough_; + if (current->short_code == 'P') + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); + break; - case ARG_VALUE: + OPTION_LONG("value", NULL, "When showing properties, only print the value"): SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); break; - case 'a': + OPTION('a', "all", NULL, "Show all properties, including empty ones"): SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } -static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) { - static const Verb verbs[] = { - { "status", VERB_ANY, 1, VERB_DEFAULT, show_status }, - { "show", VERB_ANY, 1, 0, show_properties }, - { "set-time", 2, 2, 0, set_time }, - { "set-timezone", 2, 2, 0, set_timezone }, - { "list-timezones", VERB_ANY, 1, 0, list_timezones }, - { "set-local-rtc", 2, 2, 0, set_local_rtc }, - { "set-ntp", 2, 2, 0, set_ntp }, - { "timesync-status", VERB_ANY, 1, 0, show_timesync_status }, - { "show-timesync", VERB_ANY, 1, 0, show_timesync }, - { "ntp-servers", 3, VERB_ANY, 0, verb_ntp_servers }, - { "revert", 2, 2, 0, verb_revert }, - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it is created. */ - {} - }; - - return dispatch_verb(argc, argv, verbs, bus); -} - static int run(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + char **args = NULL; int r; setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -1074,7 +1030,7 @@ static int run(int argc, char *argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - return timedatectl_main(bus, argc, argv); + return dispatch_verb_with_args(args, bus); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c index 5ba8644334da5..43cf3fddb9da6 100644 --- a/src/timedate/timedated.c +++ b/src/timedate/timedated.c @@ -1109,7 +1109,7 @@ static const sd_bus_vtable timedate_vtable[] = { SD_BUS_VTABLE_END, }; -const BusObjectImplementation manager_object = { +static const BusObjectImplementation manager_object = { "/org/freedesktop/timedate1", "org.freedesktop.timedate1", .vtables = BUS_VTABLES(timedate_vtable), diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c index 79a9a629c1410..e4f471e2aa5dc 100644 --- a/src/timesync/timesyncd-manager.c +++ b/src/timesync/timesyncd-manager.c @@ -437,7 +437,7 @@ static int manager_receive_response(sd_event_source *source, int fd, uint32_t re } /* Too short packet? */ - if (iov.iov_len < sizeof(struct ntp_msg)) { + if ((size_t) len < sizeof(struct ntp_msg)) { log_warning("Invalid response from server. Disconnecting."); return manager_connect(m); } diff --git a/src/timesync/timesyncd.c b/src/timesync/timesyncd.c index 96d0dd5c2ba2b..5e0d13023aa90 100644 --- a/src/timesync/timesyncd.c +++ b/src/timesync/timesyncd.c @@ -8,7 +8,6 @@ #include "bus-log-control-api.h" #include "bus-object.h" -#include "capability-util.h" #include "clock-util.h" #include "daemon-util.h" #include "errno-util.h" @@ -17,14 +16,12 @@ #include "fs-util.h" #include "log.h" #include "main-func.h" -#include "mkdir-label.h" #include "network-util.h" #include "process-util.h" #include "service-util.h" #include "timesyncd-bus.h" #include "timesyncd-conf.h" #include "timesyncd-manager.h" -#include "user-util.h" static int advance_tstamp(int fd, usec_t epoch) { assert(fd >= 0); @@ -72,7 +69,7 @@ static int advance_tstamp(int fd, usec_t epoch) { return 0; } -static int load_clock_timestamp(uid_t uid, gid_t gid) { +static int load_clock_timestamp(void) { usec_t epoch = TIME_EPOCH * USEC_PER_SEC, ct; _cleanup_close_ int fd = -EBADF; int r; @@ -82,18 +79,13 @@ static int load_clock_timestamp(uid_t uid, gid_t gid) { * is particularly helpful on systems lacking a battery backed RTC. We also will adjust the time to * at least the build time of systemd. */ - fd = open(TIMESYNCD_CLOCK_FILE, O_RDWR|O_CLOEXEC, 0644); + fd = RET_NERRNO(open(TIMESYNCD_CLOCK_FILE, O_RDWR|O_CLOEXEC, 0644)); if (fd < 0) { - if (errno != ENOENT) - log_debug_errno(errno, "Unable to open timestamp file "TIMESYNCD_CLOCK_FILE", ignoring: %m"); - - r = mkdir_safe_label(TIMESYNCD_CLOCK_FILE_DIR, 0755, uid, gid, - MKDIR_FOLLOW_SYMLINK | MKDIR_WARN_MODE); - if (r < 0) - log_debug_errno(r, "Failed to create "TIMESYNCD_CLOCK_FILE_DIR", ignoring: %m"); + if (fd != -ENOENT) + log_warning_errno(fd, "Unable to open timestamp file "TIMESYNCD_CLOCK_FILE", ignoring: %m"); /* Create stamp file with the compiled-in date */ - r = touch_file(TIMESYNCD_CLOCK_FILE, /* parents= */ false, epoch, uid, gid, 0644); + r = touch_file(TIMESYNCD_CLOCK_FILE, /* parents= */ false, epoch, UID_INVALID, GID_INVALID, MODE_INVALID); if (r < 0) log_debug_errno(r, "Failed to create %s, ignoring: %m", TIMESYNCD_CLOCK_FILE); } else { @@ -103,13 +95,6 @@ static int load_clock_timestamp(uid_t uid, gid_t gid) { if (fstat(fd, &st) < 0) return log_error_errno(errno, "Unable to stat timestamp file "TIMESYNCD_CLOCK_FILE": %m"); - /* Try to fix the access mode, so that we can still touch the file after dropping - * privileges */ - r = fchmod_and_chown(fd, 0644, uid, gid); - if (r < 0) - log_full_errno(ERRNO_IS_PRIVILEGE(r) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to chmod or chown %s, ignoring: %m", TIMESYNCD_CLOCK_FILE); - epoch = MAX(epoch, timespec_load(&st.st_mtim)); (void) advance_tstamp(fd, epoch); @@ -140,9 +125,6 @@ static int load_clock_timestamp(uid_t uid, gid_t gid) { static int run(int argc, char *argv[]) { _cleanup_(manager_freep) Manager *m = NULL; _unused_ _cleanup_(notify_on_cleanup) const char *notify_message = NULL; - const char *user = "systemd-timesync"; - uid_t uid, uid_current; - gid_t gid; int r; log_set_facility(LOG_CRON); @@ -161,27 +143,10 @@ static int run(int argc, char *argv[]) { if (argc != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program does not take arguments."); - uid = uid_current = geteuid(); - gid = getegid(); - - if (uid_current == 0) { - r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0); - if (r < 0) - return log_error_errno(r, "Cannot resolve user name %s: %m", user); - } - - r = load_clock_timestamp(uid, gid); + r = load_clock_timestamp(); if (r < 0) return r; - /* Drop privileges, but only if we have been started as root. If we are not running as root we assume all - * privileges are already dropped. */ - if (uid_current == 0) { - r = drop_privileges(uid, gid, (1ULL << CAP_SYS_TIME)); - if (r < 0) - return log_error_errno(r, "Failed to drop privileges: %m"); - } - r = manager_new(&m); if (r < 0) return log_error_errno(r, "Failed to allocate manager: %m"); diff --git a/src/tmpfiles/offline-passwd.c b/src/tmpfiles/offline-passwd.c index 2334e258cb404..75e9085c26bf0 100644 --- a/src/tmpfiles/offline-passwd.c +++ b/src/tmpfiles/offline-passwd.c @@ -44,6 +44,8 @@ static int populate_uid_cache(const char *root, Hashmap **ret) { _cleanup_hashmap_free_ Hashmap *cache = NULL; int r; + assert(ret); + cache = hashmap_new(&uid_gid_hash_ops); if (!cache) return -ENOMEM; @@ -85,6 +87,8 @@ static int populate_gid_cache(const char *root, Hashmap **ret) { _cleanup_hashmap_free_ Hashmap *cache = NULL; int r; + assert(ret); + cache = hashmap_new(&uid_gid_hash_ops); if (!cache) return -ENOMEM; diff --git a/src/tmpfiles/test-offline-passwd.c b/src/tmpfiles/test-offline-passwd.c index 7be29ff798556..9695ba9b63c2c 100644 --- a/src/tmpfiles/test-offline-passwd.c +++ b/src/tmpfiles/test-offline-passwd.c @@ -1,13 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "format-util.h" #include "hashmap.h" #include "offline-passwd.h" +#include "options.h" +#include "strv.h" #include "tests.h" -static char *arg_root = NULL; +static const char *arg_root = NULL; static void test_resolve_one(const char *name) { bool relaxed = name || arg_root; @@ -39,30 +39,22 @@ static void test_resolve_one(const char *name) { assert_se(relaxed || r == 0); } -static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "root", required_argument, NULL, 'r' }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert_se(argc >= 0); assert_se(argv); - while ((c = getopt_long(argc, argv, "r:", options, NULL)) >= 0) - switch (c) { - case 'r': - arg_root = optarg; - break; + OptionParser state = { argc, argv }; + const char *arg; - case '?': - return -EINVAL; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + switch (c) { - default: - assert_not_reached(); + OPTION('r', "root", "PATH", "Operate on an alternate filesystem root"): + arg_root = arg; + break; } + *ret_args = option_parser_get_args(&state); return 0; } @@ -71,15 +63,16 @@ int main(int argc, char **argv) { test_setup_logging(LOG_DEBUG); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r < 0) return r; - if (optind >= argc) + if (strv_isempty(args)) test_resolve_one(NULL); else - while (optind < argc) - test_resolve_one(argv[optind++]); + STRV_FOREACH(a, args) + test_resolve_one(*a); return 0; } diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index da75ffb818127..0c133b08c84fe 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -31,6 +30,7 @@ #include "extract-word.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "format-util.h" #include "fs-util.h" #include "glob-util.h" @@ -45,6 +45,7 @@ #include "mount-util.h" #include "mountpoint-util.h" #include "offline-passwd.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -199,6 +200,7 @@ typedef enum { static CatFlags arg_cat_flags = CAT_CONFIG_OFF; static bool arg_dry_run = false; +static bool arg_inline = false; static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; static OperationMask arg_operation = 0; static bool arg_boot = false; @@ -208,7 +210,7 @@ static char **arg_include_prefixes = NULL; static char **arg_exclude_prefixes = NULL; static char *arg_root = NULL; static char *arg_image = NULL; -static char *arg_replace = NULL; +static const char *arg_replace = NULL; static ImagePolicy *arg_image_policy = NULL; #define MAX_DEPTH 256 @@ -287,6 +289,7 @@ static int specifier_directory( unsigned i; int r; + assert(ret); assert_cc(ELEMENTSOF(paths_system) == ELEMENTSOF(paths_user)); paths = arg_runtime_scope == RUNTIME_SCOPE_USER ? paths_user : paths_system; @@ -586,9 +589,12 @@ static int opendir_and_stat( return 0; } - r = xstatx_full(dirfd(d), /* path = */ NULL, AT_EMPTY_PATH, - STATX_MODE|STATX_INO|STATX_ATIME|STATX_MTIME, - /* optional_mask = */ 0, + r = xstatx_full(dirfd(d), + /* path= */ NULL, + AT_EMPTY_PATH, + /* xstatx_flags= */ 0, + STATX_MODE|STATX_INO, + STATX_ATIME|STATX_MTIME, STATX_ATTR_MOUNT_ROOT, &sx); if (r < 0) @@ -687,6 +693,7 @@ static int dir_cleanup( struct statx sx; r = xstatx_full(dirfd(d), de->d_name, AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT, + /* xstatx_flags= */ 0, STATX_TYPE|STATX_MODE|STATX_UID, STATX_ATIME|STATX_MTIME|STATX_CTIME|STATX_BTIME, STATX_ATTR_MOUNT_ROOT, @@ -705,10 +712,10 @@ static int dir_cleanup( continue; } - atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx.stx_atime) : 0; - mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? statx_timestamp_load_nsec(&sx.stx_mtime) : 0; - ctime_nsec = FLAGS_SET(sx.stx_mask, STATX_CTIME) ? statx_timestamp_load_nsec(&sx.stx_ctime) : 0; - btime_nsec = FLAGS_SET(sx.stx_mask, STATX_BTIME) ? statx_timestamp_load_nsec(&sx.stx_btime) : 0; + atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx.stx_atime) : NSEC_INFINITY; + mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? statx_timestamp_load_nsec(&sx.stx_mtime) : NSEC_INFINITY; + ctime_nsec = FLAGS_SET(sx.stx_mask, STATX_CTIME) ? statx_timestamp_load_nsec(&sx.stx_ctime) : NSEC_INFINITY; + btime_nsec = FLAGS_SET(sx.stx_mask, STATX_BTIME) ? statx_timestamp_load_nsec(&sx.stx_btime) : NSEC_INFINITY; sub_path = path_join(p, de->d_name); if (!sub_path) { @@ -862,11 +869,19 @@ static int dir_cleanup( log_action("Would restore", "Restoring", "%s access and modification time on \"%s\": %s, %s", p, - FORMAT_TIMESTAMP_STYLE(self_atime_nsec / NSEC_PER_USEC, TIMESTAMP_US), - FORMAT_TIMESTAMP_STYLE(self_mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US)); - - timespec_store_nsec(ts + 0, self_atime_nsec); - timespec_store_nsec(ts + 1, self_mtime_nsec); + self_atime_nsec != NSEC_INFINITY + ? FORMAT_TIMESTAMP_STYLE(self_atime_nsec / NSEC_PER_USEC, TIMESTAMP_US) + : "(omitted)", + self_mtime_nsec != NSEC_INFINITY + ? FORMAT_TIMESTAMP_STYLE(self_mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US) + : "(omitted)"); + + ts[0] = self_atime_nsec != NSEC_INFINITY + ? *TIMESPEC_STORE_NSEC(self_atime_nsec) + : TIMESPEC_OMIT; + ts[1] = self_mtime_nsec != NSEC_INFINITY + ? *TIMESPEC_STORE_NSEC(self_mtime_nsec) + : TIMESPEC_OMIT; /* Restore original directory timestamps */ if (!arg_dry_run && @@ -1232,7 +1247,7 @@ static int parse_acl_cond_exec( assert(cond_exec); assert(ret); - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) return r; @@ -1351,7 +1366,7 @@ static int path_set_acl( assert(c); - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) return r; @@ -2633,7 +2648,7 @@ static int rm_if_wrong_type_safe( } /* Fail before removing anything if this is an unsafe transition. */ - if (follow_links && unsafe_transition(parent_st, &st)) { + if (follow_links && stat_unsafe_transition(parent_st, &st)) { (void) fd_get_path(parent_fd, &parent_name); return log_error_errno(SYNTHETIC_ERRNO(ENOLINK), "Unsafe transition from \"%s\" to \"%s\".", parent_name ?: "...", name); @@ -3111,6 +3126,7 @@ static int clean_item_instance( return 0; usec_t cutoff = n - i->age; + nsec_t atime_nsec, mtime_nsec; _cleanup_closedir_ DIR *d = NULL; struct statx sx; @@ -3121,6 +3137,9 @@ static int clean_item_instance( if (r <= 0) return r; + atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx.stx_atime) : NSEC_INFINITY; + mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? statx_timestamp_load_nsec(&sx.stx_mtime) : NSEC_INFINITY; + if (DEBUG_LOGGING) { _cleanup_free_ char *ab_f = NULL, *ab_d = NULL; @@ -3140,8 +3159,8 @@ static int clean_item_instance( } return dir_cleanup(c, i, instance, d, - statx_timestamp_load_nsec(&sx.stx_atime), - statx_timestamp_load_nsec(&sx.stx_mtime), + atime_nsec, + mtime_nsec, cutoff * NSEC_PER_USEC, sx.stx_dev_major, sx.stx_dev_minor, mountpoint, @@ -3585,6 +3604,7 @@ static int parse_line( assert(fname); assert(line >= 1); assert(buffer); + assert(invalid_config); const Specifier specifier_table[] = { { 'h', specifier_user_home, NULL }, @@ -4123,211 +4143,173 @@ static int exclude_default_prefixes(void) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *cmds = NULL, *opts = NULL; int r; r = terminal_urlify_man("systemd-tmpfiles", "8", &link); if (r < 0) return log_oom(); - printf("%1$s COMMAND [OPTIONS...] [CONFIGURATION FILE...]\n" - "\n%2$sCreate, delete, and clean up files and directories.%4$s\n" - "\n%3$sCommands:%4$s\n" - " --create Create and adjust files and directories\n" - " --clean Clean up files and directories\n" - " --remove Remove files and directories marked for removal\n" - " --purge Delete files and directories marked for creation in\n" - " specified configuration files (careful!)\n" - " --cat-config Show configuration files\n" - " --tldr Show non-comment parts of configuration files\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sOptions:%4$s\n" - " --user Execute user configuration\n" - " --boot Execute actions only safe at boot\n" - " --graceful Quietly ignore unknown users or groups\n" - " --prefix=PATH Only apply rules with the specified prefix\n" - " --exclude-prefix=PATH Ignore rules with the specified prefix\n" - " -E Ignore rules prefixed with /dev, /proc, /run, /sys\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY Specify disk image dissection policy\n" - " --replace=PATH Treat arguments as replacement for PATH\n" - " --dry-run Just print what would be done\n" - " --no-pager Do not pipe output into a pager\n" - "\nSee the %5$s for details.\n", + r = option_parser_get_help_table(&cmds); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("Options", &opts); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, cmds, opts); + + printf("%s COMMAND [OPTIONS...] [CONFIGURATION FILE...]\n" + "\n%sCreate, delete, and clean up files and directories.%s\n" + "\nCommands:\n", program_invocation_short_name, ansi_highlight(), - ansi_underline(), - ansi_normal(), - link); + ansi_normal()); - return 0; -} + r = table_print_or_warn(cmds); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_CAT_CONFIG, - ARG_TLDR, - ARG_USER, - ARG_CREATE, - ARG_CLEAN, - ARG_REMOVE, - ARG_PURGE, - ARG_BOOT, - ARG_GRACEFUL, - ARG_PREFIX, - ARG_EXCLUDE_PREFIX, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_REPLACE, - ARG_DRY_RUN, - ARG_NO_PAGER, - }; + printf("\nOptions:\n"); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "user", no_argument, NULL, ARG_USER }, - { "version", no_argument, NULL, ARG_VERSION }, - { "cat-config", no_argument, NULL, ARG_CAT_CONFIG }, - { "tldr", no_argument, NULL, ARG_TLDR }, - { "create", no_argument, NULL, ARG_CREATE }, - { "clean", no_argument, NULL, ARG_CLEAN }, - { "remove", no_argument, NULL, ARG_REMOVE }, - { "purge", no_argument, NULL, ARG_PURGE }, - { "boot", no_argument, NULL, ARG_BOOT }, - { "graceful", no_argument, NULL, ARG_GRACEFUL }, - { "prefix", required_argument, NULL, ARG_PREFIX }, - { "exclude-prefix", required_argument, NULL, ARG_EXCLUDE_PREFIX }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "replace", required_argument, NULL, ARG_REPLACE }, - { "dry-run", no_argument, NULL, ARG_DRY_RUN }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - {} - }; + r = table_print_or_warn(opts); + if (r < 0) + return r; - int c, r; + printf("\nSee the %s for details.\n", link); + return 0; +} + +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hE", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(); - - case ARG_VERSION: - return version(); - - case ARG_CAT_CONFIG: - arg_cat_flags = CAT_CONFIG_ON; + OPTION_LONG("create", NULL, "Create and adjust files and directories"): + arg_operation |= OPERATION_CREATE; break; - case ARG_TLDR: - arg_cat_flags = CAT_TLDR; + OPTION_LONG("clean", NULL, "Clean up files and directories"): + arg_operation |= OPERATION_CLEAN; break; - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; + OPTION_LONG("remove", NULL, "Remove files and directories marked for removal"): + arg_operation |= OPERATION_REMOVE; break; - case ARG_CREATE: - arg_operation |= OPERATION_CREATE; + OPTION_LONG("purge", NULL, + "Delete files and directories marked for creation in" + " specified configuration files (careful!)"): + arg_operation |= OPERATION_PURGE; break; - case ARG_CLEAN: - arg_operation |= OPERATION_CLEAN; + OPTION_COMMON_CAT_CONFIG: + arg_cat_flags = CAT_CONFIG_ON; break; - case ARG_REMOVE: - arg_operation |= OPERATION_REMOVE; + OPTION_COMMON_TLDR: + arg_cat_flags = CAT_TLDR; break; - case ARG_BOOT: - arg_boot = true; + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_GROUP("Options"): {} + + OPTION_LONG("user", NULL, "Execute user configuration"): + arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_PURGE: - arg_operation |= OPERATION_PURGE; + OPTION_LONG("boot", NULL, "Execute actions only safe at boot"): + arg_boot = true; break; - case ARG_GRACEFUL: + OPTION_LONG("graceful", NULL, "Quietly ignore unknown users or groups"): arg_graceful = true; break; - case ARG_PREFIX: - if (strv_extend(&arg_include_prefixes, optarg) < 0) + OPTION_LONG("prefix", "PATH", "Only apply rules with the specified prefix"): + if (strv_extend(&arg_include_prefixes, arg) < 0) return log_oom(); break; - case ARG_EXCLUDE_PREFIX: - if (strv_extend(&arg_exclude_prefixes, optarg) < 0) + OPTION_LONG("exclude-prefix", "PATH", "Ignore rules with the specified prefix"): + if (strv_extend(&arg_exclude_prefixes, arg) < 0) return log_oom(); break; - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root); + OPTION_SHORT('E', NULL, "Ignore rules prefixed with /dev, /proc, /run, /sys"): + r = exclude_default_prefixes(); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root); if (r < 0) return r; + break; - /* Imply -E here since it makes little sense to create files persistently in the /run mountpoint of a disk image */ - _fallthrough_; + OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); + if (r < 0) + return r; - case 'E': + /* Imply -E here since it makes little sense to create files persistently in the /run mountpoint of a disk image */ r = exclude_default_prefixes(); if (r < 0) return r; - break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_REPLACE: - if (!path_is_absolute(optarg)) + OPTION_LONG("replace", "PATH", "Treat arguments as replacement for PATH"): + if (!path_is_absolute(arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must be an absolute path."); - if (!endswith(optarg, ".conf")) + if (!endswith(arg, ".conf")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must have the extension '.conf'."); - arg_replace = optarg; + arg_replace = arg; break; - case ARG_DRY_RUN: + OPTION_LONG("dry-run", NULL, "Just print what would be done"): arg_dry_run = true; break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION_LONG("inline", NULL, "Treat arguments as configuration lines"): + arg_inline = true; break; - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; } + char **args = option_parser_get_args(&state); + size_t n_args = option_parser_get_n_args(&state); + if (arg_operation == 0 && arg_cat_flags == CAT_CONFIG_OFF) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "You need to specify at least one of --clean, --create, --remove, or --purge."); - if (FLAGS_SET(arg_operation, OPERATION_PURGE) && optind >= argc) + if (FLAGS_SET(arg_operation, OPERATION_PURGE) && n_args == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Refusing --purge without specification of a configuration file."); @@ -4335,7 +4317,11 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --replace= is not supported with --cat-config/--tldr."); - if (arg_replace && optind >= argc) + if (arg_inline && arg_cat_flags != CAT_CONFIG_OFF) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Option --inline is not supported with --cat-config/--tldr."); + + if (arg_replace && n_args == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When --replace= is given, some configuration items must be specified."); @@ -4347,6 +4333,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); + *ret_args = args; return 1; } @@ -4411,14 +4398,30 @@ static int parse_arguments( char **config_dirs, char **args, bool *invalid_config) { + + unsigned pos = 1; int r; assert(c); + assert(invalid_config); STRV_FOREACH(arg, args) { - r = read_config_file(c, config_dirs, *arg, false, invalid_config); - if (r < 0) - return r; + if (arg_inline) { + bool invalid_arg = false; + + /* Use (argument):n, where n==1 for the first positional arg */ + r = parse_line("(argument)", pos, *arg, &invalid_arg, c); + if (invalid_arg) + *invalid_config = true; + else if (r < 0) + return r; + } else { + r = read_config_file(c, config_dirs, *arg, /* ignore_enoent= */ false, invalid_config); + if (r < 0) + return r; + } + + pos++; } return 0; @@ -4531,7 +4534,8 @@ static int run(int argc, char *argv[]) { } phase; int r; - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -4586,7 +4590,7 @@ static int run(int argc, char *argv[]) { } if (arg_cat_flags != CAT_CONFIG_OFF) - return cat_config(config_dirs, argv + optind); + return cat_config(config_dirs, args); if (should_bypass("SYSTEMD_TMPFILES")) return 0; @@ -4630,10 +4634,10 @@ static int run(int argc, char *argv[]) { * insert the positional arguments at the specified place. Otherwise, if command line arguments are * specified, execute just them, and finally, without --replace= or any positional arguments, just * read configuration and execute it. */ - if (arg_replace || optind >= argc) - r = read_config_files(&c, config_dirs, argv + optind, &invalid_config); + if (arg_replace || strv_isempty(args)) + r = read_config_files(&c, config_dirs, args, &invalid_config); else - r = parse_arguments(&c, config_dirs, argv + optind, &invalid_config); + r = parse_arguments(&c, config_dirs, args, &invalid_config); if (r < 0) return r; diff --git a/src/tpm2-setup/meson.build b/src/tpm2-setup/meson.build index a862e7239cc6b..e87a13d8e66c9 100644 --- a/src/tpm2-setup/meson.build +++ b/src/tpm2-setup/meson.build @@ -10,7 +10,7 @@ executables += [ 'HAVE_TPM2', ], 'dependencies' : [ - libopenssl, + libopenssl_cflags, ], }, libexec_template + { @@ -22,6 +22,15 @@ executables += [ 'HAVE_TPM2', ], }, + libexec_template + { + 'name' : 'systemd-tpm2-swtpm', + 'sources' : files('tpm2-swtpm.c'), + 'conditions' : [ + 'ENABLE_BOOTLOADER', + 'HAVE_OPENSSL', + 'HAVE_TPM2', + ], + }, generator_template + { 'name' : 'systemd-tpm2-generator', 'sources' : files('tpm2-generator.c'), diff --git a/src/tpm2-setup/tpm2-clear.c b/src/tpm2-setup/tpm2-clear.c index 0800a90747961..e6a063f2a8d43 100644 --- a/src/tpm2-setup/tpm2-clear.c +++ b/src/tpm2-setup/tpm2-clear.c @@ -1,15 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-messages.h" #include "alloc-util.h" #include "build.h" #include "env-util.h" #include "fileio.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pretty-print.h" #include "proc-cmdline.h" #include "tpm2-util.h" @@ -18,68 +18,55 @@ static bool arg_graceful = false; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-tpm2-clear", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...]\n" - "\n%5$sRequest clearing of the TPM2 from PC firmware.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --graceful Exit gracefully if no TPM2 device is found\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\n%sRequest clearing of the TPM2 from PC firmware.%s\n" + "\n%sOptions:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_GRACEFUL, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "graceful", no_argument, NULL, ARG_GRACEFUL }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + + FOREACH_OPTION(&state, c, /* ret_a= */ NULL, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_GRACEFUL: + OPTION_LONG("graceful", NULL, "Exit gracefully if no TPM2 device is found"): arg_graceful = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind != argc) + if (option_parser_get_n_args(&state) != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program expects no arguments."); return 1; diff --git a/src/tpm2-setup/tpm2-generator.c b/src/tpm2-setup/tpm2-generator.c index 043e0fd4b7281..65f450daaffb9 100644 --- a/src/tpm2-setup/tpm2-generator.c +++ b/src/tpm2-setup/tpm2-generator.c @@ -1,8 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + +#include "dropin.h" +#include "efivars.h" #include "generator.h" +#include "initrd-util.h" #include "log.h" #include "parse-util.h" +#include "path-util.h" #include "proc-cmdline.h" #include "special.h" #include "tpm2-util.h" @@ -15,6 +21,7 @@ static const char *arg_dest = NULL; static int arg_tpm2_wait = -1; /* tri-state: negative → don't know */ +static bool arg_tpm2_software_fallback = false; static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r; @@ -27,12 +34,19 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat log_warning_errno(r, "Failed to parse 'systemd.tpm2_wait=' kernel command line argument, ignoring: %s", value); else arg_tpm2_wait = r; + + } else if (proc_cmdline_key_streq(key, "systemd.tpm2_software_fallback")) { + r = value ? parse_boolean(value) : 1; + if (r < 0) + log_warning_errno(r, "Failed to parse 'systemd.tpm2_software_fallback=' kernel command line argument, ignoring: %s", value); + else + arg_tpm2_software_fallback = r; } return 0; } -static int generate_tpm_target_symlink(void) { +static int generate_tpm_target_symlink(Tpm2Support support, bool software_fallback_enabled) { int r; if (arg_tpm2_wait == 0) { @@ -40,9 +54,7 @@ static int generate_tpm_target_symlink(void) { return 0; } - if (arg_tpm2_wait < 0) { - Tpm2Support support = tpm2_support(); - + if (arg_tpm2_wait < 0 && !software_fallback_enabled) { if (FLAGS_SET(support, TPM2_SUPPORT_DRIVER)) { log_debug("Not generating tpm2.target synchronization point, as TPM2 device is already present."); return 0; @@ -52,11 +64,6 @@ static int generate_tpm_target_symlink(void) { log_debug("Not generating tpm2.target synchronization point, as firmware reports no TPM2 present."); return 0; } - - if (!FLAGS_SET(support, TPM2_SUPPORT_SYSTEM|TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_LIBRARIES)) { - log_debug("Not generating tpm2.target synchronization point, as userspace support for TPM2 is not complete."); - return 0; - } } r = generator_add_symlink(arg_dest, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_TPM2_TARGET); @@ -66,6 +73,93 @@ static int generate_tpm_target_symlink(void) { return 0; } +static int generate_swtpm_symlink(Tpm2Support support) { + int r; + + if (!arg_tpm2_software_fallback) + return 0; + + if (FLAGS_SET(support, TPM2_SUPPORT_DRIVER) || FLAGS_SET(support, TPM2_SUPPORT_FIRMWARE)) { + log_debug("Not generating software TPM units, as a TPM2 device is otherwise available."); + return 0; + } + + if (!is_efi_boot()) { /* We need the ESP to store the TPM state. */ + log_warning("TPM software fallback requested but not booted in EFI mode, not pulling in software TPM unit."); + return 0; + } + + r = find_executable("swtpm", /* ret_filename= */ NULL); + if (r == -ENOENT) { + log_warning("TPM software fallback requested but swtpm not available, not pulling in software TPM unit."); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to determine if 'swtpm' is available: %m"); + + r = generator_add_symlink(arg_dest, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-tpm2-swtpm.service"); + if (r < 0) + return log_error_errno(r, "Failed to hook in systemd-tpm2-swtpm.service: %m"); + + if (in_initrd()) + /* Order + pull in the early ESP mount so that swtpm has a place to store its data. */ + r = write_drop_in( + arg_dest, + "systemd-tpm2-swtpm.service", + 50, "esp", + "# Automatically generated by systemd-tpm2-generator\n\n" + "[Unit]\n" + "Wants=sysefi.mount\n" + "After=sysefi.mount\n"); + else + /* Order (but not pull in) the regular ESP automount so that swtpm has a place to store its + * data. Note that it might be mounted to two different places depending on the existence of + * XBOOTLDR, hence order after both. */ + r = write_drop_in( + arg_dest, + "systemd-tpm2-swtpm.service", + 50, "esp", + "# Automatically generated by systemd-tpm2-generator\n\n" + "[Unit]\n" + "After=boot.automount efi.automount\n"); + if (r < 0) + return log_error_errno(r, "Failed to hook ESP mount before systemd-tpm2-swtpm.service: %m"); + + return 1; /* Tell caller we now created swtpm units */ +} + +static int generate_now(void) { + int r; + + /* Let's shortcut things before we check for TPM2 support if no one cares anyway */ + if (arg_tpm2_wait == 0 && !arg_tpm2_software_fallback) { + log_debug("Not generating tpm2.target synchronization point or activating software TPM, as turned off via kernel command line."); + return 0; + } + + /* We are supposed to sync on TPM or do a software fallback, let's first and unconditionally validate this makes sense at + * all, i.e. if we have a suitable kernel+userspace. */ + Tpm2Support support = tpm2_support(); + if (!FLAGS_SET(support, TPM2_SUPPORT_SYSTEM|TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_LIBRARIES)) { + + /* Raise log level if things were explicitly configured */ + log_full((arg_tpm2_wait > 0 || + arg_tpm2_software_fallback) ? LOG_NOTICE : LOG_DEBUG, + "Not generating tpm2.target synchronization point or activating software TPM, as userspace support for TPM2 is not complete."); + return 0; + } + + r = generate_swtpm_symlink(support); + if (r < 0) + return r; + + r = generate_tpm_target_symlink(support, /* software_fallback_enabled= */ r > 0); + if (r < 0) + return r; + + return 0; +} + static int run(const char *dest, const char *dest_early, const char *dest_late) { int r; @@ -75,7 +169,7 @@ static int run(const char *dest, const char *dest_early, const char *dest_late) if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - return generate_tpm_target_symlink(); + return generate_now(); } DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c index a811ea436dbee..b779650b73384 100644 --- a/src/tpm2-setup/tpm2-setup.c +++ b/src/tpm2-setup/tpm2-setup.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include +#include #include "sd-messages.h" @@ -9,14 +9,17 @@ #include "build.h" #include "conf-files.h" #include "constants.h" +#include "crypto-util.h" #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "hexdecoct.h" #include "log.h" #include "main-func.h" #include "mkdir.h" +#include "options.h" #include "parse-util.h" #include "pretty-print.h" #include "set.h" @@ -37,94 +40,76 @@ STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); #define TPM2_SRK_TPM2B_PUBLIC_PERSISTENT_PATH "/var/lib/systemd/tpm2-srk-public-key.tpm2b_public" #define TPM2_SRK_TPM2B_PUBLIC_RUNTIME_PATH "/run/systemd/tpm2-srk-public-key.tpm2b_public" -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-tpm2-setup", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...]\n" - "\n%5$sSet up the TPM2 Storage Root Key (SRK), and initialize NvPCRs.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --tpm2-device=PATH\n" - " Pick TPM2 device\n" - " --early=BOOL Store SRK public key in /run/ rather than /var/lib/\n" - " --graceful Exit gracefully if no TPM2 device is found\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\n%sSet up the TPM2 Storage Root Key (SRK), and initialize NvPCRs.%s\n" + "\n%sOptions:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_TPM2_DEVICE, - ARG_EARLY, - ARG_GRACEFUL, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "early", required_argument, NULL, ARG_EARLY }, - { "graceful", no_argument, NULL, ARG_GRACEFUL }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(0, NULL, NULL); + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_TPM2_DEVICE: - if (streq(optarg, "list")) + OPTION_LONG("tpm2-device", "PATH", "Pick TPM2 device"): + if (streq(arg, "list")) return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - if (free_and_strdup(&arg_tpm2_device, streq(optarg, "auto") ? NULL : optarg) < 0) + if (free_and_strdup(&arg_tpm2_device, streq(arg, "auto") ? NULL : arg) < 0) return log_oom(); - break; - case ARG_EARLY: - r = parse_boolean(optarg); + OPTION_LONG("early", "BOOL", "Store SRK public key in /run/ rather than /var/lib/"): + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --early= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --early= argument: %s", arg); arg_early = r; break; - case ARG_GRACEFUL: + OPTION_LONG("graceful", NULL, "Exit gracefully if no TPM2 device is found"): arg_graceful = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind != argc) + if (option_parser_get_n_args(&state) != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program expects no argument."); return 1; @@ -143,7 +128,7 @@ static void public_key_data_done(struct public_key_data *d) { assert(d); if (d->pkey) { - EVP_PKEY_free(d->pkey); + sym_EVP_PKEY_free(d->pkey); d->pkey = NULL; } if (d->public) { @@ -164,7 +149,7 @@ static int public_key_make_fingerprint(struct public_key_data *d) { assert(!d->fingerprint); assert(!d->fingerprint_hex); - r = pubkey_fingerprint(d->pkey, EVP_sha256(), &d->fingerprint, &d->fingerprint_size); + r = pubkey_fingerprint(d->pkey, sym_EVP_sha256(), &d->fingerprint, &d->fingerprint_size); if (r < 0) return log_error_errno(r, "Failed to calculate fingerprint of public key: %m"); @@ -291,8 +276,8 @@ static int setup_srk(void) { log_struct_errno(LOG_INFO, r, LOG_MESSAGE("Insufficient permissions to access TPM, not generating SRK."), LOG_MESSAGE_ID(SD_MESSAGE_SRK_ENROLLMENT_NEEDS_AUTHORIZATION_STR)); - return 76; /* Special return value which means "Insufficient permissions to access TPM, - * cannot generate SRK". This isn't really an error when called at boot. */; + return EX_PROTOCOL; /* Special return value which means "Insufficient permissions to access TPM, + * cannot generate SRK". This isn't really an error when called at boot. */; } if (r < 0) return r; @@ -336,7 +321,7 @@ static int setup_srk(void) { if (r < 0) return log_error_errno(r, "Failed to open SRK public key file '%s' for writing: %m", pem_path); - if (PEM_write_PUBKEY(f, tpm2_key.pkey) <= 0) + if (sym_PEM_write_PUBKEY(f, tpm2_key.pkey) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write SRK public key file '%s'.", pem_path); if (fchmod(fileno(f), 0444) < 0) @@ -422,11 +407,17 @@ static int setup_nvpcr_one( r = tpm2_nvpcr_initialize(c->tpm2_context, /* session= */ NULL, name, &c->anchor_secret); } + if (r == -EOPNOTSUPP) { + c->n_failed++; + return log_struct_errno(LOG_ERR, r, + LOG_MESSAGE("The TPM does not correctly support NV indexes in NT_EXTEND mode, unable to allocate NvPCR '%s': %m", name), + LOG_MESSAGE_ID(SD_MESSAGE_TPM_NVPCR_UNSUPPORTED_STR)); + } if (r == -ENOSPC) { c->n_failed++; return log_struct_errno(LOG_ERR, r, LOG_MESSAGE("The TPM's NV index space is exhausted, unable to allocate NvPCR '%s': %m", name), - LOG_MESSAGE_ID(SD_MESSAGE_TPM_INVINDEX_EXHAUSTED_STR)); + LOG_MESSAGE_ID(SD_MESSAGE_TPM_NVINDEX_EXHAUSTED_STR)); } if (r < 0) { c->n_failed++; @@ -446,7 +437,7 @@ static int setup_nvpcr_one( static int setup_nvpcr(void) { _cleanup_(setup_nvpcr_context_done) SetupNvPCRContext c = {}; - int r = 0; + int r; _cleanup_strv_free_ char **l = NULL; r = conf_files_list_nulstr( @@ -471,13 +462,11 @@ static int setup_nvpcr(void) { RET_GATHER(ret, setup_nvpcr_one(&c, *i)); } - if (c.n_already > 0 && c.n_anchored == 0 && !arg_early) { + if (c.n_already > 0 && c.n_anchored == 0 && !arg_early) /* If we didn't anchor anything right now, but we anchored something earlier, then it might * have happened in the initrd, and thus the anchor ID was not committed to /var/ or the ESP * yet. Hence, let's explicitly do so now, to catch up. */ - RET_GATHER(ret, tpm2_nvpcr_acquire_anchor_secret(/* ret= */ NULL, /* sync_secondary= */ true)); - } if (c.n_failed > 0) log_warning("%zu NvPCRs failed to initialize, proceeding anyway.", c.n_failed); @@ -492,6 +481,13 @@ static int setup_nvpcr(void) { else if (c.n_failed == 0) log_debug("No NvPCRs defined, nothing initialized."); + /* Turn some errors into recognizable ones, which we can catch with + * SuccessExitStatus= in the service unit file. */ + if (ret == -EOPNOTSUPP) + return EX_UNAVAILABLE; /* e.g. no NvPCR support in TPM */ + if (ret == -ENOSPC) + return EX_CANTCREAT; /* NV index space on TPM exhausted */ + return ret; } @@ -504,17 +500,26 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - if (arg_graceful && !tpm2_is_fully_supported()) { + if (arg_graceful && !tpm2_is_mostly_supported()) { log_notice("No complete TPM2 support detected, exiting gracefully."); return EXIT_SUCCESS; } + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + umask(0022); + /* Execute both jobs, and then return unlisted errors preferably, and listed errors + * (i.e. EX_UNAVAILABLE, EX_CANTCREAT, EX_PROTOCOL) otherwise. */ r = setup_srk(); - RET_GATHER(r, setup_nvpcr()); - - return r; + int k = setup_nvpcr(); + if (r < 0) + return r; + if (k < 0) + return k; + return r != EXIT_SUCCESS ? r : k; } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/tpm2-setup/tpm2-swtpm.c b/src/tpm2-setup/tpm2-swtpm.c new file mode 100644 index 0000000000000..db5ec09312b5e --- /dev/null +++ b/src/tpm2-setup/tpm2-swtpm.c @@ -0,0 +1,236 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-daemon.h" + +#include "alloc-util.h" +#include "chase.h" +#include "errno-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "find-esp.h" +#include "fs-util.h" +#include "hexdecoct.h" +#include "hmac.h" +#include "initrd-util.h" +#include "iovec-util.h" +#include "log.h" +#include "main-func.h" +#include "path-lookup.h" +#include "path-util.h" +#include "sha256.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "swtpm-util.h" + +#define BOOT_SECRET_SIZE 32U + +static int load_boot_secret(struct iovec *ret) { + _cleanup_(iovec_done_erase) struct iovec buf = {}; + int r; + + assert(ret); + + const char *bs = in_initrd() ? "/.extra/boot-secret" : "/run/systemd/stub/boot-secret"; + r = read_full_file_full( + AT_FDCWD, + bs, + UINT64_MAX, + BOOT_SECRET_SIZE, + READ_FULL_FILE_SECURE|READ_FULL_FILE_VERIFY_REGULAR, + /* bind_name= */ NULL, + (char**) &buf.iov_base, + &buf.iov_len); + if (r == -ENOENT) { + log_warning_errno(r, "Boot secret (%s) not found, not encrypting software TPM state!", bs); + *ret = (struct iovec) {}; + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to read '%s': %m", bs); + + if (buf.iov_len < BOOT_SECRET_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Boot secret too short, refusing."); + + *ret = TAKE_STRUCT(buf); + return 1; +} + +static int prepare_secret(const char *runtime_dir, char **ret) { + int r; + + assert(runtime_dir); + assert(ret); + + _cleanup_(iovec_done_erase) struct iovec boot_secret = {}; + r = load_boot_secret(&boot_secret); + if (r < 0) + return r; + if (r == 0) { + *ret = NULL; + return 0; + } + + /* Derive a suitable swtpm specific secret */ + static const char tag[] = "systemd swtpm tag v1"; + uint8_t secret[SHA256_DIGEST_SIZE]; + CLEANUP_ERASE(secret); + hmac_sha256(boot_secret.iov_base, + boot_secret.iov_len, + tag, + strlen(tag), + secret); + + _cleanup_free_ char *p = path_join(runtime_dir, "secret"); + if (!p) + return log_oom(); + + assert_cc(sizeof(secret) >= 16); /* swtpm only wants a 16 byte key */ + _cleanup_(erase_and_freep) char *h = hexmem(secret, 16); + if (!h) + return log_oom(); + + r = write_data_file_atomic_at(XAT_FDROOT, p, &IOVEC_MAKE_STRING(h), WRITE_DATA_FILE_MODE_0400); + if (r < 0) + return log_error_errno(r, "Failed to write secret file: %m"); + + *ret = TAKE_PTR(p); + return 1; +} + +static int setup_swtpm(const char *state_dir, int state_fd, const char *secret) { + int r; + + assert(state_dir); + assert(state_fd >= 0); + + /* Sets up the state directory via swtpm_setup */ + + if (in_initrd()) { + /* In the initrd remove previous transient state */ + r = RET_NERRNO(unlinkat(state_fd, "tpm2-00.volatilestate", /* flags= */ 0)); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to remove 'tpm2-00.volatilestate': %m"); + } + + r = dir_is_empty_at(state_fd, /* path= */ NULL, /* ignore_hidden_or_backup= */ false); + if (r < 0) + return log_error_errno(r, "Failed to check if TPM state directory is empty: %m"); + if (r == 0) { + log_debug("TPM state directory is already populated, not manufacturing a TPM."); + return 0; + } + + if (!in_initrd()) + return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "swtpm TPM state directory has not been initialized in the initrd, refusing."); + + log_debug("TPM state directory is unpopulated, manufacturing a TPM."); + + return manufacture_swtpm(state_dir, secret); +} + +static int run(int argc, char *argv[]) { + int r; + + log_setup(); + + _cleanup_free_ char *runtime_dir = NULL; + r = runtime_directory(RUNTIME_SCOPE_SYSTEM, "systemd/swtpm", &runtime_dir); + if (r < 0) + return log_error_errno(r, "Unable to determine runtime directory: %m"); + + _cleanup_free_ char *swtpm = NULL; + r = find_executable("swtpm", &swtpm); + if (r < 0) + return log_error_errno(r, "Failed to find 'swtpm' binary: %m"); + + _cleanup_free_ char *state_dir = NULL; + _cleanup_close_ int state_fd = -EBADF; + if (in_initrd()) { + /* The early ESP support uses only a single mount point, we do not need to search for it. */ + r = chase("/loader/swtpm", + "/sysefi", + CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + &state_dir, + &state_fd); + if (r < 0) + return log_error_errno(r, "Failed to open swtpm state directory in ESP: %m"); + } else { + _cleanup_free_ char *esp_path = NULL; + _cleanup_close_ int esp_fd = -EBADF; + r = find_esp_and_warn( + /* root= */ NULL, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &esp_path, + &esp_fd); + if (r == -ENOKEY) /* This one find_esp_and_warn() doesn't actually log about. */ + return log_error_errno(r, "No ESP discovered."); + if (r < 0) + return r; + + _cleanup_free_ char *unprefixed_state_dir = NULL; + r = chaseat(esp_fd, + esp_fd, + "/loader/swtpm", + CHASE_TRIGGER_AUTOFS|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + &unprefixed_state_dir, + &state_fd); + if (r < 0) + return log_error_errno(r, "Failed to open swtpm state directory in ESP: %m"); + + state_dir = path_join(esp_path, unprefixed_state_dir); + if (!state_dir) + return log_oom(); + } + + _cleanup_(unlink_and_freep) char *secret = NULL; + r = prepare_secret(runtime_dir, &secret); + if (r < 0) + return r; + + r = setup_swtpm(state_dir, state_fd, secret); + if (r < 0) + return r; + + _cleanup_strv_free_ char **args = + strv_new(swtpm, + "chardev", + "--vtpm-proxy", + "--tpm2", + /* Make sure that in the initrd swtpm never sends TPM2_Shutdown() for us, we want to + * be able to stop the daemon after all temporarily during the initrd→host + * transition. */ + in_initrd() ? "--flags=startup-clear,disable-auto-shutdown" : "--flags=startup-clear"); + if (!args) + return log_oom(); + + if (strv_extendf(&args, "--ctrl=type=unixio,path=%s/socket", runtime_dir) < 0) + return log_oom(); + + if (secret && strv_extendf(&args, "--key=file=%s,format=hex,mode=aes-cbc,remove=true", secret) < 0) + return log_oom(); + + if (strv_extendf(&args, "--tpmstate=dir=%s,mode=0600", state_dir) < 0) + return log_oom(); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *cmd = quote_command_line(args, SHELL_ESCAPE_EMPTY); + + log_debug("Chain-loading: %s", strnull(cmd)); + } + + /* Ideally swtpm could send this itself, but for now let's accept it like this. */ + // FIXME: remove this once swtpm 0.11 is released and hit all relevant distros. Then bump version + // requirements. + (void) sd_notify(/* unset_environment= */ true, "READY=1"); + + /* NB: if the execve() succeeds it's swtpm's job to actually unlink the secret file */ + execv(swtpm, args); + return log_error_errno(errno, "Failed to chainload swtpm: %m"); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/tty-ask-password-agent/tty-ask-password-agent.c b/src/tty-ask-password-agent/tty-ask-password-agent.c index 1ee2239aa4a59..b927871d211c3 100644 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ b/src/tty-ask-password-agent/tty-ask-password-agent.c @@ -4,7 +4,6 @@ ***/ #include -#include #include #include #include @@ -23,12 +22,14 @@ #include "errno-util.h" #include "exit-status.h" #include "fd-util.h" -#include "format-util.h" #include "fileio.h" +#include "format-table.h" +#include "format-util.h" #include "inotify-util.h" #include "io-util.h" #include "main-func.h" #include "mkdir-label.h" +#include "options.h" #include "path-util.h" #include "pidref.h" #include "pretty-print.h" @@ -442,112 +443,85 @@ static int process_and_watch_password_files(bool watch) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-tty-ask-password-agent", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...]\n\n" - "%sProcess system password requests.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --list Show pending password requests\n" - " --query Process pending password requests\n" - " --watch Continuously process password requests\n" - " --wall Continuously forward password requests to wall\n" - " --plymouth Ask question with Plymouth instead of on TTY\n" - " --console[=DEVICE] Ask question on /dev/console (or DEVICE if specified)\n" - " instead of the current TTY\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\n%sProcess system password requests.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_LIST = 0x100, - ARG_QUERY, - ARG_WATCH, - ARG_WALL, - ARG_PLYMOUTH, - ARG_CONSOLE, - ARG_VERSION - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "list", no_argument, NULL, ARG_LIST }, - { "query", no_argument, NULL, ARG_QUERY }, - { "watch", no_argument, NULL, ARG_WATCH }, - { "wall", no_argument, NULL, ARG_WALL }, - { "plymouth", no_argument, NULL, ARG_PLYMOUTH }, - { "console", optional_argument, NULL, ARG_CONSOLE }, - {} - }; - - int r, c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_LIST: + OPTION_LONG("list", NULL, "Show pending password requests"): arg_action = ACTION_LIST; break; - case ARG_QUERY: + OPTION_LONG("query", NULL, "Process pending password requests"): arg_action = ACTION_QUERY; break; - case ARG_WATCH: + OPTION_LONG("watch", NULL, "Continuously process password requests"): arg_action = ACTION_WATCH; break; - case ARG_WALL: + OPTION_LONG("wall", NULL, "Continuously forward password requests to wall"): arg_action = ACTION_WALL; break; - case ARG_PLYMOUTH: + OPTION_LONG("plymouth", NULL, + "Ask question with Plymouth instead of on TTY"): arg_plymouth = true; break; - case ARG_CONSOLE: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "console", "DEVICE", + "Ask question on /dev/console (or DEVICE if specified) instead of the current TTY"): arg_console = true; - if (optarg) { - if (isempty(optarg)) + if (arg) { + if (isempty(arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty console device path is not allowed."); - r = free_and_strdup_warn(&arg_device, optarg); + r = free_and_strdup_warn(&arg_device, arg); if (r < 0) return r; } break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind != argc) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s takes no arguments.", program_invocation_short_name); diff --git a/src/udev/ata_id/ata_id.c b/src/udev/ata_id/ata_id.c index 508f99b01a06a..112456ffc2fd0 100644 --- a/src/udev/ata_id/ata_id.c +++ b/src/udev/ata_id/ata_id.c @@ -6,7 +6,6 @@ */ #include -#include #include #include #include @@ -18,8 +17,12 @@ #include "device-nodes.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" #include "main-func.h" +#include "options.h" +#include "strv.h" #include "udev-util.h" #include "unaligned.h" @@ -356,40 +359,47 @@ static int disk_identify(int fd, return 0; } +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...] DEVICE"); + help_section("Options:"); + + return table_print_or_warn(options); +} + static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "export", no_argument, NULL, 'x' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'v' }, - {} - }; - int c; + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + const char *arg; - while ((c = getopt_long(argc, argv, "xh", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'x': + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('x', "export", NULL, + "Print values as environment keys"): arg_export = true; break; - case 'h': - printf("%s [OPTIONS...] DEVICE\n\n" - " -x --export Print values as environment keys\n" - " -h --help Show this help text\n" - " --version Show package version\n", - program_invocation_short_name); - return 0; - case 'v': - return version(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - if (!argv[optind]) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "DEVICE argument missing."); + char **args = option_parser_get_args(&state); + if (strv_length(args) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Need exactly one DEVICE argument."); - arg_device = argv[optind]; + arg_device = args[0]; return 1; } diff --git a/src/udev/cdrom_id/cdrom_id.c b/src/udev/cdrom_id/cdrom_id.c index d311f2b3222ec..1a167475d20d8 100644 --- a/src/udev/cdrom_id/cdrom_id.c +++ b/src/udev/cdrom_id/cdrom_id.c @@ -4,7 +4,6 @@ */ #include -#include #include #include #include @@ -13,11 +12,15 @@ #include "build.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" +#include "help-util.h" #include "main-func.h" +#include "options.h" #include "random-util.h" #include "sort-util.h" #include "string-table.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" #include "udev-util.h" #include "unaligned.h" @@ -898,60 +901,63 @@ static void print_properties(const Context *c) { } static int help(void) { - printf("%s [OPTIONS...] DEVICE\n\n" - " -l --lock-media Lock the media (to enable eject request events)\n" - " -u --unlock-media Unlock the media\n" - " -e --eject-media Eject the media\n" - " -d --debug Print debug messages to stderr\n" - " -h --help Show this help text\n" - " --version Show package version\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; - return 0; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...] DEVICE"); + help_section("Options:"); + + return table_print_or_warn(options); } static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "lock-media", no_argument, NULL, 'l' }, - { "unlock-media", no_argument, NULL, 'u' }, - { "eject-media", no_argument, NULL, 'e' }, - { "debug", no_argument, NULL, 'd' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'v' }, - {} - }; - int c; + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + const char *arg; - while ((c = getopt_long(argc, argv, "deluh", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'l': + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('l', "lock-media", NULL, + "Lock the media (to enable eject request events)"): arg_lock = true; break; - case 'u': + + OPTION('u', "unlock-media", NULL, + "Unlock the media"): arg_unlock = true; break; - case 'e': + + OPTION('e', "eject-media", NULL, + "Eject the media"): arg_eject = true; break; - case 'd': + + OPTION('d', "debug", NULL, + "Print debug messages to stderr"): log_set_target(LOG_TARGET_CONSOLE); log_set_max_level(LOG_DEBUG); log_open(); break; - case 'h': - return help(); - case 'v': - return version(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - arg_node = argv[optind]; - if (!arg_node) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No device specified."); + char **args = option_parser_get_args(&state); + if (strv_length(args) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Need exactly one DEVICE argument."); + arg_node = args[0]; return 1; } diff --git a/src/udev/dmi_memory_id/dmi_memory_id.c b/src/udev/dmi_memory_id/dmi_memory_id.c index 9475edcd5418c..df683d00729d0 100644 --- a/src/udev/dmi_memory_id/dmi_memory_id.c +++ b/src/udev/dmi_memory_id/dmi_memory_id.c @@ -42,16 +42,19 @@ * https://www.dmtf.org/sites/default/files/DSP0270_1.0.1.pdf */ -#include #include #include "alloc-util.h" #include "build.h" #include "fileio.h" +#include "format-table.h" +#include "help-util.h" #include "main-func.h" +#include "options.h" #include "string-util.h" #include "udev-util.h" #include "unaligned.h" +#include "utf8.h" #define SUPPORTED_SMBIOS_VER 0x030300 @@ -186,7 +189,7 @@ static void dmi_memory_device_string( str = strdupa_safe(dmi_string(h, s)); str = strstrip(str); - if (!isempty(str)) + if (!isempty(str) && utf8_is_valid(str) && !string_has_cc(str, /* ok= */ NULL)) printf("MEMORY_DEVICE_%u_%s=%s\n", slot_num, attr_suffix, str); } @@ -642,45 +645,49 @@ static int legacy_decode(const uint8_t *buf, const char *devmem, bool no_file_of } static int help(void) { - printf("%s [OPTIONS...]\n\n" - " -F --from-dump FILE Read DMI information from a binary file\n" - " -h --help Show this help text\n" - " --version Show package version\n", - program_invocation_short_name); - return 0; + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...]"); + help_section("Options:"); + + return table_print_or_warn(options); } -static int parse_argv(int argc, char * const *argv) { - static const struct option options[] = { - { "from-dump", required_argument, NULL, 'F' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'v' }, - {} - }; - int c; +static int parse_argv(int argc, char *argv[]) { + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + const char *arg; - while ((c = getopt_long(argc, argv, "F:hV", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'F': - arg_source_file = optarg; - break; - case 'V': - return version(); - case 'h': + + OPTION_COMMON_HELP: return help(); - case '?': - return -EINVAL; - case 'v': + + OPTION_COMMON_VERSION_WITH_HIDDEN_V: return version(); - default: - assert_not_reached(); + + OPTION('F', "from-dump", "FILE", + "Read DMI information from a binary file"): + arg_source_file = arg; + break; } + if (option_parser_get_n_args(&state) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This program takes no arguments."); + return 1; } -static int run(int argc, char* const* argv) { +static int run(int argc, char *argv[]) { _cleanup_free_ uint8_t *buf = NULL; bool no_file_offset = false; size_t size; diff --git a/src/udev/fido_id/fido_id.c b/src/udev/fido_id/fido_id.c index aa918a9798460..a301b9acdb087 100644 --- a/src/udev/fido_id/fido_id.c +++ b/src/udev/fido_id/fido_id.c @@ -7,7 +7,6 @@ */ #include -#include #include #include #include @@ -18,41 +17,54 @@ #include "device-util.h" #include "fd-util.h" #include "fido_id_desc.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "path-util.h" +#include "strv.h" #include "udev-util.h" static const char *arg_device = NULL; +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...] SYSFS_PATH"); + help_abstract("Identify FIDO security tokens."); + help_section("Options:"); + + return table_print_or_warn(options); +} + static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'v' }, - {} - }; - int c; - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - printf("%s [OPTIONS...] SYSFS_PATH\n\n" - " -h --help Show this help text\n" - " --version Show package version\n", - program_invocation_short_name); - return 0; - case 'v': + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: return version(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - if (argc > 2) + char **args = option_parser_get_args(&state); + if (strv_length(args) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error: unexpected argument."); - arg_device = argv[optind]; + arg_device = args[0]; return 1; } diff --git a/src/udev/iocost/iocost.c b/src/udev/iocost/iocost.c index a1c9e0c9ede64..0767ccba09525 100644 --- a/src/udev/iocost/iocost.c +++ b/src/udev/iocost/iocost.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-device.h" @@ -11,7 +10,10 @@ #include "conf-parser.h" #include "device-util.h" #include "devnum-util.h" +#include "format-table.h" +#include "help-util.h" #include "main-func.h" +#include "options.h" #include "string-util.h" #include "strv.h" #include "udev-util.h" @@ -50,53 +52,52 @@ static int parse_config(void) { } static int help(void) { - printf("%s [OPTIONS...]\n\n" - "Set up iocost model and qos solutions for block devices\n" - "\nCommands:\n" - " apply [SOLUTION] Apply solution for the device if\n" - " found, do nothing otherwise\n" - " query Query the known solution for\n" - " the device\n" - "\nOptions:\n" - " -h --help Show this help\n" - " --version Show package version\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; + int r; - return 0; -} + r = option_parser_get_help_table(&options); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; + (void) table_sync_column_widths(0, options, verbs); + + help_cmdline("[OPTIONS...] COMMAND"); + help_abstract("Set up iocost model and qos solutions for block devices."); + + help_section("Commands:"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options:"); + return table_print_or_warn(options); +} - int c; +VERB_COMMON_HELP_HIDDEN(help); - assert(argc >= 1); +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + assert(argc >= 0); assert(argv); + assert(remaining_args); + + OptionParser state = { argc, argv }; + const char *arg; - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *remaining_args = option_parser_get_args(&state); return 1; } @@ -280,32 +281,27 @@ static int query_solutions_for_path(const char *path) { return 0; } -static int verb_query(int argc, char *argv[], void *userdata) { +VERB(verb_query, "query", "PATH", 2, 2, 0, + "Query the known solution for the device"); +static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { return query_solutions_for_path(ASSERT_PTR(argv[1])); } -static int verb_apply(int argc, char *argv[], void *userdata) { +VERB(verb_apply, "apply", "PATH [SOLUTION]", 2, 3, 0, + "Apply solution for the device if found, do nothing otherwise"); +static int verb_apply(int argc, char *argv[], uintptr_t _data, void *userdata) { return apply_solution_for_path( ASSERT_PTR(argv[1]), argc > 2 ? ASSERT_PTR(argv[2]) : NULL); } -static int iocost_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "query", 2, 2, 0, verb_query }, - { "apply", 2, 3, 0, verb_apply }, - {}, - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { + char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -313,7 +309,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return iocost_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/udev/meson.build b/src/udev/meson.build index 7df5b3de857b1..700a8cc8d5ed3 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -59,6 +59,10 @@ if conf.get('HAVE_ACL') == 1 udevadm_extract_sources += files('udev-builtin-uaccess.c') endif +if conf.get('HAVE_TPM2') == 1 + udevadm_extract_sources += files('udev-builtin-tpm2_id.c') +endif + ############################################################ generate_keyboard_keys_list = files('generate-keyboard-keys-list.sh') diff --git a/src/udev/mtd_probe/mtd_probe.c b/src/udev/mtd_probe/mtd_probe.c index d79a0617e21e0..1cc769ad76aed 100644 --- a/src/udev/mtd_probe/mtd_probe.c +++ b/src/udev/mtd_probe/mtd_probe.c @@ -19,48 +19,59 @@ */ #include -#include #include -#include #include #include "build.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" #include "main-func.h" #include "mtd_probe.h" +#include "options.h" +#include "strv.h" static const char *arg_device = NULL; +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...] /dev/mtd[n]"); + help_abstract("Probe MTD devices."); + help_section("Options:"); + + return table_print_or_warn(options); +} + static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'v' }, - {} - }; - int c; - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - printf("%s /dev/mtd[n]\n\n" - " -h --help Show this help text\n" - " --version Show package version\n", - program_invocation_short_name); - return 0; - case 'v': + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: return version(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - if (argc > 2) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error: unexpected argument."); + char **args = option_parser_get_args(&state); + if (strv_length(args) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Need exactly one DEVICE argument."); - arg_device = argv[optind]; + arg_device = args[0]; return 1; } @@ -73,12 +84,12 @@ static int run(int argc, char** argv) { if (r <= 0) return r; - mtd_fd = open(argv[1], O_RDONLY|O_CLOEXEC|O_NOCTTY); + mtd_fd = open(arg_device, O_RDONLY|O_CLOEXEC|O_NOCTTY); if (mtd_fd < 0) { bool ignore = ERRNO_IS_DEVICE_ABSENT_OR_EMPTY(errno); log_full_errno(ignore ? LOG_DEBUG : LOG_WARNING, errno, "Failed to open device node '%s'%s: %m", - argv[1], ignore ? ", ignoring" : ""); + arg_device, ignore ? ", ignoring" : ""); return ignore ? 0 : -errno; } diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index eefa95dc5f68d..3c4b886e6759c 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -558,26 +558,6 @@ static int link_apply_ethtool_settings(Link *link, int *ethtool_fd) { return 0; } -static bool hw_addr_is_valid(Link *link, const struct hw_addr_data *hw_addr) { - assert(link); - assert(hw_addr); - - switch (link->iftype) { - case ARPHRD_ETHER: - /* Refuse all zero and all 0xFF. */ - assert(hw_addr->length == ETH_ALEN); - return !ether_addr_is_null(&hw_addr->ether) && !ether_addr_is_broadcast(&hw_addr->ether); - - case ARPHRD_INFINIBAND: - /* The last 8 bytes cannot be zero. */ - assert(hw_addr->length == INFINIBAND_ALEN); - return !memeqzero(hw_addr->bytes + INFINIBAND_ALEN - 8, 8); - - default: - assert_not_reached(); - } -} - static int link_generate_new_hw_addr(Link *link, struct hw_addr_data *ret) { struct hw_addr_data hw_addr = HW_ADDR_NULL; bool is_static = false; @@ -647,7 +627,7 @@ static int link_generate_new_hw_addr(Link *link, struct hw_addr_data *ret) { * systems booting up at the very same time. */ for (;;) { random_bytes(p, len); - if (hw_addr_is_valid(link, &hw_addr)) + if (hw_addr_is_valid(&hw_addr, link->iftype)) break; } @@ -662,7 +642,7 @@ static int link_generate_new_hw_addr(Link *link, struct hw_addr_data *ret) { assert(len <= sizeof(result)); memcpy(p, &result, len); - if (!hw_addr_is_valid(link, &hw_addr)) + if (!hw_addr_is_valid(&hw_addr, link->iftype)) return log_link_warning_errno(link, SYNTHETIC_ERRNO(EINVAL), "Could not generate valid persistent MAC address."); } @@ -1271,7 +1251,7 @@ int config_parse_rx_tx_queues( log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse %s=, ignoring assignment: %s.", lvalue, rvalue); return 0; } - if (k == 0 || k > 4096) { + if (k == 0 || k > 16384) { log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid %s=, ignoring assignment: %s.", lvalue, rvalue); return 0; } diff --git a/src/udev/scsi_id/scsi.h b/src/udev/scsi_id/scsi.h index ebb8ae9008be9..71c532e427ac5 100644 --- a/src/udev/scsi_id/scsi.h +++ b/src/udev/scsi_id/scsi.h @@ -24,7 +24,7 @@ struct scsi_ioctl_command { * as this is a nice value for some devices, especially some of the usb * mass storage devices. */ -#define SCSI_INQ_BUFF_LEN 254 +#define SCSI_INQ_BUFF_LEN 254U /* * SCSI INQUIRY vendor and model (really product) lengths. diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c index 5216455f41d59..8f580a8cec6f5 100644 --- a/src/udev/scsi_id/scsi_id.c +++ b/src/udev/scsi_id/scsi_id.c @@ -5,7 +5,6 @@ */ #include -#include #include #include @@ -15,28 +14,17 @@ #include "extract-word.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" +#include "help-util.h" +#include "main-func.h" +#include "options.h" +#include "parse-util.h" #include "scsi_id.h" #include "string-util.h" #include "strv.h" #include "strxcpyx.h" #include "udev-util.h" - -static const struct option options[] = { - { "device", required_argument, NULL, 'd' }, - { "config", required_argument, NULL, 'f' }, - { "page", required_argument, NULL, 'p' }, - { "denylisted", no_argument, NULL, 'b' }, - { "allowlisted", no_argument, NULL, 'g' }, - { "blacklisted", no_argument, NULL, 'b' }, /* backward compat */ - { "whitelisted", no_argument, NULL, 'g' }, /* backward compat */ - { "replace-whitespace", no_argument, NULL, 'u' }, - { "sg-version", required_argument, NULL, 's' }, - { "verbose", no_argument, NULL, 'v' }, - { "version", no_argument, NULL, 'V' }, /* don't advertise -V */ - { "export", no_argument, NULL, 'x' }, - { "help", no_argument, NULL, 'h' }, - {} -}; +#include "utf8.h" static bool all_good = false; static bool dev_specified = false; @@ -99,31 +87,22 @@ static void set_type(unsigned type_num, char *to, size_t len) { * * vendor and model can end in '\n'. */ -static int get_file_options(const char *vendor, const char *model, - int *argc, char ***newargv) { +static int get_file_options(const char *vendor, const char *model, char ***ret_argv) { _cleanup_free_ char *vendor_in = NULL, *model_in = NULL, *options_in = NULL; /* read in from file */ _cleanup_strv_free_ char **options_argv = NULL; - _cleanup_fclose_ FILE *f = NULL; - int lineno, r; + int r; - f = fopen(config_file, "re"); + _cleanup_fclose_ FILE *f = fopen(config_file, "re"); if (!f) { if (errno == ENOENT) - return 1; - else { - log_error_errno(errno, "can't open %s: %m", config_file); - return -1; - } + goto finish; + return log_error_errno(errno, "Cannot open %s: %m", config_file); } - *newargv = NULL; - lineno = 0; - for (;;) { + for (int lineno = 0;;) { _cleanup_free_ char *buffer = NULL, *key = NULL, *value = NULL; const char *buf; - vendor_in = model_in = options_in = NULL; - r = read_line(f, MAX_BUFFER_LEN, &buffer); if (r < 0) return log_error_errno(r, "read_line() on line %d of %s failed: %m", lineno, config_file); @@ -195,185 +174,179 @@ static int get_file_options(const char *vendor, const char *model, } - if (vendor_in == NULL && model_in == NULL && options_in == NULL) - return 1; /* No matches */ + if (!vendor_in && !model_in && !options_in) + goto finish; /* No matches */ - /* - * Something matched. Allocate newargv, and store - * values found in options_in. - */ + /* Something matched. Allocate newargv, and store values found in options_in. */ options_argv = strv_split(options_in, " \t"); if (!options_argv) return log_oom(); - r = strv_prepend(&options_argv, ""); /* getopt skips over argv[0] */ + r = strv_prepend(&options_argv, ""); /* argv[0] is skipped */ if (r < 0) return r; - *newargv = TAKE_PTR(options_argv); - *argc = strv_length(*newargv); + finish: + *ret_argv = TAKE_PTR(options_argv); + return !!*ret_argv; /* true if something matched, false otherwise */ +} + +static int parse_page_code(const char *value, enum page_code *ret) { + assert(value); + assert(ret); + + if (streq(value, "0x80")) + *ret = PAGE_80; + else if (streq(value, "0x83")) + *ret = PAGE_83; + else if (streq(value, "pre-spc3-83")) + *ret = PAGE_83_PRE_SPC3; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown page code '%s'", value); return 0; } -static void help(void) { - printf("Usage: %s [OPTION...] DEVICE\n\n" - "SCSI device identification.\n\n" - " -h --help Print this message\n" - " --version Print version of the program\n\n" - " -d --device= Device node for SG_IO commands\n" - " -f --config= Location of config file\n" - " -p --page=0x80|0x83|pre-spc3-83 SCSI page (0x80, 0x83, pre-spc3-83)\n" - " -s --sg-version=3|4 Use SGv3 or SGv4\n" - " -b --denylisted Treat device as denylisted\n" - " -g --allowlisted Treat device as allowlisted\n" - " -u --replace-whitespace Replace all whitespace by underscores\n" - " -v --verbose Verbose logging\n" - " -x --export Print values as environment keys\n", - program_invocation_short_name); +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTION...] DEVICE"); + help_abstract("SCSI device identification."); + help_section("Options:"); + + return table_print_or_warn(options); } -static int set_options(int argc, char **argv, - char *maj_min_dev) { - int option; +static int set_options(int argc, char **argv, char *maj_min_dev) { + assert(argc >= 0); + assert(argv); - /* - * optind is a global extern used by getopt. Since we can call - * set_options twice (once for command line, and once for config - * file) we have to reset this back to 1. - */ - optind = 1; - while ((option = getopt_long(argc, argv, "d:f:gp:uvVxhbs:", options, NULL)) >= 0) - switch (option) { - case 'b': - all_good = false; - break; + OptionParser state = { argc, argv }; + const char *arg; + int r; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + switch (c) { + + OPTION_COMMON_HELP: + return help(); - case 'd': + OPTION_COMMON_VERSION_WITH_HIDDEN_V: + return version(); + + OPTION('d', "device", "PATH", "Device node for SG_IO commands"): dev_specified = true; - strscpy(maj_min_dev, MAX_PATH_LEN, optarg); + strscpy(maj_min_dev, MAX_PATH_LEN, arg); break; - case 'f': - strscpy(config_file, MAX_PATH_LEN, optarg); + OPTION('f', "config", "PATH", "Location of config file"): + strscpy(config_file, MAX_PATH_LEN, arg); break; - case 'g': - all_good = true; + OPTION('p', "page", "0x80|0x83|pre-spc3-83", "SCSI page"): + r = parse_page_code(arg, &default_page_code); + if (r < 0) + return r; + break; + + OPTION('s', "sg-version", "3|4", "Use SGv3 or SGv4"): + r = safe_atoi(arg, &sg_version); + if (r < 0) + return log_error_errno(r, "Invalid SG version '%s'", arg); + if (!IN_SET(sg_version, 3, 4)) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), + "Unknown SG version '%s'", arg); break; - case 'h': - help(); - exit(EXIT_SUCCESS); - - case 'p': - if (streq(optarg, "0x80")) - default_page_code = PAGE_80; - else if (streq(optarg, "0x83")) - default_page_code = PAGE_83; - else if (streq(optarg, "pre-spc3-83")) - default_page_code = PAGE_83_PRE_SPC3; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown page code '%s'", - optarg); + OPTION('b', "denylisted", NULL, "Treat device as denylisted"): {} + OPTION('b', "blacklisted", NULL, /* help= */ NULL): /* backward compat */ + all_good = false; break; - case 's': - sg_version = atoi(optarg); - if (sg_version < 3 || sg_version > 4) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown SG version '%s'", - optarg); + OPTION('g', "allowlisted", NULL, "Treat device as allowlisted"): {} + OPTION('g', "whitelisted", NULL, /* help= */ NULL): /* backward compat */ + all_good = true; break; - case 'u': + OPTION('u', "replace-whitespace", NULL, "Replace all whitespace by underscores"): reformat_serial = true; break; - case 'v': + OPTION('v', "verbose", NULL, "Verbose logging"): log_set_target(LOG_TARGET_CONSOLE); log_set_max_level(LOG_DEBUG); log_open(); break; - case 'V': - version(); - exit(EXIT_SUCCESS); - - case 'x': + OPTION('x', "export", NULL, "Print values as environment keys"): export = true; break; - - case '?': - return -1; - - default: - assert_not_reached(); } - if (optind < argc && !dev_specified) { + char **args = option_parser_get_args(&state); + if (!strv_isempty(args) && !dev_specified) { dev_specified = true; - strscpy(maj_min_dev, MAX_PATH_LEN, argv[optind]); + strscpy(maj_min_dev, MAX_PATH_LEN, args[0]); } - return 0; + return 1; } -static int per_dev_options(struct scsi_id_device *dev_scsi, int *good_bad, int *page_code) { - _cleanup_strv_free_ char **newargv = NULL; - int retval; - int newargc; - int option; +static int per_dev_options(struct scsi_id_device *dev_scsi, int *good_bad, enum page_code *page_code) { + int r; + + assert(dev_scsi); + assert(good_bad); + assert(page_code); *good_bad = all_good; *page_code = default_page_code; - retval = get_file_options(vendor_str, model_str, &newargc, &newargv); + _cleanup_strv_free_ char **newargv = NULL; + r = get_file_options(vendor_str, model_str, &newargv); + if (r <= 0) + return r; - optind = 1; /* reset this global extern */ - while (retval == 0) { - option = getopt_long(newargc, newargv, "bgp:", options, NULL); - if (option == -1) - break; + size_t newargc = strv_length(newargv); + if (newargc > INT_MAX) + return log_oom(); /* Close enough :) */ - switch (option) { - case 'b': - *good_bad = 0; - break; - - case 'g': - *good_bad = 1; - break; + OptionParser state = { newargc, newargv }; + const Option *opt; + const char *arg; - case 'p': - if (streq(optarg, "0x80")) { - *page_code = PAGE_80; - } else if (streq(optarg, "0x83")) { - *page_code = PAGE_83; - } else if (streq(optarg, "pre-spc3-83")) { - *page_code = PAGE_83_PRE_SPC3; - } else { - log_error("Unknown page code '%s'", optarg); - retval = -1; - } - break; + /* We reuse the option parser, but only a subset of the options is supported here. + * If any others are encountered, return an error. */ - default: - log_error("Unknown or bad option '%c' (0x%x)", option, (unsigned) option); - retval = -1; - } - } + FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) + if (opt->short_code == 'b') + *good_bad = 0; + else if (opt->short_code == 'g') + *good_bad = 1; + else if (opt->short_code == 'p') { + r = parse_page_code(arg, page_code); + if (r < 0) + return r; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Option %s not supported in the config file.", + strnull(option_get_synopsis("", opt, "/", /* show_metavar=*/ false))); - return retval; + return 0; } static int set_inq_values(struct scsi_id_device *dev_scsi, const char *path) { - int retval; + int r; dev_scsi->use_sg = sg_version; - retval = scsi_std_inquiry(dev_scsi, path); - if (retval) - return retval; + r = scsi_std_inquiry(dev_scsi, path); + if (r < 0) + return r; encode_devnode_name(dev_scsi->vendor, vendor_enc_str, sizeof(vendor_enc_str)); encode_devnode_name(dev_scsi->model, model_enc_str, sizeof(model_enc_str)); @@ -388,30 +361,31 @@ static int set_inq_values(struct scsi_id_device *dev_scsi, const char *path) { return 0; } +static bool scsi_string_is_valid(const char *s) { + return !isempty(s) && utf8_is_valid(s) && !string_has_cc(s, /* ok= */ NULL); +} + /* * scsi_id: try to get an id, if one is found, printf it to stdout. - * returns a value passed to exit() - 0 if printed an id, else 1. */ static int scsi_id(char *maj_min_dev) { struct scsi_id_device dev_scsi = {}; - int good_dev; - int page_code; - int retval = 0; + enum page_code page_code; + int good_dev, r; - if (set_inq_values(&dev_scsi, maj_min_dev) < 0) { - retval = 1; - goto out; - } + r = set_inq_values(&dev_scsi, maj_min_dev); + if (r < 0) + return r; /* get per device (vendor + model) options from the config file */ - per_dev_options(&dev_scsi, &good_dev, &page_code); - if (!good_dev) { - retval = 1; - goto out; - } + r = per_dev_options(&dev_scsi, &good_dev, &page_code); + if (r < 0) + return r; + if (!good_dev) + return -EIO; /* read serial number from mode pages (no values for optical drives) */ - scsi_get_serial(&dev_scsi, maj_min_dev, page_code, MAX_SERIAL_LEN); + (void) scsi_get_serial(&dev_scsi, maj_min_dev, page_code, MAX_SERIAL_LEN); if (export) { char serial_str[MAX_SERIAL_LEN]; @@ -431,25 +405,23 @@ static int scsi_id(char *maj_min_dev) { udev_replace_chars(serial_str, NULL); printf("ID_SERIAL_SHORT=%s\n", serial_str); } - if (dev_scsi.wwn[0] != '\0') { + if (scsi_string_is_valid(dev_scsi.wwn)) { printf("ID_WWN=0x%s\n", dev_scsi.wwn); - if (dev_scsi.wwn_vendor_extension[0] != '\0') { + if (scsi_string_is_valid(dev_scsi.wwn_vendor_extension)) { printf("ID_WWN_VENDOR_EXTENSION=0x%s\n", dev_scsi.wwn_vendor_extension); printf("ID_WWN_WITH_EXTENSION=0x%s%s\n", dev_scsi.wwn, dev_scsi.wwn_vendor_extension); } else printf("ID_WWN_WITH_EXTENSION=0x%s\n", dev_scsi.wwn); } - if (dev_scsi.tgpt_group[0] != '\0') + if (scsi_string_is_valid(dev_scsi.tgpt_group)) printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group); - if (dev_scsi.unit_serial_number[0] != '\0') + if (scsi_string_is_valid(dev_scsi.unit_serial_number)) printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number); - goto out; + return 0; } - if (dev_scsi.serial[0] == '\0') { - retval = 1; - goto out; - } + if (dev_scsi.serial[0] == '\0') + return -ENODATA; if (reformat_serial) { char serial_str[MAX_SERIAL_LEN]; @@ -457,19 +429,17 @@ static int scsi_id(char *maj_min_dev) { udev_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str)-1); udev_replace_chars(serial_str, NULL); printf("%s\n", serial_str); - goto out; + return 0; } printf("%s\n", dev_scsi.serial); -out: - return retval; + return 0; } -int main(int argc, char **argv) { +static int run(int argc, char **argv) { _cleanup_strv_free_ char **newargv = NULL; - int retval = 0; char maj_min_dev[MAX_PATH_LEN]; - int newargc; + int r; (void) udev_parse_config(); log_setup(); @@ -477,35 +447,30 @@ int main(int argc, char **argv) { /* * Get config file options. */ - retval = get_file_options(NULL, NULL, &newargc, &newargv); - if (retval < 0) { - retval = 1; - goto exit; - } - if (retval == 0) { - assert(newargv); - - if (set_options(newargc, newargv, maj_min_dev) < 0) { - retval = 2; - goto exit; - } + r = get_file_options(NULL, NULL, &newargv); + if (r < 0) + return r; + if (r == 1) { + size_t newargc = strv_length(newargv); + if (newargc > INT_MAX) + return log_oom(); /* Close enough :) */ + + r = set_options(newargc, newargv, maj_min_dev); + if (r <= 0) + return r; } /* * Get command line options (overriding any config file settings). */ - if (set_options(argc, argv, maj_min_dev) < 0) - exit(EXIT_FAILURE); - - if (!dev_specified) { - log_error("No device specified."); - retval = 1; - goto exit; - } + r = set_options(argc, argv, maj_min_dev); + if (r <= 0) + return r; - retval = scsi_id(maj_min_dev); + if (!dev_specified) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No device specified."); -exit: - log_close(); - return retval; + return scsi_id(maj_min_dev); } + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/udev/scsi_id/scsi_id.h b/src/udev/scsi_id/scsi_id.h index db49c7f3d955c..984bbc05f4ca6 100644 --- a/src/udev/scsi_id/scsi_id.h +++ b/src/udev/scsi_id/scsi_id.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include "basic-forward.h" + /* * Copyright © IBM Corp. 2003 */ @@ -50,7 +52,7 @@ struct scsi_id_device { int scsi_std_inquiry(struct scsi_id_device *dev_scsi, const char *devname); int scsi_get_serial(struct scsi_id_device *dev_scsi, const char *devname, - int page_code, int len); + int page_code, size_t len); /* * Page code values. diff --git a/src/udev/scsi_id/scsi_serial.c b/src/udev/scsi_id/scsi_serial.c index 39937006154e7..6d8974eaa3967 100644 --- a/src/udev/scsi_id/scsi_serial.c +++ b/src/udev/scsi_id/scsi_serial.c @@ -15,11 +15,14 @@ #include #include "devnum-util.h" +#include "fd-util.h" +#include "hexdecoct.h" #include "log.h" #include "random-util.h" #include "scsi.h" #include "scsi_id.h" #include "string-util.h" +#include "strxcpyx.h" #include "time-util.h" /* @@ -55,8 +58,6 @@ static const struct scsi_id_search_values id_search_list[] = { { SCSI_ID_VENDOR_SPECIFIC, SCSI_ID_NAA_DONT_CARE, SCSI_ID_ASCII }, }; -static const char hex_str[]="0123456789abcdef"; - /* * Values returned in the result/status, only the ones used by the code * are used here. @@ -81,7 +82,7 @@ static const char hex_str[]="0123456789abcdef"; #define SG_ERR_CAT_OTHER 99 /* Some other error/warning */ static int do_scsi_page80_inquiry(struct scsi_id_device *dev_scsi, int fd, - char *serial, char *serial_short, int max_len); + char *serial, char *serial_short, size_t max_len); static int sg_err_category_new(int scsi_status, int msg_status, int host_status, int driver_status, const @@ -419,12 +420,12 @@ static int append_vendor_model( * serial number. */ static int check_fill_0x83_id(struct scsi_id_device *dev_scsi, - unsigned char *page_83, + uint8_t *page_83, const struct scsi_id_search_values *id_search, char *serial, char *serial_short, - int max_len, char *wwn, + size_t max_len, char *wwn, char *wwn_vendor_extension, char *tgpt_group) { - int i, j, s, len; + size_t i, j, s, len; /* * ASSOCIATION must be with the device (value 0) @@ -470,7 +471,7 @@ static int check_fill_0x83_id(struct scsi_id_device *dev_scsi, len += VENDOR_LENGTH + MODEL_LENGTH; if (max_len < len) { - log_debug("%s: length %d too short - need %d", + log_debug("%s: length %zu too short - need %zu", dev_scsi->kernel, max_len, len); return 1; } @@ -483,7 +484,7 @@ static int check_fill_0x83_id(struct scsi_id_device *dev_scsi, return 1; } - serial[0] = hex_str[id_search->id_type]; + serial[0] = hexchar(id_search->id_type); /* * For SCSI_ID_VENDOR_SPECIFIC prepend the vendor and model before @@ -491,9 +492,14 @@ static int check_fill_0x83_id(struct scsi_id_device *dev_scsi, * this differs from SCSI_ID_T10_VENDOR, where the vendor is * included in the identifier. */ - if (id_search->id_type == SCSI_ID_VENDOR_SPECIFIC) + if (id_search->id_type == SCSI_ID_VENDOR_SPECIFIC) { if (append_vendor_model(dev_scsi, serial + 1) < 0) return 1; + /* append_vendor_model() uses memcpy() without null-terminating. + * The buffer was zeroed by the caller, but ensure the string is + * explicitly terminated for strlen() below. */ + serial[1 + VENDOR_LENGTH + MODEL_LENGTH] = '\0'; + } i = 4; /* offset to the start of the identifier */ s = j = strlen(serial); @@ -501,16 +507,16 @@ static int check_fill_0x83_id(struct scsi_id_device *dev_scsi, /* * ASCII descriptor. */ - while (i < (4 + page_83[3])) + while (i < (4U + page_83[3])) serial[j++] = page_83[i++]; } else { /* * Binary descriptor, convert to ASCII, using two bytes of * ASCII for each byte in the page_83. */ - while (i < (4 + page_83[3])) { - serial[j++] = hex_str[(page_83[i] & 0xf0) >> 4]; - serial[j++] = hex_str[page_83[i] & 0x0f]; + while (i < (4U + page_83[3])) { + serial[j++] = hexchar(page_83[i] >> 4); + serial[j++] = hexchar(page_83[i]); i++; } } @@ -518,9 +524,9 @@ static int check_fill_0x83_id(struct scsi_id_device *dev_scsi, strcpy(serial_short, serial + s); if (id_search->id_type == SCSI_ID_NAA && wwn != NULL) { - strncpy(wwn, serial + s, 16); + strscpy(wwn, 17, serial + s); if (wwn_vendor_extension) - strncpy(wwn_vendor_extension, serial + s + 16, 16); + strscpy(wwn_vendor_extension, 17, serial + s + 16); } return 0; @@ -528,18 +534,22 @@ static int check_fill_0x83_id(struct scsi_id_device *dev_scsi, /* Extract the raw binary from VPD 0x83 pre-SPC devices */ static int check_fill_0x83_prespc3(struct scsi_id_device *dev_scsi, - unsigned char *page_83, + uint8_t page_83[static SCSI_INQ_BUFF_LEN], const struct scsi_id_search_values - *id_search, char *serial, char *serial_short, int max_len) { - int i, j; + *id_search, char *serial, char *serial_short, size_t max_len) { + size_t j; - serial[0] = hex_str[SCSI_ID_NAA]; + assert(max_len > 0); + + serial[0] = hexchar(SCSI_ID_NAA); /* serial has been memset to zero before */ j = strlen(serial); /* j = 1; */ - for (i = 0; (i < page_83[3]) && (j < max_len-3); ++i) { - serial[j++] = hex_str[(page_83[4+i] & 0xf0) >> 4]; - serial[j++] = hex_str[ page_83[4+i] & 0x0f]; + /* Cap reported page length to buffer size in case of malformed responses */ + size_t page_len = MIN((size_t)page_83[3], SCSI_INQ_BUFF_LEN - 4); + for (size_t i = 0; (i < page_len) && (j + 3 < max_len); ++i) { + serial[j++] = hexchar(page_83[4+i] >> 4); + serial[j++] = hexchar(page_83[4+i]); } serial[max_len-1] = 0; strncpy(serial_short, serial, max_len-1); @@ -548,11 +558,11 @@ static int check_fill_0x83_prespc3(struct scsi_id_device *dev_scsi, /* Get device identification VPD page */ static int do_scsi_page83_inquiry(struct scsi_id_device *dev_scsi, int fd, - char *serial, char *serial_short, int len, + char *serial, char *serial_short, size_t len, char *unit_serial_number, char *wwn, char *wwn_vendor_extension, char *tgpt_group) { int retval; - unsigned char page_83[SCSI_INQ_BUFF_LEN]; + uint8_t page_83[SCSI_INQ_BUFF_LEN]; /* also pick up the page 80 serial number */ do_scsi_page80_inquiry(dev_scsi, fd, NULL, unit_serial_number, MAX_SERIAL_LEN); @@ -605,12 +615,25 @@ static int do_scsi_page83_inquiry(struct scsi_id_device *dev_scsi, int fd, * Search for a match in the prioritized id_search_list - since WWN ids * come first we can pick up the WWN in check_fill_0x83_id(). */ + + /* Cap reported page length to buffer size in case of malformed responses. + * Below, j can equal page_end, and at that point page_83[j + 3] (the first descriptor data byte) + * must still be readable before the inner bounds check, so page_end + 4 < SCSI_INQ_BUFF_LEN + * requires page_end <= SCSI_INQ_BUFF_LEN - 5. */ + unsigned page_end = MIN(((unsigned)page_83[2] << 8) + (unsigned)page_83[3] + 3U, + SCSI_INQ_BUFF_LEN - 5U); + FOREACH_ELEMENT(search_value, id_search_list) { /* * Examine each descriptor returned. There is normally only * one or a small number of descriptors. */ - for (unsigned j = 4; j <= ((unsigned)page_83[2] << 8) + (unsigned)page_83[3] + 3; j += page_83[j + 3] + 4) { + for (unsigned j = 4; j <= page_end; j += page_83[j + 3] + 4) { + /* Ensure the full descriptor fits within the buffer, including + * fixed-offset accesses up to page_83[7] in the TGTGROUP path + * of check_fill_0x83_id(), so require at least 8 bytes from j */ + if (j + MAX(4U + (unsigned)page_83[j + 3], 8U) > SCSI_INQ_BUFF_LEN) + break; retval = check_fill_0x83_id(dev_scsi, page_83 + j, search_value, serial, serial_short, len, @@ -633,10 +656,10 @@ static int do_scsi_page83_inquiry(struct scsi_id_device *dev_scsi, int fd, * conformant to the SCSI-2 format. */ static int do_scsi_page83_prespc3_inquiry(struct scsi_id_device *dev_scsi, int fd, - char *serial, char *serial_short, int len) { + char *serial, char *serial_short, size_t len) { int retval; - int i, j; - unsigned char page_83[SCSI_INQ_BUFF_LEN]; + size_t i, j; + uint8_t page_83[SCSI_INQ_BUFF_LEN]; memzero(page_83, SCSI_INQ_BUFF_LEN); retval = scsi_inquiry(dev_scsi, fd, 1, PAGE_83, page_83, SCSI_INQ_BUFF_LEN); @@ -672,7 +695,7 @@ static int do_scsi_page83_prespc3_inquiry(struct scsi_id_device *dev_scsi, int f if (page_83[6] == 0) return 2; - serial[0] = hex_str[SCSI_ID_NAA]; + serial[0] = hexchar(SCSI_ID_NAA); /* * The first four bytes contain data, not a descriptor. */ @@ -683,9 +706,11 @@ static int do_scsi_page83_prespc3_inquiry(struct scsi_id_device *dev_scsi, int f * using two bytes of ASCII for each byte * in the page_83. */ - while (i < (page_83[3]+4)) { - serial[j++] = hex_str[(page_83[i] & 0xf0) >> 4]; - serial[j++] = hex_str[page_83[i] & 0x0f]; + /* Cap reported page length to buffer size in case of malformed responses */ + size_t page_len = MIN((size_t)page_83[3] + 4, SCSI_INQ_BUFF_LEN); + while (i < page_len && j + 2 < len) { + serial[j++] = hexchar(page_83[i] >> 4); + serial[j++] = hexchar(page_83[i]); i++; } return 0; @@ -693,12 +718,11 @@ static int do_scsi_page83_prespc3_inquiry(struct scsi_id_device *dev_scsi, int f /* Get unit serial number VPD page */ static int do_scsi_page80_inquiry(struct scsi_id_device *dev_scsi, int fd, - char *serial, char *serial_short, int max_len) { + char *serial, char *serial_short, size_t max_len) { int retval; int ser_ind; - int i; - int len; - unsigned char buf[SCSI_INQ_BUFF_LEN]; + size_t page_len; + uint8_t buf[SCSI_INQ_BUFF_LEN]; memzero(buf, SCSI_INQ_BUFF_LEN); retval = scsi_inquiry(dev_scsi, fd, 1, PAGE_80, buf, SCSI_INQ_BUFF_LEN); @@ -710,58 +734,52 @@ static int do_scsi_page80_inquiry(struct scsi_id_device *dev_scsi, int fd, return 1; } - len = 1 + VENDOR_LENGTH + MODEL_LENGTH + buf[3]; - if (max_len < len) { - log_debug("%s: length %d too short - need %d", - dev_scsi->kernel, max_len, len); + page_len = 1 + VENDOR_LENGTH + MODEL_LENGTH + buf[3]; + if (max_len < page_len) { + log_debug("%s: length %zu too short - need %zu", + dev_scsi->kernel, max_len, page_len); return 1; } /* * Prepend 'S' to avoid unlikely collision with page 0x83 vendor * specific type where we prepend '0' + vendor + model. */ - len = buf[3]; + /* Cap reported page length to buffer size in case of malformed responses */ + page_len = MIN((size_t)buf[3], SCSI_INQ_BUFF_LEN - 4); if (serial) { serial[0] = 'S'; ser_ind = append_vendor_model(dev_scsi, serial + 1); if (ser_ind < 0) return 1; ser_ind++; /* for the leading 'S' */ - for (i = 4; i < len + 4; i++, ser_ind++) + for (size_t i = 4; i < page_len + 4; i++, ser_ind++) serial[ser_ind] = buf[i]; } if (serial_short) { - memcpy(serial_short, buf + 4, len); - serial_short[len] = '\0'; + memcpy(serial_short, buf + 4, page_len); + serial_short[page_len] = '\0'; } return 0; } int scsi_std_inquiry(struct scsi_id_device *dev_scsi, const char *devname) { - int fd; - unsigned char buf[SCSI_INQ_BUFF_LEN]; + unsigned char buf[SCSI_INQ_BUFF_LEN] = {}; struct stat statbuf; - int err = 0; + int r; - fd = open(devname, O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY); - if (fd < 0) { - log_debug_errno(errno, "scsi_id: cannot open %s: %m", devname); - return 1; - } + _cleanup_close_ int fd = open(devname, O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY); + if (fd < 0) + return log_debug_errno(errno, "scsi_id: cannot open %s: %m", devname); + + if (fstat(fd, &statbuf) < 0) + return log_debug_errno(errno, "scsi_id: cannot stat %s: %m", devname); - if (fstat(fd, &statbuf) < 0) { - log_debug_errno(errno, "scsi_id: cannot stat %s: %m", devname); - err = 2; - goto out; - } format_devnum(statbuf.st_rdev, dev_scsi->kernel); - memzero(buf, SCSI_INQ_BUFF_LEN); - err = scsi_inquiry(dev_scsi, fd, 0, 0, buf, SCSI_INQ_BUFF_LEN); - if (err < 0) - goto out; + r = scsi_inquiry(dev_scsi, fd, 0, 0, buf, SCSI_INQ_BUFF_LEN); + if (r < 0) + return r; - err = 0; memcpy(dev_scsi->vendor, buf + 8, 8); dev_scsi->vendor[8] = '\0'; memcpy(dev_scsi->model, buf + 16, 16); @@ -769,18 +787,15 @@ int scsi_std_inquiry(struct scsi_id_device *dev_scsi, const char *devname) { memcpy(dev_scsi->revision, buf + 32, 4); dev_scsi->revision[4] = '\0'; dev_scsi->type = buf[0] & 0x1f; - -out: - close(fd); - return err; + return 0; } int scsi_get_serial(struct scsi_id_device *dev_scsi, const char *devname, - int page_code, int len) { - unsigned char page0[SCSI_INQ_BUFF_LEN]; + int page_code, size_t len) { + uint8_t page0[SCSI_INQ_BUFF_LEN]; int fd = -EBADF; int cnt; - int ind; + size_t ind; int retval; memzero(dev_scsi->serial, len); @@ -855,7 +870,10 @@ int scsi_get_serial(struct scsi_id_device *dev_scsi, const char *devname, goto completed; } - for (ind = 4; ind <= page0[3] + 3; ind++) + /* Cap reported page length to buffer size in case of malformed responses */ + size_t page0_end = MIN((size_t)page0[3] + 3, SCSI_INQ_BUFF_LEN - 1); + + for (ind = 4; ind <= page0_end; ind++) if (page0[ind] == PAGE_83) if (!do_scsi_page83_inquiry(dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len, dev_scsi->unit_serial_number, dev_scsi->wwn, dev_scsi->wwn_vendor_extension, dev_scsi->tgpt_group)) { @@ -866,7 +884,7 @@ int scsi_get_serial(struct scsi_id_device *dev_scsi, const char *devname, goto completed; } - for (ind = 4; ind <= page0[3] + 3; ind++) + for (ind = 4; ind <= page0_end; ind++) if (page0[ind] == PAGE_80) if (!do_scsi_page80_inquiry(dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len)) { diff --git a/src/udev/test-udev-rule-runner.c b/src/udev/test-udev-rule-runner.c index 34755b1f8b3a7..1acbe2f0909c8 100644 --- a/src/udev/test-udev-rule-runner.c +++ b/src/udev/test-udev-rule-runner.c @@ -103,7 +103,7 @@ static int run(int argc, char *argv[]) { /* Let's make sure the test runs with selinux assumed disabled. */ #if HAVE_SELINUX - if (dlopen_libselinux() >= 0) + if (dlopen_libselinux(LOG_DEBUG) >= 0) sym_fini_selinuxmnt(); #endif mac_selinux_retest(); diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index 6ec02674b9fd6..ca40b62d782b9 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -20,6 +20,7 @@ #include "blockdev-util.h" #include "device-util.h" #include "devnum-util.h" +#include "efi-api.h" #include "efi-loader.h" #include "errno-util.h" #include "fd-util.h" @@ -421,6 +422,78 @@ static int read_loopback_backing_inode( return 0; } +static int probe_gpt_boot_disk_needs_loop(UdevEvent *event, int fd) { + sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + int r; + + /* Probe the GPT sector size. For devices booted via El Torito, the GPT may use a different + * sector size than the device (e.g. a 512-byte GPT on a device with 2048-byte blocks). If + * there's a mismatch, check if this is the boot disk by comparing GPT partition UUIDs with the + * ESP/XBOOTLDR UUID exported by the boot loader. If it matches, set a property so that udev + * rules can set up a loop device with the correct sector size — the kernel can't parse the + * partition table itself in this case. + * + * Even if the sector sizes match, if the device does not support partition scanning (e.g. some + * CD-ROM drives), the kernel still can't parse the partition table. In that case, if the disk + * contains the ESP we booted from, we still need a loop device to expose the partitions. */ + + _cleanup_free_ void *entries = NULL; + uint32_t n_entries, entry_size; + ssize_t gpt_ssz = gpt_probe(fd, /* ret_header= */ NULL, &entries, &n_entries, &entry_size); + if (gpt_ssz < 0) + return log_device_debug_errno(dev, gpt_ssz, "Failed to probe GPT: %m"); + if (gpt_ssz == 0) + return 0; + + uint32_t device_ssz; + r = blockdev_get_sector_size(fd, &device_ssz); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to get device sector size: %m"); + + bool sector_size_mismatch = (uint32_t) gpt_ssz != device_ssz; + + if (!sector_size_mismatch) { + r = blockdev_partscan_enabled(dev); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to check if partition scanning is enabled: %m"); + if (r > 0) + return 0; + } + + if (sector_size_mismatch) + log_device_debug(dev, "GPT sector size %zi does not match device sector size %" PRIu32 ".", + gpt_ssz, device_ssz); + else + log_device_debug(dev, "Device does not support partition scanning."); + + sd_id128_t loader_part_uuid; + r = efi_loader_get_device_part_uuid(&loader_part_uuid); + if (r < 0) { + if (r != -ENOENT && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return log_device_debug_errno(dev, r, "Failed to get loader partition UUID: %m"); + + return 0; + } + + for (uint32_t i = 0; i < n_entries; i++) { + const GptPartitionEntry *entry = (const GptPartitionEntry *) ((const uint8_t *) entries + (size_t) entry_size * i); + + sd_id128_t type = efi_guid_to_id128(entry->partition_type_guid); + if (!sd_id128_in_set(type, SD_GPT_ESP, SD_GPT_XBOOTLDR)) + continue; + + if (!sd_id128_equal(efi_guid_to_id128(entry->unique_partition_guid), loader_part_uuid)) + continue; + + log_device_debug(dev, "Found boot partition (ESP/XBOOTLDR) on disk where kernel cannot scan partitions."); + udev_builtin_add_property(event, "ID_PART_GPT_AUTO_ROOT_DISK_NEEDS_LOOP", "1"); + udev_builtin_add_propertyf(event, "ID_PART_GPT_SECTOR_SIZE", "%zi", gpt_ssz); + return 1; /* boot disk needs loop device */ + } + + return 0; +} + static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); const char *devnode, *root_partition = NULL, *data, *name; @@ -440,7 +513,7 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { {} }; - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) return log_device_debug_errno(dev, r, "blkid not available: %m"); @@ -476,17 +549,6 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { } } - sym_blkid_probe_set_superblocks_flags(pr, - BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID | - BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE | -#ifdef BLKID_SUBLKS_FSINFO /* since util-linux 2.39 */ - BLKID_SUBLKS_FSINFO | -#endif - BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION); - - if (noraid) - sym_blkid_probe_filter_superblocks_usage(pr, BLKID_FLTR_NOTIN, BLKID_USAGE_RAID); - r = sd_device_get_devname(dev, &devnode); if (r < 0) return log_device_debug_errno(dev, r, "Failed to get device name: %m"); @@ -499,6 +561,23 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { return ignore ? 0 : fd; } + if (offset == 0) { + r = probe_gpt_boot_disk_needs_loop(event, fd); + if (r > 0) + return 0; + } + + sym_blkid_probe_set_superblocks_flags(pr, + BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID | + BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE | +#ifdef BLKID_SUBLKS_FSINFO /* since util-linux 2.39 */ + BLKID_SUBLKS_FSINFO | +#endif + BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION); + + if (noraid) + sym_blkid_probe_filter_superblocks_usage(pr, BLKID_FLTR_NOTIN, BLKID_USAGE_RAID); + errno = 0; r = sym_blkid_probe_set_device(pr, fd, offset, 0); if (r < 0) diff --git a/src/udev/udev-builtin-dissect_image.c b/src/udev/udev-builtin-dissect_image.c index 8b706a7f9a32f..0fd58ef2f5606 100644 --- a/src/udev/udev-builtin-dissect_image.c +++ b/src/udev/udev-builtin-dissect_image.c @@ -192,7 +192,7 @@ static int verb_probe(UdevEvent *event, sd_device *dev) { (void) image_policy_to_string(image_policy, /* simplify= */ false, &a); (void) image_policy_to_string(image_policy_mangled, /* simplify= */ false, &b); - log_device_debug_errno(dev, ERFKILL, "Couldn't dissect block device with regular policy '%s', retrying with policy where root/usr are set to ignore '%s'.", a, b); + log_device_debug_errno(dev, SYNTHETIC_ERRNO(ERFKILL), "Couldn't dissect block device with regular policy '%s', retrying with policy where root/usr are set to ignore '%s'.", a, b); } r = dissect_loop_device( diff --git a/src/udev/udev-builtin-keyboard.c b/src/udev/udev-builtin-keyboard.c index 5ab40a35526d3..3ced8ad91ca04 100644 --- a/src/udev/udev-builtin-keyboard.c +++ b/src/udev/udev-builtin-keyboard.c @@ -90,6 +90,8 @@ static const char* parse_token(const char *current, int32_t *val_out) { char *next; int32_t val; + assert(val_out); + if (!current) return NULL; diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c index d93849f332603..4e962d54dec7e 100644 --- a/src/udev/udev-builtin-net_id.c +++ b/src/udev/udev-builtin-net_id.c @@ -22,6 +22,7 @@ #include "device-private.h" #include "device-util.h" #include "dirent-util.h" +#include "escape.h" #include "ether-addr-util.h" #include "fd-util.h" #include "fileio.h" @@ -31,10 +32,17 @@ #include "stdio-util.h" #include "string-util.h" #include "udev-builtin.h" +#include "utf8.h" #define ONBOARD_14BIT_INDEX_MAX ((1U << 14) - 1) #define ONBOARD_16BIT_INDEX_MAX ((1U << 16) - 1) +static int log_invalid_device_attr(sd_device *dev, const char *attr, const char *value) { + _cleanup_free_ char *escaped = cescape(value); + return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), + "Invalid %s value '%s'.", attr, strnull(escaped)); +} + static int device_get_parent_skip_virtio(sd_device *dev, sd_device **ret) { int r; @@ -193,6 +201,9 @@ static int get_port_specifier(sd_device *dev, char **ret) { } } + if (!utf8_is_valid(phys_port_name) || string_has_cc(phys_port_name, /* ok= */ NULL)) + return log_invalid_device_attr(dev, "phys_port_name", phys_port_name); + /* Otherwise, use phys_port_name as is. */ buf = strjoin("n", phys_port_name); if (!buf) @@ -297,6 +308,9 @@ static int names_pci_onboard_label(UdevEvent *event, sd_device *pci_dev, const c if (r < 0) return log_device_debug_errno(pci_dev, r, "Failed to get PCI onboard label: %m"); + if (!utf8_is_valid(label) || string_has_cc(label, /* ok= */ NULL)) + return log_invalid_device_attr(dev, "label", label); + char str[ALTIFNAMSIZ]; if (snprintf_ok(str, sizeof str, "%s%s", naming_scheme_has(NAMING_LABEL_NOPREFIX) ? "" : prefix, @@ -710,8 +724,7 @@ static int names_vio(UdevEvent *event, const char *prefix) { "VIO bus ID and slot ID have invalid length: %s", s); if (!in_charset(s, HEXDIGITS)) - return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), - "VIO bus ID and slot ID contain invalid characters: %s", s); + return log_invalid_device_attr(dev, "VIO bus ID and slot ID", s); /* Parse only slot ID (the last 4 hexdigits). */ r = safe_atou_full(s + 4, 16, &slotid); @@ -767,8 +780,7 @@ static int names_platform(UdevEvent *event, const char *prefix) { return -EOPNOTSUPP; if (!in_charset(vendor, validchars)) - return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ENOENT), - "Platform vendor contains invalid characters: %s", vendor); + return log_invalid_device_attr(dev, "platform vendor", vendor); ascii_strlower(vendor); @@ -1247,6 +1259,8 @@ static int names_netdevsim(UdevEvent *event, const char *prefix) { if (isempty(phys_port_name)) return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EOPNOTSUPP), "The 'phys_port_name' attribute is empty."); + if (!utf8_is_valid(phys_port_name) || string_has_cc(phys_port_name, /* ok= */ NULL)) + return log_invalid_device_attr(dev, "phys_port_name", phys_port_name); char str[ALTIFNAMSIZ]; if (snprintf_ok(str, sizeof str, "%si%un%s", prefix, addr, phys_port_name)) diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c index 4db20e4a13f9c..a252ec99dd79b 100644 --- a/src/udev/udev-builtin-path_id.c +++ b/src/udev/udev-builtin-path_id.c @@ -398,6 +398,8 @@ static sd_device* handle_scsi_hyperv(sd_device *parent, char **path, size_t guid static sd_device* handle_scsi(sd_device *parent, char **path, char **compat_path, bool *supported_parent) { const char *id, *name; + assert(supported_parent); + if (device_is_devtype(parent, "scsi_device") <= 0) return parent; @@ -454,6 +456,8 @@ static sd_device* handle_cciss(sd_device *parent, char **path) { static void handle_scsi_tape(sd_device *dev, char **path) { const char *name; + assert(path); + /* must be the last device in the syspath */ if (*path) return; @@ -654,7 +658,7 @@ static void add_id_tag(UdevEvent *event, const char *path) { size_t i = 0; /* compose valid udev tag name */ - for (const char *p = path; *p; p++) { + for (const char *p = path; *p && i < sizeof(tag) - 1; p++) { if (ascii_isdigit(*p) || ascii_isalpha(*p) || *p == '-') { diff --git a/src/udev/udev-builtin-tpm2_id.c b/src/udev/udev-builtin-tpm2_id.c new file mode 100644 index 0000000000000..6edf618e11112 --- /dev/null +++ b/src/udev/udev-builtin-tpm2_id.c @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "device-util.h" +#include "string-util.h" +#include "tpm2-util.h" +#include "udev-builtin.h" + +static int builtin_tpm2_id(UdevEvent *event, int argc, char *argv[]) { + sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + int r; + + if (argc != 2 || !streq(argv[1], "identify")) + return log_device_error_errno( + dev, SYNTHETIC_ERRNO(EINVAL), "%s: expected: identify", argv[0]); + + const char *dn; + r = sd_device_get_devname(dev, &dn); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to get device node for device: %m"); + + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + r = tpm2_context_new(dn, &c); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to open device node '%s': %m", dn); + + Tpm2VendorInfo info; + r = tpm2_get_vendor_info(c, &info); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to acquire TPM2 vendor information: %m"); + + if (!isempty(info.manufacturer)) { + r = udev_builtin_add_property(event, "ID_TPM2_MANUFACTURER", info.manufacturer); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to set field: %m"); + } + + if (!isempty(info.vendor_string)) { + r = udev_builtin_add_property(event, "ID_TPM2_VENDOR_STRING", info.vendor_string); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to set field: %m"); + } + + _cleanup_free_ char *m = NULL; + r = tpm2_vendor_info_to_modalias(&info, &m); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to get modalias string for TPM2 device: %m"); + + r = udev_builtin_add_property(event, "ID_TPM2_MODALIAS", m); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to set field: %m"); + + return 0; +} + +const UdevBuiltin udev_builtin_tpm2_id = { + .name = "tpm2_id", + .cmd = builtin_tpm2_id, + .help = "Identify TPM2 chips", + .run_once = true, +}; diff --git a/src/udev/udev-builtin-usb_id.c b/src/udev/udev-builtin-usb_id.c index 80597ea89ee25..cfbea9d9819dc 100644 --- a/src/udev/udev-builtin-usb_id.c +++ b/src/udev/udev-builtin-usb_id.c @@ -22,6 +22,9 @@ static void set_usb_iftype(char *to, int if_class_num, size_t len) { const char *type = "generic"; + assert(to); + assert(len > 0); + switch (if_class_num) { case 1: type = "audio"; @@ -71,6 +74,8 @@ static int set_usb_mass_storage_ifsubtype(char *to, const char *from, size_t len int type_num = 0; const char *type = "generic"; + assert(to); + if (safe_atoi(from, &type_num) >= 0) switch (type_num) { case 1: /* RBC devices */ @@ -98,6 +103,8 @@ static void set_scsi_type(char *to, const char *from, size_t len) { unsigned type_num; const char *type = "generic"; + assert(to); + if (safe_atou(from, &type_num) >= 0) switch (type_num) { case 0: @@ -143,6 +150,10 @@ static int dev_if_packed_info(sd_device *dev, char *ifs_str, size_t len) { uint8_t iInterface; } _packed_; + assert(dev); + assert(ifs_str); + assert(len >= 2); + r = sd_device_get_syspath(dev, &syspath); if (r < 0) return r; @@ -168,7 +179,7 @@ static int dev_if_packed_info(sd_device *dev, char *ifs_str, size_t len) { desc = (struct usb_interface_descriptor *) (buf + pos); if (desc->bLength < 3) break; - if (desc->bLength > size - sizeof(struct usb_interface_descriptor)) + if (desc->bLength > (size_t) size - pos) return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EIO), "Corrupt data read from \"%s\"", filename); pos += desc->bLength; diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c index 5f559f5c9a10c..f3c8936cad810 100644 --- a/src/udev/udev-builtin.c +++ b/src/udev/udev-builtin.c @@ -29,6 +29,9 @@ static const UdevBuiltin *const builtins[_UDEV_BUILTIN_MAX] = { [UDEV_BUILTIN_NET_ID] = &udev_builtin_net_id, [UDEV_BUILTIN_NET_LINK] = &udev_builtin_net_setup_link, [UDEV_BUILTIN_PATH_ID] = &udev_builtin_path_id, +#if HAVE_TPM2 + [UDEV_BUILTIN_TPM2_ID] = &udev_builtin_tpm2_id, +#endif #if HAVE_ACL [UDEV_BUILTIN_UACCESS] = &udev_builtin_uaccess, #endif @@ -136,13 +139,20 @@ int udev_builtin_add_property(UdevEvent *event, const char *key, const char *val assert(key); + val = empty_to_null(val); + r = device_add_property(dev, key, val); if (r < 0) - return log_device_debug_errno(dev, r, "Failed to add property '%s%s%s'", + return log_device_debug_errno(dev, r, "Failed to %s property '%s%s%s'", + val ? "add" : "remove", key, val ? "=" : "", strempty(val)); - if (event->event_mode == EVENT_UDEVADM_TEST_BUILTIN) - printf("%s=%s\n", key, strempty(val)); + if (event->event_mode == EVENT_UDEVADM_TEST_BUILTIN) { + if (val) + printf("%s=%s\n", key, val); + else + printf("%s (removed)\n", key); + } return 0; } diff --git a/src/udev/udev-builtin.h b/src/udev/udev-builtin.h index 3e830c77ea2b0..504c4108c8055 100644 --- a/src/udev/udev-builtin.h +++ b/src/udev/udev-builtin.h @@ -43,6 +43,9 @@ extern const UdevBuiltin udev_builtin_net_driver; extern const UdevBuiltin udev_builtin_net_id; extern const UdevBuiltin udev_builtin_net_setup_link; extern const UdevBuiltin udev_builtin_path_id; +#if HAVE_TPM2 +extern const UdevBuiltin udev_builtin_tpm2_id; +#endif #if HAVE_ACL extern const UdevBuiltin udev_builtin_uaccess; #endif diff --git a/src/udev/udev-def.h b/src/udev/udev-def.h index 836dd7045460a..d5e02e50b623f 100644 --- a/src/udev/udev-def.h +++ b/src/udev/udev-def.h @@ -51,6 +51,9 @@ typedef enum UdevBuiltinCommand { UDEV_BUILTIN_NET_ID, UDEV_BUILTIN_NET_LINK, UDEV_BUILTIN_PATH_ID, +#if HAVE_TPM2 + UDEV_BUILTIN_TPM2_ID, +#endif #if HAVE_ACL UDEV_BUILTIN_UACCESS, #endif diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index f0e4fbccfd791..70238b9b54673 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -1359,6 +1359,7 @@ static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOper assert(line); assert(*line); assert(ret_key); + assert(ret_attr); assert(ret_op); assert(ret_value); assert(ret_is_case_insensitive); @@ -1840,7 +1841,7 @@ int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing ConfFile **files = NULL; size_t n_files = 0; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); r = conf_files_list_strv_full(".rules", /* root= */ NULL, CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED, (const char* const*) directories, &files, &n_files); diff --git a/src/udev/udev-varlink.c b/src/udev/udev-varlink.c index 78bcc11fd4f4b..355e5a7860530 100644 --- a/src/udev/udev-varlink.c +++ b/src/udev/udev-varlink.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "bus-polkit.h" #include "fd-util.h" #include "json-util.h" #include "log.h" @@ -18,7 +19,9 @@ static int vl_method_reload(sd_varlink *link, sd_json_variant *parameters, sd_va assert(link); - r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + /* Currently, udevd does not support polkit, but the varlink IDL says that io.systemd.service.Reload + * optionally takes the polkit field. Let's silently ignore the field. */ + r = sd_varlink_dispatch(link, parameters, dispatch_table_polkit_only, /* userdata= */ NULL); if (r != 0) return r; diff --git a/src/udev/udev-worker.c b/src/udev/udev-worker.c index 67617203d765f..a0c95d2ce330d 100644 --- a/src/udev/udev-worker.c +++ b/src/udev/udev-worker.c @@ -157,10 +157,8 @@ static int worker_mark_block_device_read_only(sd_device *dev) { return 0; r = device_in_subsystem(dev, "block"); - if (r < 0) + if (r <= 0) return r; - if (r == 0) - return 0; /* Exclude synthetic devices for now, this is supposed to be a safety feature to avoid modification * of physical devices, and what sits on top of those doesn't really matter if we don't allow the diff --git a/src/udev/udevadm-cat.c b/src/udev/udevadm-cat.c index c77e76d053809..9d94f5a86c652 100644 --- a/src/udev/udevadm-cat.c +++ b/src/udev/udevadm-cat.c @@ -92,7 +92,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int cat_main(int argc, char *argv[], void *userdata) { +int verb_cat_main(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = parse_argv(argc, argv); @@ -105,7 +105,7 @@ int cat_main(int argc, char *argv[], void *userdata) { ConfFile **files = NULL; size_t n_files = 0; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); r = search_rules_files(strv_skip(argv, optind), arg_root, &files, &n_files); if (r < 0) diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c index 492b00f222b0c..964f721731ceb 100644 --- a/src/udev/udevadm-control.c +++ b/src/udev/udevadm-control.c @@ -331,7 +331,7 @@ static int send_control_commands(void) { return 0; } -int control_main(int argc, char *argv[], void *userdata) { +int verb_control_main(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; if (running_in_chroot() > 0) { diff --git a/src/udev/udevadm-hwdb.c b/src/udev/udevadm-hwdb.c index b84ebea162f73..5810efefd8ce2 100644 --- a/src/udev/udevadm-hwdb.c +++ b/src/udev/udevadm-hwdb.c @@ -78,7 +78,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int hwdb_main(int argc, char *argv[], void *userdata) { +int verb_hwdb_main(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = parse_argv(argc, argv); diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c index 1598d8a3210bf..62d7dce4217de 100644 --- a/src/udev/udevadm-info.c +++ b/src/udev/udevadm-info.c @@ -1275,7 +1275,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int info_main(int argc, char *argv[], void *userdata) { +int verb_info_main(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = parse_argv(argc, argv); diff --git a/src/udev/udevadm-lock.c b/src/udev/udevadm-lock.c index 17b9e2d3e9bcf..483b64973d401 100644 --- a/src/udev/udevadm-lock.c +++ b/src/udev/udevadm-lock.c @@ -228,7 +228,7 @@ static int lock_device( return TAKE_FD(fd); } -int lock_main(int argc, char *argv[], void *userdata) { +int verb_lock_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_fdset_free_ FDSet *fds = NULL; _cleanup_free_ dev_t *devnos = NULL; size_t n_devnos = 0; diff --git a/src/udev/udevadm-monitor.c b/src/udev/udevadm-monitor.c index e729a99e95b36..6f33cc3710cca 100644 --- a/src/udev/udevadm-monitor.c +++ b/src/udev/udevadm-monitor.c @@ -65,6 +65,8 @@ static int setup_monitor(MonitorNetlinkGroup sender, sd_event *event, sd_device_ const char *subsystem, *devtype, *tag; int r; + assert(ret); + r = device_monitor_new_full(&monitor, sender, -EBADF); if (r < 0) return log_error_errno(r, "Failed to create netlink socket: %m"); @@ -187,7 +189,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int monitor_main(int argc, char *argv[], void *userdata) { +int verb_monitor_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *kernel_monitor = NULL, *udev_monitor = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; int r; diff --git a/src/udev/udevadm-settle.c b/src/udev/udevadm-settle.c index 19128ec80e77a..b71759dc818e6 100644 --- a/src/udev/udevadm-settle.c +++ b/src/udev/udevadm-settle.c @@ -184,7 +184,7 @@ static int on_inotify(sd_event_source *s, const struct inotify_event *event, voi return 0; } -int settle_main(int argc, char *argv[], void *userdata) { +int verb_settle_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_event_unrefp) sd_event *event = NULL; int r; diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c index a1ad9cb320384..f17df9a7d51a2 100644 --- a/src/udev/udevadm-test-builtin.c +++ b/src/udev/udevadm-test-builtin.c @@ -18,9 +18,9 @@ static int help(void) { printf("%s test-builtin [OPTIONS] COMMAND DEVPATH\n\n" "Test a built-in command.\n\n" " -h --help Print this message\n" - " -V --version Print version of the program\n\n" + " -V --version Print version of the program\n" " -a --action=ACTION|help Set action string\n" - "Commands:\n", + "\nCommands:\n", program_invocation_short_name); udev_builtin_list(); @@ -63,7 +63,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int builtin_main(int argc, char *argv[], void *userdata) { +int verb_builtin_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(udev_event_unrefp) UdevEvent *event = NULL; _cleanup_(sd_device_unrefp) sd_device *dev = NULL; UdevBuiltinCommand cmd; diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index 06d9d2cd16f28..f3ac39717e946 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -129,7 +129,7 @@ static void maybe_insert_empty_line(void) { fputs("\n", stderr); } -int test_main(int argc, char *argv[], void *userdata) { +int verb_test_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(udev_rules_freep) UdevRules *rules = NULL; _cleanup_(udev_event_unrefp) UdevEvent *event = NULL; _cleanup_(sd_device_unrefp) sd_device *dev = NULL; diff --git a/src/udev/udevadm-trigger.c b/src/udev/udevadm-trigger.c index 10f8a15fb17aa..afa6a84262084 100644 --- a/src/udev/udevadm-trigger.c +++ b/src/udev/udevadm-trigger.c @@ -534,7 +534,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int trigger_main(int argc, char *argv[], void *userdata) { +int verb_trigger_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *m = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; diff --git a/src/udev/udevadm-util.c b/src/udev/udevadm-util.c index 14b508007f507..c30af47ff7c73 100644 --- a/src/udev/udevadm-util.c +++ b/src/udev/udevadm-util.c @@ -302,7 +302,7 @@ static int search_rules_file(const char *s, const char *root, ConfFile ***files, ConfFile **f = NULL; size_t n = 0; - CLEANUP_ARRAY(f, n, conf_file_free_many); + CLEANUP_ARRAY(f, n, conf_file_free_array); r = conf_files_list_strv_full(".rules", root, CONF_FILES_REGULAR | CONF_FILES_WARN, (const char* const*) STRV_MAKE_CONST(s), &f, &n); if (r < 0) @@ -311,7 +311,7 @@ static int search_rules_file(const char *s, const char *root, ConfFile ***files, if (!GREEDY_REALLOC_APPEND(*files, *n_files, f, n)) return log_oom(); - f = mfree(f); /* The array elements are owned by 'files'. So, conf_file_free_many() must not be called. */ + f = mfree(f); /* The array elements are owned by 'files'. So, conf_file_free_array() must not be called. */ n = 0; return 0; } @@ -321,7 +321,7 @@ int search_rules_files(char * const *a, const char *root, ConfFile ***ret_files, size_t n_files = 0; int r; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); assert(ret_files); assert(ret_n_files); diff --git a/src/udev/udevadm-verify.c b/src/udev/udevadm-verify.c index 7918ea2f7e7b2..6af7f06ab05fe 100644 --- a/src/udev/udevadm-verify.c +++ b/src/udev/udevadm-verify.c @@ -156,7 +156,7 @@ static int verify_rules(UdevRules *rules, ConfFile * const *files, size_t n_file return ret; } -int verify_main(int argc, char *argv[], void *userdata) { +int verb_verify_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(udev_rules_freep) UdevRules *rules = NULL; int r; @@ -171,7 +171,7 @@ int verify_main(int argc, char *argv[], void *userdata) { ConfFile **files = NULL; size_t n_files = 0; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); r = search_rules_files(strv_skip(argv, optind), arg_root, &files, &n_files); if (r < 0) diff --git a/src/udev/udevadm-wait.c b/src/udev/udevadm-wait.c index 70b14a7e91e91..0e285fc36b247 100644 --- a/src/udev/udevadm-wait.c +++ b/src/udev/udevadm-wait.c @@ -376,7 +376,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; /* work to do */ } -int wait_main(int argc, char *argv[], void *userdata) { +int verb_wait_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *udev_monitor = NULL, *kernel_monitor = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; int r; diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index 3a91d14ef8e93..70ff213cb9999 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -91,30 +91,30 @@ int print_version(void) { return 0; } -static int version_main(int argc, char *argv[], void *userdata) { +static int verb_version_main(int argc, char *argv[], uintptr_t _data, void *userdata) { return print_version(); } -static int help_main(int argc, char *argv[], void *userdata) { +static int verb_help_main(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } static int udevadm_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "cat", VERB_ANY, VERB_ANY, 0, cat_main }, - { "info", VERB_ANY, VERB_ANY, 0, info_main }, - { "trigger", VERB_ANY, VERB_ANY, 0, trigger_main }, - { "settle", VERB_ANY, VERB_ANY, 0, settle_main }, - { "control", VERB_ANY, VERB_ANY, 0, control_main }, - { "monitor", VERB_ANY, VERB_ANY, 0, monitor_main }, - { "hwdb", VERB_ANY, VERB_ANY, 0, hwdb_main }, - { "test", VERB_ANY, VERB_ANY, 0, test_main }, - { "test-builtin", VERB_ANY, VERB_ANY, 0, builtin_main }, - { "wait", VERB_ANY, VERB_ANY, 0, wait_main }, - { "lock", VERB_ANY, VERB_ANY, 0, lock_main }, - { "verify", VERB_ANY, VERB_ANY, 0, verify_main }, - { "version", VERB_ANY, VERB_ANY, 0, version_main }, - { "help", VERB_ANY, VERB_ANY, 0, help_main }, + { "cat", VERB_ANY, VERB_ANY, 0, verb_cat_main }, + { "info", VERB_ANY, VERB_ANY, 0, verb_info_main }, + { "trigger", VERB_ANY, VERB_ANY, 0, verb_trigger_main }, + { "settle", VERB_ANY, VERB_ANY, 0, verb_settle_main }, + { "control", VERB_ANY, VERB_ANY, 0, verb_control_main }, + { "monitor", VERB_ANY, VERB_ANY, 0, verb_monitor_main }, + { "hwdb", VERB_ANY, VERB_ANY, 0, verb_hwdb_main }, + { "test", VERB_ANY, VERB_ANY, 0, verb_test_main }, + { "test-builtin", VERB_ANY, VERB_ANY, 0, verb_builtin_main }, + { "wait", VERB_ANY, VERB_ANY, 0, verb_wait_main }, + { "lock", VERB_ANY, VERB_ANY, 0, verb_lock_main }, + { "verify", VERB_ANY, VERB_ANY, 0, verb_verify_main }, + { "version", VERB_ANY, VERB_ANY, 0, verb_version_main }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help_main }, {} }; diff --git a/src/udev/udevadm.h b/src/udev/udevadm.h index f75fbb416f8db..285a474fba277 100644 --- a/src/udev/udevadm.h +++ b/src/udev/udevadm.h @@ -3,17 +3,17 @@ #include "shared-forward.h" -int cat_main(int argc, char *argv[], void *userdata); -int info_main(int argc, char *argv[], void *userdata); -int trigger_main(int argc, char *argv[], void *userdata); -int settle_main(int argc, char *argv[], void *userdata); -int control_main(int argc, char *argv[], void *userdata); -int monitor_main(int argc, char *argv[], void *userdata); -int hwdb_main(int argc, char *argv[], void *userdata); -int test_main(int argc, char *argv[], void *userdata); -int builtin_main(int argc, char *argv[], void *userdata); -int verify_main(int argc, char *argv[], void *userdata); -int wait_main(int argc, char *argv[], void *userdata); -int lock_main(int argc, char *argv[], void *userdata); +int verb_cat_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_info_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_trigger_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_settle_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_control_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_monitor_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_hwdb_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_test_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_builtin_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_verify_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_wait_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_lock_main(int argc, char *argv[], uintptr_t _data, void *userdata); int print_version(void); diff --git a/src/udev/udevd.c b/src/udev/udevd.c index ab3106334bee7..593eee89b8bde 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -19,6 +19,7 @@ #include "process-util.h" #include "rlimit-util.h" #include "terminal-util.h" +#include "tpm2-util.h" #include "udev-config.h" #include "udev-manager.h" #include "udevd.h" @@ -57,10 +58,11 @@ int run_udevd(int argc, char *argv[]) { return log_error_errno(r, "Failed to create /run/udev: %m"); /* Load some shared libraries before we fork any workers */ - (void) dlopen_libacl(); - (void) dlopen_libblkid(); - (void) dlopen_libkmod(); - (void) dlopen_libmount(); + (void) dlopen_libacl(LOG_DEBUG); + (void) dlopen_libblkid(LOG_DEBUG); + (void) dlopen_libkmod(LOG_DEBUG); + (void) dlopen_libmount(LOG_DEBUG); + (void) dlopen_tpm2(LOG_DEBUG); if (arg_daemonize) { pid_t pid; diff --git a/src/udev/v4l_id/v4l_id.c b/src/udev/v4l_id/v4l_id.c index 1e374c393c347..c5bfa935b2536 100644 --- a/src/udev/v4l_id/v4l_id.c +++ b/src/udev/v4l_id/v4l_id.c @@ -4,7 +4,6 @@ */ #include -#include #include #include #include @@ -12,41 +11,55 @@ #include "build.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" #include "main-func.h" +#include "options.h" +#include "string-util.h" +#include "strv.h" +#include "utf8.h" static const char *arg_device = NULL; +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...] DEVICE"); + help_abstract("Video4Linux device identification."); + help_section("Options:"); + + return table_print_or_warn(options); +} + static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'v' }, - {} - }; - int c; - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - printf("%s [OPTIONS...] DEVICE\n\n" - "Video4Linux device identification.\n\n" - " -h --help Show this help text\n" - " --version Show package version\n", - program_invocation_short_name); - return 0; - case 'v': + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: return version(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - if (!argv[optind]) + char **args = option_parser_get_args(&state); + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "DEVICE argument missing."); + "Need exactly one DEVICE argument."); - arg_device = argv[optind]; + arg_device = args[0]; return 1; } @@ -72,7 +85,8 @@ static int run(int argc, char *argv[]) { int capabilities; printf("ID_V4L_VERSION=2\n"); - printf("ID_V4L_PRODUCT=%s\n", v2cap.card); + if (utf8_is_valid((char *)v2cap.card) && !string_has_cc((char *)v2cap.card, /* ok= */ NULL)) + printf("ID_V4L_PRODUCT=%s\n", v2cap.card); printf("ID_V4L_CAPABILITIES=:"); if (v2cap.capabilities & V4L2_CAP_DEVICE_CAPS) diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index 6f492bc9ba07f..03d475e5e763e 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -1401,7 +1401,7 @@ def make_uki(opts: UkifyConfig) -> None: if opts.hwids is not None: hwids = parse_hwid_dir(Path(opts.hwids)) else: - hwids_dir = Path(f'/tmp/s/usr/lib/systemd/boot/hwids/{opts.efi_arch}') + hwids_dir = Path(f'/usr/lib/systemd/boot/hwids/{opts.efi_arch}') if hwids_dir.is_dir(): print(f'Automatically building .hwids section from {hwids_dir}', file=sys.stderr) hwids = parse_hwid_dir(hwids_dir) diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index c50acca04529d..bc678b8f41988 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -1,16 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" +#include "build.h" #include "chase.h" #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "label-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "string-util.h" @@ -61,65 +63,58 @@ static int save_timestamp(const char *dir, struct timespec *ts) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-update-done", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...]\n\n" - "%5$sMark /etc/ and /var/ as fully updated.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --root=PATH Operate on root directory PATH\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\n%sMark /etc/ and /var/ as fully updated.%s\n" + "\n%sOptions:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_ROOT = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "root", required_argument, NULL, ARG_ROOT }, - {}, - }; - - int r, c; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); + OPTION_COMMON_VERSION: + return version(); + + OPTION_LONG("root", "PATH", "Operate on root directory PATH"): + r = parse_path_argument(arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); return 1; diff --git a/src/update-utmp/update-utmp.c b/src/update-utmp/update-utmp.c index 5a999806bd5d2..875ae9a09bcad 100644 --- a/src/update-utmp/update-utmp.c +++ b/src/update-utmp/update-utmp.c @@ -51,7 +51,7 @@ static int get_startup_monotonic_time(Context *c, usec_t *ret) { return 0; } -static int on_reboot(int argc, char *argv[], void *userdata) { +static int verb_on_reboot(int argc, char *argv[], uintptr_t _data, void *userdata) { Context *c = ASSERT_PTR(userdata); usec_t t = 0, boottime; int r, q = 0; @@ -80,7 +80,7 @@ static int on_reboot(int argc, char *argv[], void *userdata) { return q; } -static int on_shutdown(int argc, char *argv[], void *userdata) { +static int verb_on_shutdown(int argc, char *argv[], uintptr_t _data, void *userdata) { int r, q = 0; /* We started shut-down, so let's write the utmp record and send the audit msg. */ @@ -103,8 +103,8 @@ static int on_shutdown(int argc, char *argv[], void *userdata) { static int run(int argc, char *argv[]) { static const Verb verbs[] = { - { "reboot", 1, 1, 0, on_reboot }, - { "shutdown", 1, 1, 0, on_shutdown }, + { "reboot", 1, 1, 0, verb_on_reboot }, + { "shutdown", 1, 1, 0, verb_on_shutdown }, {} }; diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index 88f738a6061eb..6b4371aa509b1 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -407,7 +407,7 @@ static int table_add_uid_map( return n_added; } -static int display_user(int argc, char *argv[], void *userdata) { +static int verb_display_user(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; bool draw_separator = false; int ret = 0, r; @@ -750,7 +750,7 @@ static int add_unavailable_gid(Table *table, uid_t start, uid_t end) { return 2; } -static int display_group(int argc, char *argv[], void *userdata) { +static int verb_display_group(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; bool draw_separator = false; int ret = 0, r; @@ -951,7 +951,7 @@ static int show_membership(const char *user, const char *group, Table *table) { return 0; } -static int display_memberships(int argc, char *argv[], void *userdata) { +static int verb_display_memberships(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; int ret = 0, r; @@ -1047,7 +1047,7 @@ static int display_memberships(int argc, char *argv[], void *userdata) { return ret; } -static int display_services(int argc, char *argv[], void *userdata) { +static int verb_display_services(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *t = NULL; _cleanup_closedir_ DIR *d = NULL; int r; @@ -1072,13 +1072,9 @@ static int display_services(int argc, char *argv[], void *userdata) { (void) table_set_sort(t, (size_t) 0); FOREACH_DIRENT(de, d, return -errno) { - _cleanup_free_ char *j = NULL, *no = NULL; + _cleanup_free_ char *no = NULL; _cleanup_close_ int fd = -EBADF; - j = path_join("/run/systemd/userdb/", de->d_name); - if (!j) - return log_oom(); - fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); if (fd < 0) return log_error_errno(errno, "Failed to allocate AF_UNIX/SOCK_STREAM socket: %m"); @@ -1114,7 +1110,7 @@ static int display_services(int argc, char *argv[], void *userdata) { return 0; } -static int ssh_authorized_keys(int argc, char *argv[], void *userdata) { +static int verb_ssh_authorized_keys(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(user_record_unrefp) UserRecord *ur = NULL; char **chain_invocation; int r; @@ -1260,7 +1256,7 @@ static int load_credential_one( _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; unsigned line = 0, column = 0; - r = sd_json_parse_file_at(NULL, credential_dir_fd, name, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse_file_at(/* f= */ NULL, credential_dir_fd, name, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse credential '%s' as JSON at %u:%u: %m", name, line, column); @@ -1497,7 +1493,7 @@ static int load_credential_one( return 0; } -static int load_credentials(int argc, char *argv[], void *userdata) { +static int verb_load_credentials(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_close_ int credential_dir_fd = open_credentials_dir(); @@ -1532,7 +1528,7 @@ static int load_credentials(int argc, char *argv[], void *userdata) { return r; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -1590,6 +1586,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -1670,7 +1670,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -1840,7 +1840,7 @@ static int parse_argv(int argc, char *argv[]) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; const char *fn = streq(optarg, "-") ? NULL : optarg; unsigned line = 0; - r = sd_json_parse_file(fn ? NULL : stdin, fn ?: "", SD_JSON_PARSE_SENSITIVE, &v, &line, /* reterr_column= */ NULL); + r = sd_json_parse_file(fn ? NULL : stdin, fn ?: "", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, /* reterr_column= */ NULL); if (r < 0) return log_syntax(/* unit= */ NULL, LOG_ERR, fn ?: "", line, r, "JSON parse failure."); @@ -1872,14 +1872,14 @@ static int parse_argv(int argc, char *argv[]) { static int run(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "user", VERB_ANY, VERB_ANY, VERB_DEFAULT, display_user }, - { "group", VERB_ANY, VERB_ANY, 0, display_group }, - { "users-in-group", VERB_ANY, VERB_ANY, 0, display_memberships }, - { "groups-of-user", VERB_ANY, VERB_ANY, 0, display_memberships }, - { "services", VERB_ANY, 1, 0, display_services }, - { "ssh-authorized-keys", 2, VERB_ANY, 0, ssh_authorized_keys }, - { "load-credentials", VERB_ANY, 1, 0, load_credentials }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "user", VERB_ANY, VERB_ANY, VERB_DEFAULT, verb_display_user }, + { "group", VERB_ANY, VERB_ANY, 0, verb_display_group }, + { "users-in-group", VERB_ANY, VERB_ANY, 0, verb_display_memberships }, + { "groups-of-user", VERB_ANY, VERB_ANY, 0, verb_display_memberships }, + { "services", VERB_ANY, 1, 0, verb_display_services }, + { "ssh-authorized-keys", 2, VERB_ANY, 0, verb_ssh_authorized_keys }, + { "load-credentials", VERB_ANY, 1, 0, verb_load_credentials }, {} }; diff --git a/src/userdb/userdbd-manager.c b/src/userdb/userdbd-manager.c index 8803f3090f963..cf9f2f5c6b8c3 100644 --- a/src/userdb/userdbd-manager.c +++ b/src/userdb/userdbd-manager.c @@ -79,6 +79,8 @@ int manager_new(Manager **ret) { _cleanup_(manager_freep) Manager *m = NULL; int r; + assert(ret); + m = new(Manager, 1); if (!m) return -ENOMEM; diff --git a/src/userdb/userwork.c b/src/userdb/userwork.c index 6abb8795e602d..aa77cde86b353 100644 --- a/src/userdb/userwork.c +++ b/src/userdb/userwork.c @@ -172,7 +172,7 @@ static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *paramete * we are done'; == 0 means 'not processed, caller should process now' */ return r; - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; @@ -313,7 +313,7 @@ static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *paramet if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; @@ -401,7 +401,7 @@ static int vl_method_get_memberships(sd_varlink *link, sd_json_variant *paramete if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; diff --git a/src/validatefs/validatefs.c b/src/validatefs/validatefs.c index 74a784d8ab1f9..44a58387f05b1 100644 --- a/src/validatefs/validatefs.c +++ b/src/validatefs/validatefs.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-device.h" #include "alloc-util.h" @@ -12,11 +10,13 @@ #include "device-util.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" #include "gpt.h" #include "initrd-util.h" #include "log.h" #include "main-func.h" #include "mountpoint-util.h" +#include "options.h" #include "parse-argument.h" #include "path-util.h" #include "pretty-print.h" @@ -32,93 +32,79 @@ STATIC_DESTRUCTOR_REGISTER(arg_target, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); static int help(void) { + _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - _cleanup_free_ char *link = NULL; r = terminal_urlify_man("systemd-validatefs@.service", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] /path/to/mountpoint\n" - "\n%3$sCheck file system validation constraints.%4$s\n\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - " --root=PATH|auto Operate relative to the specified path\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] /path/to/mountpoint\n" + "\n%sCheck file system validation constraints.%s\n" + "\nOptions:\n", program_invocation_short_name, - link, ansi_highlight(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_ROOT, - }; - - int c, r; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version" , no_argument, NULL, ARG_VERSION }, - { "root", required_argument, NULL, ARG_ROOT }, - {} - }; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ROOT: - if (streq(optarg, "auto")) { - arg_root = mfree(arg_root); - - if (in_initrd()) { - arg_root = strdup("/sysroot"); - if (!arg_root) - return log_oom(); - } + OPTION_LONG("root", "PATH|auto", "Operate relative to the specified path"): + if (streq(arg, "auto")) + r = free_and_strdup_warn(&arg_root, in_initrd() ? "/sysroot" : NULL); + else { + if (!path_is_absolute(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--root= argument must be 'auto' or absolute path, got: %s", arg); - break; + r = parse_path_argument(arg, /* suppress_root= */ true, &arg_root); } - - if (!path_is_absolute(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--root= argument must be 'auto' or absolute path, got: %s", optarg); - - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind + 1 != argc) + char **args = option_parser_get_args(&state); + + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s excepts exactly one argument (the mount point).", + "%s expects exactly one argument (the mount point).", program_invocation_short_name); - arg_target = strdup(argv[optind]); + arg_target = strdup(args[0]); if (!arg_target) return log_oom(); if (arg_root && !path_startswith(arg_target, arg_root)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path '%s' does not start with specified root '%s', refusing.", arg_target, arg_root); - + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Specified path '%s' does not start with specified root '%s', refusing.", + arg_target, arg_root); return 1; } @@ -305,9 +291,9 @@ static int validate_gpt_metadata_one(sd_device *d, const char *path, const Valid assert(d); assert(f); - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_ERR); if (r < 0) - return log_error_errno(r, "Cannot validate GPT constraints, refusing."); + return r; _cleanup_close_ int block_fd = sd_device_open(d, O_RDONLY|O_CLOEXEC|O_NONBLOCK); if (block_fd < 0) diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 233423935f763..f2f8c271d4718 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -12,6 +11,7 @@ #include "bus-util.h" #include "chase.h" #include "env-util.h" +#include "escape.h" #include "fd-util.h" #include "fileio.h" #include "format-table.h" @@ -19,6 +19,7 @@ #include "log.h" #include "main-func.h" #include "memfd-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -30,6 +31,7 @@ #include "process-util.h" #include "recurse-dir.h" #include "runtime-scope.h" +#include "socket-forward.h" #include "string-util.h" #include "strv.h" #include "terminal-util.h" @@ -52,6 +54,7 @@ static bool arg_quiet = false; static char **arg_graceful = NULL; static usec_t arg_timeout = 0; static bool arg_exec = false; +static bool arg_upgrade = false; static PushFds arg_push_fds = {}; static bool arg_ask_password = true; static bool arg_legend = true; @@ -69,198 +72,168 @@ STATIC_DESTRUCTOR_REGISTER(arg_push_fds, push_fds_done); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("varlinkctl", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options, verbs); + pager_open(arg_pager_flags); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sIntrospect Varlink Services.%6$s\n" - "\n%3$sCommands:%4$s\n" - " info ADDRESS Show service information\n" - " list-interfaces ADDRESS\n" - " List interfaces implemented by service\n" - " list-methods ADDRESS [INTERFACE…]\n" - " List methods implemented by services or specific\n" - " interfaces\n" - " introspect ADDRESS [INTERFACE…]\n" - " Show interface definition\n" - " call ADDRESS METHOD [PARAMS]\n" - " Invoke method\n" - " --exec call ADDRESS METHOD PARAMS -- CMDLINE…\n" - " Invoke method and pass response and fds to command\n" - " list-registry Show list of services in the service registry\n" - " validate-idl [FILE] Validate interface description\n" - " help Show this help\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-ask-password Do not prompt for password\n" - " --no-pager Do not pipe output into a pager\n" - " --system Enumerate system registry\n" - " --user Enumerate user registry\n" - " --more Request multiple responses\n" - " --collect Collect multiple responses in a JSON array\n" - " --oneway Do not request response\n" - " --json=MODE Output as JSON\n" - " -j Same as --json=pretty on tty, --json=short otherwise\n" - " -q --quiet Do not output method reply\n" - " --graceful=ERROR Treat specified Varlink error as success\n" - " --timeout=SECS Maximum time to wait for method call completion\n" - " -E Short for --more --timeout=infinity\n" - " --push-fd=FD Pass the specified fd along with method call\n" - "\nSee the %2$s for details.\n", + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sIntrospect Varlink Services.%s\n" + "\nCommands:\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\nOptions:\n"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nWith --exec, specify the command to invoke:\n" + " %s --exec call ADDRESS METHOD PARAMS -- CMDLINE…\n", + program_invocation_short_name); + + printf("\nSee the %s for details.\n", link); return 0; } -static int verb_help(int argc, char **argv, void *userdata) { - return help(); -} +VERB_COMMON_HELP(help); -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_MORE, - ARG_ONEWAY, - ARG_JSON, - ARG_COLLECT, - ARG_GRACEFUL, - ARG_TIMEOUT, - ARG_EXEC, - ARG_PUSH_FD, - ARG_NO_ASK_PASSWORD, - ARG_USER, - ARG_SYSTEM, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "more", no_argument, NULL, ARG_MORE }, - { "oneway", no_argument, NULL, ARG_ONEWAY }, - { "json", required_argument, NULL, ARG_JSON }, - { "collect", no_argument, NULL, ARG_COLLECT }, - { "quiet", no_argument, NULL, 'q' }, - { "graceful", required_argument, NULL, ARG_GRACEFUL }, - { "timeout", required_argument, NULL, ARG_TIMEOUT }, - { "exec", no_argument, NULL, ARG_EXEC }, - { "push-fd", required_argument, NULL, ARG_PUSH_FD }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - {}, - }; - - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hjqE", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case 'E': - arg_timeout = USEC_INFINITY; - _fallthrough_; + OPTION_LONG("system", NULL, "Enumerate system registry"): + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; - case ARG_MORE: - arg_method_flags = (arg_method_flags & ~SD_VARLINK_METHOD_ONEWAY) | SD_VARLINK_METHOD_MORE; + OPTION_LONG("user", NULL, "Enumerate user registry"): + arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_ONEWAY: - arg_method_flags = (arg_method_flags & ~SD_VARLINK_METHOD_MORE) | SD_VARLINK_METHOD_ONEWAY; + OPTION_LONG("more", NULL, "Request multiple responses"): + arg_method_flags = (arg_method_flags & ~SD_VARLINK_METHOD_ONEWAY) | SD_VARLINK_METHOD_MORE; break; - case ARG_COLLECT: + OPTION_LONG("collect", NULL, "Collect multiple responses in a JSON array"): arg_collect = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_LONG("oneway", NULL, "Do not request response"): + arg_method_flags = (arg_method_flags & ~SD_VARLINK_METHOD_MORE) | SD_VARLINK_METHOD_ONEWAY; + break; + + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case 'j': + OPTION_COMMON_LOWERCASE_J: arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; - case 'q': + OPTION('q', "quiet", NULL, "Do not output method reply"): arg_quiet = true; break; - case ARG_GRACEFUL: - r = varlink_idl_qualified_symbol_name_is_valid(optarg); + OPTION_LONG("graceful", "ERROR", "Treat specified Varlink error as success"): + r = varlink_idl_qualified_symbol_name_is_valid(arg); if (r < 0) - return log_error_errno(r, "Failed to validate Varlink error name '%s': %m", optarg); + return log_error_errno(r, "Failed to validate Varlink error name '%s': %m", arg); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid Varlink error name: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid Varlink error name: %s", arg); - if (strv_extend(&arg_graceful, optarg) < 0) + if (strv_extend(&arg_graceful, arg) < 0) return log_oom(); - break; - case ARG_TIMEOUT: - if (isempty(optarg)) { + OPTION_LONG("timeout", "SECS", "Maximum time to wait for method call completion"): + if (isempty(arg)) { arg_timeout = USEC_INFINITY; break; } - r = parse_sec(optarg, &arg_timeout); + r = parse_sec(arg, &arg_timeout); if (r < 0) - return log_error_errno(r, "Failed to parse --timeout= parameter '%s': %m", optarg); + return log_error_errno(r, "Failed to parse --timeout= parameter '%s': %m", arg); if (arg_timeout == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Timeout cannot be zero."); + break; + OPTION_SHORT('E', NULL, "Short for --more --timeout=infinity"): + arg_timeout = USEC_INFINITY; + arg_method_flags = (arg_method_flags & ~SD_VARLINK_METHOD_ONEWAY) | SD_VARLINK_METHOD_MORE; + break; + + OPTION_LONG("upgrade", NULL, + "Request protocol upgrade (connection becomes raw" + " bidirectional pipe on stdin/stdout after reply)"): + arg_upgrade = true; break; - case ARG_EXEC: + OPTION_LONG("exec", NULL, "Invoke method and pass response and fds to command"): arg_exec = true; break; - case ARG_PUSH_FD: { + OPTION_LONG("push-fd", "FD", "Pass the specified fd along with method call"): { if (!GREEDY_REALLOC(arg_push_fds.fds, arg_push_fds.n_fds + 1)) return log_oom(); _cleanup_close_ int add_fd = -EBADF; - if (STARTSWITH_SET(optarg, "/", "./")) { + if (STARTSWITH_SET(arg, "/", "./")) { /* We usually expect a numeric fd spec, but as an extension let's treat this * as a path to open in read-only mode in case this is clearly an absolute or * relative path */ - add_fd = open(optarg, O_CLOEXEC|O_RDONLY|O_NOCTTY); + add_fd = open(arg, O_CLOEXEC|O_RDONLY|O_NOCTTY); if (add_fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", optarg); + return log_error_errno(errno, "Failed to open '%s': %m", arg); } else { - int parsed_fd = parse_fd(optarg); + int parsed_fd = parse_fd(arg); if (parsed_fd < 0) - return log_error_errno(parsed_fd, "Failed to parse --push-fd= parameter: %s", optarg); + return log_error_errno(parsed_fd, "Failed to parse --push-fd= parameter: %s", arg); /* Make a copy, so that the same fd could be used multiple times in a reasonable * way. This also validates the fd early */ @@ -272,24 +245,6 @@ static int parse_argv(int argc, char *argv[]) { arg_push_fds.fds[arg_push_fds.n_fds++] = TAKE_FD(add_fd); break; } - - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; - break; - - case ARG_SYSTEM: - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } /* If more than one reply is expected, imply JSON-SEQ output, and set SD_JSON_FORMAT_FLUSH */ @@ -298,6 +253,7 @@ static int parse_argv(int argc, char *argv[]) { strv_sort_uniq(arg_graceful); + *ret_args = option_parser_get_args(&state); return 1; } @@ -366,7 +322,9 @@ static void get_info_data_done(GetInfoData *d) { d->interfaces = strv_free(d->interfaces); } -static int verb_info(int argc, char *argv[], void *userdata) { +VERB(verb_info, "info", "ADDRESS", 2, 2, 0, "Show service information"); +VERB(verb_info, "list-interfaces", "ADDRESS", 2, 2, 0, "List interfaces implemented by service"); +static int verb_info(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; const char *url; int r; @@ -405,6 +363,8 @@ static int verb_info(int argc, char *argv[], void *userdata) { if (streq_ptr(argv[0], "list-interfaces")) { STRV_FOREACH(i, data.interfaces) puts(*i); + + return 0; } else { _cleanup_(table_unrefp) Table *t = NULL; @@ -428,20 +388,14 @@ static int verb_info(int argc, char *argv[], void *userdata) { if (r < 0) return table_log_add_error(r); - r = table_print(t, NULL); - if (r < 0) - return table_log_print_error(r); + return table_print_or_warn(t); } } else { - sd_json_variant *v; - - v = streq_ptr(argv[0], "list-interfaces") ? + sd_json_variant *v = streq_ptr(argv[0], "list-interfaces") ? sd_json_variant_by_key(reply, "interfaces") : reply; - sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); + return sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); } - - return 0; } static size_t break_columns(void) { @@ -463,7 +417,10 @@ typedef struct GetInterfaceDescriptionData { const char *description; } GetInterfaceDescriptionData; -static int verb_introspect(int argc, char *argv[], void *userdata) { +VERB(verb_introspect, "introspect", "ADDRESS [INTERFACE…]", 2, VERB_ANY, 0, "Show interface definition"); +VERB(verb_introspect, "list-methods", "ADDRESS [INTERFACE…]", 2, VERB_ANY, 0, + "List methods implemented by services or specific interfaces"); +static int verb_introspect(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; _cleanup_strv_free_ char **auto_interfaces = NULL; char **interfaces; @@ -634,11 +591,157 @@ static int reply_callback( return r; } -static int verb_call(int argc, char *argv[], void *userdata) { +static int upgrade_forward_done(SocketForward *sf, int error, void *userdata) { + sd_event *event = ASSERT_PTR(userdata); + + return sd_event_exit(event, error < 0 ? error : 0); +} + +/* This will only return if something goes wrong, otherwise the exec_cmdline + * is run and replaces our code. */ +static int exec_with_listen_fds(char **exec_cmdline, int *fds, size_t n_fds) { + _cleanup_free_ char *j = quote_command_line(exec_cmdline, SHELL_ESCAPE_EMPTY); + if (!j) + return log_oom(); + + log_close(); + log_set_open_when_needed(true); + + int r = close_all_fds(fds, n_fds); + if (r < 0) + return log_error_errno(r, "Failed to close all remaining file descriptors: %m"); + + r = pack_fds(fds, n_fds); + if (r < 0) + return log_error_errno(r, "Failed to rearrange file descriptors: %m"); + + r = fd_cloexec_many(fds, n_fds, false); + if (r < 0) + return log_error_errno(r, "Failed to disable O_CLOEXEC for file descriptors: %m"); + + if (n_fds > 0) { + r = setenvf("LISTEN_FDS", /* overwrite= */ true, "%zu", n_fds); + if (r < 0) + return log_error_errno(r, "Failed to set $LISTEN_FDS environment variable: %m"); + + r = setenvf("LISTEN_PID", /* overwrite= */ true, PID_FMT, getpid_cached()); + if (r < 0) + return log_error_errno(r, "Failed to set $LISTEN_PID environment variable: %m"); + + uint64_t pidfdid; + if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) { + r = setenvf("LISTEN_PIDFDID", /* overwrite= */ true, "%" PRIu64, pidfdid); + if (r < 0) + return log_error_errno(r, "Failed to set $LISTEN_PIDFDID environment variable: %m"); + } + } else { + (void) unsetenv("LISTEN_FDS"); + (void) unsetenv("LISTEN_PID"); + (void) unsetenv("LISTEN_PIDFDID"); + } + (void) unsetenv("LISTEN_FDNAMES"); + + log_debug("Executing: %s", j); + + execvp(exec_cmdline[0], exec_cmdline); + return log_error_errno(errno, "Failed to execute '%s': %m", j); +} + +static int varlink_call_and_upgrade(const char *url, const char *method, sd_json_variant *parameters, char **exec_cmdline) { + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(socket_forward_freep) SocketForward *sf = NULL; + _cleanup_close_ int input_fd = -EBADF, output_fd = -EBADF; + const char *error_id = NULL; + int r; + + r = varlink_connect_auto(&vl, url); + if (r < 0) + return r; + + r = sd_varlink_call_and_upgrade( + vl, + method, + parameters, + /* ret_parameters= */ NULL, + &error_id, + &input_fd, + &output_fd); + if (r < 0) + return log_error_errno(r, "Failed to upgrade connection via %s(): %m", method); + if (!isempty(error_id)) + return log_error_errno(SYNTHETIC_ERRNO(EBADE), "Upgrade via %s() failed with error: %s", method, error_id); + + if (!strv_isempty(exec_cmdline)) { + /* --exec mode: place the upgraded connection on stdin/stdout so that the child + * process can just read/write naturally. */ + (void) sd_notify(/* unset_environment= */ false, "READY=1"); + + log_close(); + log_set_open_when_needed(true); + + int keep_fds[] = { TAKE_FD(input_fd), TAKE_FD(output_fd) }; + + r = close_all_fds(keep_fds, 2); + if (r < 0) { + log_error_errno(r, "Failed to close remaining file descriptors: %m"); + _exit(EXIT_FAILURE); + } + + r = move_fd(keep_fds[0], STDIN_FILENO, /* cloexec= */ false); + if (r < 0) { + log_error_errno(r, "Failed to move upgraded connection to stdin: %m"); + _exit(EXIT_FAILURE); + } + + r = move_fd(keep_fds[1], STDOUT_FILENO, /* cloexec= */ false); + if (r < 0) { + log_error_errno(r, "Failed to move upgraded connection to stdout: %m"); + _exit(EXIT_FAILURE); + } + + (void) exec_with_listen_fds(exec_cmdline, /* fds= */ NULL, /* n_fds= */ 0); + /* This is only reached on failure, otherwise we continue with exec_cmdline). */ + _exit(EXIT_FAILURE); + } + + /* No --exec: bidirectional proxy between stdin/stdout and the upgraded socket */ + + /* Use a clean event loop just for the forwarding to avoid any interference. */ + r = sd_event_new(&event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + + _cleanup_close_ int stdin_fd = fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC, 3); + if (stdin_fd < 0) + return log_error_errno(errno, "Failed to dup stdin: %m"); + + _cleanup_close_ int stdout_fd = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 3); + if (stdout_fd < 0) + return log_error_errno(errno, "Failed to dup stdout: %m"); + + r = socket_forward_new_pair( + event, + TAKE_FD(stdin_fd), TAKE_FD(stdout_fd), + TAKE_FD(input_fd), TAKE_FD(output_fd), + upgrade_forward_done, event, + &sf); + if (r < 0) + return log_error_errno(r, "Failed to set up socket forwarding for varlink: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run socket forward for varlink: %m"); + + return 0; +} + +VERB(verb_call, "call", "ADDRESS METHOD [PARAMS]", 3, VERB_ANY, 0, "Invoke method"); +static int verb_call(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *jp = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; const char *url, *method, *parameter, *source; - char **cmdline; + char **exec_cmdline; int r; assert(argc >= 3); @@ -651,12 +754,31 @@ static int verb_call(int argc, char *argv[], void *userdata) { if (arg_exec && (arg_collect || (arg_method_flags & (SD_VARLINK_METHOD_ONEWAY|SD_VARLINK_METHOD_MORE))) != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--exec and --collect/--more/--oneway may not be combined."); + if (arg_upgrade) { + if (arg_collect || arg_method_flags != 0 || arg_push_fds.n_fds > 0 || !strv_isempty(arg_graceful)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--upgrade may not be combined with --collect/--more/--oneway/--push-fd=/--graceful."); + } + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); url = argv[1]; method = argv[2]; parameter = argc > 3 && !streq(argv[3], "-") ? argv[3] : NULL; - cmdline = strv_skip(argv, 4); + exec_cmdline = strv_skip(argv, 4); + + if (!varlink_idl_qualified_symbol_name_is_valid(method)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid qualified method name: '%s' (Expected valid Varlink interface name, followed by a dot, followed by a valid Varlink symbol name.)", method); + + if (arg_upgrade) { + /* For --upgrade, parse parameters from argv only (stdin is used for the upgraded connection) */ + if (parameter) { + r = sd_json_parse(parameter, SD_JSON_PARSE_MUST_BE_OBJECT, &jp, /* reterr_line= */ NULL, /* reterr_column= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse parameters: %m"); + } + + return varlink_call_and_upgrade(url, method, jp, exec_cmdline); + } /* No JSON mode explicitly configured? Then default to the same as -j (except if --exec is used, in * which case generate shortest possible JSON since we are going to pass it to a program rather than @@ -674,22 +796,19 @@ static int verb_call(int argc, char *argv[], void *userdata) { * leave incomplete lines hanging around. */ arg_json_format_flags |= SD_JSON_FORMAT_NEWLINE; - if (!varlink_idl_qualified_symbol_name_is_valid(method)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid qualified method name: '%s' (Expected valid Varlink interface name, followed by a dot, followed by a valid Varlink symbol name.)", method); - unsigned line = 0, column = 0; if (parameter) { source = ""; /* is correct, as dispatch_verb() shifts arguments by one for the verb. */ - r = sd_json_parse_with_source(parameter, source, 0, &jp, &line, &column); + r = sd_json_parse_with_source(parameter, source, SD_JSON_PARSE_MUST_BE_OBJECT, &jp, &line, &column); } else { if (isatty_safe(STDIN_FILENO) && !arg_quiet) log_notice("Expecting method call parameter JSON object on standard input. (Provide empty string or {} for no parameters.)"); source = ""; - r = sd_json_parse_file_at(stdin, AT_FDCWD, source, 0, &jp, &line, &column); + r = sd_json_parse_file_at(stdin, AT_FDCWD, source, SD_JSON_PARSE_MUST_BE_OBJECT, &jp, &line, &column); } if (r < 0 && r != -ENODATA) return log_error_errno(r, "Failed to parse parameters at %s:%u:%u: %m", source, line, column); @@ -842,10 +961,6 @@ static int verb_call(int argc, char *argv[], void *userdata) { if (mfd < 0) return log_error_errno(mfd, "Failed to allocate memfd for reply: %m"); - _cleanup_free_ char *j = strv_join(cmdline, " "); - if (!j) - return log_oom(); - int *fd_array = NULL, n = 0; size_t m = 0; CLEANUP_ARRAY(fd_array, m, close_many_and_free); @@ -872,65 +987,14 @@ static int verb_call(int argc, char *argv[], void *userdata) { * lives in our process their fds. Hence we will now no longer bubble up any * errors. */ - log_close(); - log_set_open_when_needed(true); - r = move_fd(mfd, STDIN_FILENO, /* cloexec= */ false); if (r < 0) { log_error_errno(r, "Failed to move reply to STDIN_FILENO: %m"); _exit(EXIT_FAILURE); } - r = close_all_fds(fd_array, m); - if (r < 0) { - log_error_errno(r, "Failed to close all remaining file descriptors: %m"); - _exit(EXIT_FAILURE); - } - - r = pack_fds(fd_array, m); - if (r < 0) { - log_error_errno(r, "Failed to rearrange file descriptors: %m"); - _exit(EXIT_FAILURE); - } - - r = fd_cloexec_many(fd_array, m, false); - if (r < 0) { - log_error_errno(r, "Failed to disable O_CLOEXEC for file descriptors: %m"); - _exit(EXIT_FAILURE); - } - - if (m > 0) { - r = setenvf("LISTEN_FDS", /* overwrite= */ true, "%zu", m); - if (r < 0) { - log_error_errno(r, "Failed to set $LISTEN_FDS environment variable: %m"); - _exit(EXIT_FAILURE); - } - - r = setenvf("LISTEN_PID", /* overwrite= */ true, PID_FMT, getpid_cached()); - if (r < 0) { - log_error_errno(r, "Failed to set $LISTEN_PID environment variable: %m"); - _exit(EXIT_FAILURE); - } - - uint64_t pidfdid; - if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) { - r = setenvf("LISTEN_PIDFDID", /* overwrite= */ true, "%" PRIu64, pidfdid); - if (r < 0) { - log_error_errno(r, "Failed to set $LISTEN_PIDFDID environment variable: %m"); - _exit(EXIT_FAILURE); - } - } - } else { - (void) unsetenv("LISTEN_FDS"); - (void) unsetenv("LISTEN_PID"); - (void) unsetenv("LISTEN_PIDFDID"); - } - (void) unsetenv("LISTEN_FDNAMES"); - - log_debug("Executing: %s", j); - - execvp(cmdline[0], cmdline); - log_error_errno(errno, "Failed to execute '%s': %m", j); + (void) exec_with_listen_fds(exec_cmdline, fd_array, m); + /* This is only reached on failure, otherwise we continue with exec_cmdline. */ _exit(EXIT_FAILURE); } @@ -946,7 +1010,8 @@ static int verb_call(int argc, char *argv[], void *userdata) { return 0; } -static int verb_validate_idl(int argc, char *argv[], void *userdata) { +VERB(verb_validate_idl, "validate-idl", "[FILE]", 1, 2, 0, "Validate interface description"); +static int verb_validate_idl(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_varlink_interface_freep) sd_varlink_interface *vi = NULL; _cleanup_free_ char *text = NULL; const char *fname; @@ -995,7 +1060,8 @@ static int verb_validate_idl(int argc, char *argv[], void *userdata) { return 0; } -static int verb_list_registry(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_list_registry, "list-registry", "Show list of services in the service registry"); +static int verb_list_registry(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; assert(argc <= 1); @@ -1094,20 +1160,153 @@ static int verb_list_registry(int argc, char *argv[], void *userdata) { return 0; } -static int varlinkctl_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "info", 2, 2, 0, verb_info }, - { "list-interfaces", 2, 2, 0, verb_info }, - { "introspect", 2, VERB_ANY, 0, verb_introspect }, - { "list-methods", 2, VERB_ANY, 0, verb_introspect }, - { "call", 3, VERB_ANY, 0, verb_call }, - { "list-registry", VERB_ANY, 1, 0, verb_list_registry }, - { "validate-idl", 1, 2, 0, verb_validate_idl }, - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); +/* Build a minimal IDL from a qualified method name so that introspection works. The parsed interface is + * returned to the caller who must keep it alive for the lifetime of the server + * (sd_varlink_server_add_interface() borrows the pointer). */ +static int varlink_server_add_interface_from_method(sd_varlink_server *s, const char *method, sd_varlink_interface **ret_interface) { + assert(s); + assert(method); + assert(ret_interface); + + const char *dot = strrchr(method, '.'); + assert(dot); + + _cleanup_free_ char *interface_name = strndup(method, dot - method); + if (!interface_name) + return log_oom(); + + /* Note that we do not need to put the upgrade flag comment here, it is added automatically + * by varlink_idl_format_symbol() because of the SD_VARLINK_REQUIRES_UPGRADE flag. */ + _cleanup_free_ char *idl_text = strjoin( + "interface ", interface_name, "\n" + "\n" + "method ", dot + 1, " () -> ()\n"); + if (!idl_text) + return log_oom(); + + _cleanup_(sd_varlink_interface_freep) sd_varlink_interface *iface = NULL; + int r = sd_varlink_idl_parse(idl_text, /* reterr_line= */ NULL, /* reterr_column= */ NULL, &iface); + if (r < 0) + return log_error_errno(r, "Failed to parse IDL for method '%s': %m", method); + + /* Mark the method as requiring the upgrade flag so introspection shows the annotation */ + assert(iface->symbols[0] && iface->symbols[0]->symbol_type == SD_VARLINK_METHOD); + ((sd_varlink_symbol*) iface->symbols[0])->symbol_flags |= SD_VARLINK_REQUIRES_UPGRADE; + + r = sd_varlink_server_add_interface(s, iface); + if (r < 0) + return r; + + *ret_interface = TAKE_PTR(iface); + + return 0; +} + +static int method_serve_upgrade(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + char **exec_cmdline = ASSERT_PTR(userdata); + _cleanup_close_ int input_fd = -EBADF, output_fd = -EBADF; + int r; + + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_UPGRADE)) + return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_UPGRADE, NULL); + + r = sd_varlink_reply_and_upgrade(link, /* parameters= */ NULL, &input_fd, &output_fd); + if (r < 0) + return log_error_errno(r, "Failed to upgrade connection: %m"); + + /* Copy exec_cmdline before forking: pidref_safe_fork() calls rename_process() which + * overwrites the argv area that exec_cmdline points into. */ + _cleanup_strv_free_ char **cmdline_copy = strv_copy(exec_cmdline); + if (!cmdline_copy) + return log_oom(); + + r = pidref_safe_fork_full( + "(serve)", + (int[]) { input_fd, output_fd, STDERR_FILENO }, + /* except_fds= */ NULL, /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_REARRANGE_STDIO|FORK_DETACH|FORK_LOG, + /* ret= */ NULL); + if (r < 0) + return r; + if (r == 0) { + execvp(cmdline_copy[0], cmdline_copy); + log_error_errno(errno, "Failed to execute '%s': %m", cmdline_copy[0]); + _exit(EXIT_FAILURE); + } + + return 0; +} + +VERB(verb_serve, "serve", "METHOD CMDLINE…", 3, VERB_ANY, 0, "Serve a command via varlink protocol upgrade"); +static int verb_serve(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + const char *method; + char **exec_cmdline; + int r, n; + + assert(argc >= 3); /* Guaranteed by verb dispatch table */ + + method = argv[1]; + exec_cmdline = argv + 2; + + r = varlink_idl_qualified_symbol_name_is_valid(method); + if (r < 0) + return log_error_errno(r, "Failed to validate method name '%s': %m", method); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid qualified method name: '%s'", method); + + /* Require socket activation */ + n = sd_listen_fds(/* unset_environment= */ true); + if (n < 0) + return log_error_errno(n, "Failed to determine passed file descriptors: %m"); + if (n == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No file descriptors passed via socket activation."); + if (n > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected exactly one socket activation fd, got %d.", n); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_varlink_server_new(&s, SD_VARLINK_SERVER_INHERIT_USERDATA|SD_VARLINK_SERVER_UPGRADABLE); + if (r < 0) + return log_error_errno(r, "Failed to allocate varlink server: %m"); + + _cleanup_free_ char *description = strjoin("serve:", method); + if (!description) + return log_oom(); + + r = sd_varlink_server_set_description(s, description); + if (r < 0) + return log_error_errno(r, "Failed to set server description: %m"); + + r = sd_varlink_server_bind_method(s, method, method_serve_upgrade); + if (r < 0) + return log_error_errno(r, "Failed to bind method '%s': %m", method); + + _cleanup_(sd_varlink_interface_freep) sd_varlink_interface *iface = NULL; + r = varlink_server_add_interface_from_method(s, method, &iface); + if (r < 0) + return log_error_errno(r, "Failed to add interface for method '%s': %m", method); + + sd_varlink_server_set_userdata(s, exec_cmdline); + + r = sd_varlink_server_listen_fd(s, SD_LISTEN_FDS_START); + if (r < 0) + return log_error_errno(r, "Failed to listen on socket activation fd: %m"); + + r = sd_varlink_server_attach_event(s, event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach varlink server to event loop: %m"); + + (void) sd_notify(/* unset_environment= */ false, "READY=1"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + return 0; } static int run(int argc, char *argv[]) { @@ -1115,11 +1314,12 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; /* unnecessary initialization to appease gcc <= 13 */ + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return varlinkctl_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index f1039319fd872..73bf240cf5130 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -155,6 +155,8 @@ static void context_load_config(Context *c) { } static int verify_vc_device(int fd) { + assert(fd >= 0); + unsigned char data[] = { TIOCL_GETFGCONSOLE, }; @@ -171,8 +173,9 @@ static int verify_vc_allocation(unsigned idx) { } static int verify_vc_allocation_byfd(int fd) { - struct vt_stat vcs = {}; + assert(fd >= 0); + struct vt_stat vcs = {}; if (ioctl(fd, VT_GETSTATE, &vcs) < 0) return -errno; @@ -212,6 +215,22 @@ static int verify_vc_display_mode(int fd) { return mode != KD_TEXT ? -EBUSY : 0; } +static int verify_vc_support_font(int fd) { + struct console_font_op cfo = { + .op = KD_FONT_OP_GET, + .width = UINT_MAX, + .height = UINT_MAX, + .charcount = UINT_MAX, + }; + + assert(fd >= 0); + + if (ioctl(fd, KDFONTOP, &cfo) < 0) + return ERRNO_IS_NOT_SUPPORTED(errno) ? 0 : -errno; + + return 1; +} + static int toggle_utf8_vc(const char *name, int fd, bool utf8) { int r; struct termios tc = {}; @@ -268,6 +287,14 @@ static int keyboard_load_and_wait(const char *vc, Context *c, bool utf8) { if (streq(keymap, "@kernel")) return 0; + if (access(KBD_LOADKEYS, X_OK) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '" KBD_LOADKEYS "' is available: %m"); + + log_notice("'" KBD_LOADKEYS "' is not available, skipping keyboard mapping setup."); + return 0; /* Report that we skipped this */ + } + args[i++] = KBD_LOADKEYS; args[i++] = "-q"; args[i++] = "-C"; @@ -295,10 +322,16 @@ static int keyboard_load_and_wait(const char *vc, Context *c, bool utf8) { _exit(EXIT_FAILURE); } - return pidref_wait_for_terminate_and_check(KBD_LOADKEYS, &pidref, WAIT_LOG); + r = pidref_wait_for_terminate_and_check(KBD_LOADKEYS, &pidref, WAIT_LOG); + if (r < 0) + return r; + if (r != EXIT_SUCCESS) + return -EPROTO; + + return 1; /* Report that we did something */ } -static int font_load_and_wait(const char *vc, Context *c) { +static int font_load_and_wait(int fd, const char *vc, Context *c) { const char* args[9]; unsigned i = 0; int r; @@ -315,6 +348,24 @@ static int font_load_and_wait(const char *vc, Context *c) { if (!font && !font_map && !font_unimap) return 0; + if (access(KBD_SETFONT, X_OK) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '" KBD_SETFONT "' is available: %m"); + + log_notice("'" KBD_SETFONT "' is not available, skipping console font setup."); + return 0; /* Report that we skipped this */ + } + + /* May be called on the dummy console (e.g. during keymap setup with fbcon deferred takeover). Font + * changes are not supported here and will fail. */ + r = verify_vc_support_font(fd); + if (r < 0) + return log_error_errno(r, "Failed to check '%s' has font support: %m", vc); + if (r == 0) { + log_notice("'%s' has no font support, skipping.", vc); + return 0; /* Report that we skipped this */ + } + args[i++] = KBD_SETFONT; args[i++] = "-C"; args[i++] = vc; @@ -346,16 +397,19 @@ static int font_load_and_wait(const char *vc, Context *c) { _exit(EXIT_FAILURE); } - /* setfont returns EX_OSERR when ioctl(KDFONTOP/PIO_FONTX/PIO_FONTX) fails. This might mean various - * things, but in particular lack of a graphical console. Let's be generous and not treat this as an - * error. */ + /* setfont returns EX_OSERR when ioctl(KDFONTOP/PIO_FONTX/PIO_FONTX) fails. Let's be generous and not + * treat this as an error. */ r = pidref_wait_for_terminate_and_check(KBD_SETFONT, &pidref, WAIT_LOG_ABNORMAL); - if (r == EX_OSERR) + if (r < 0) + return r; /* WAIT_LOG_ABNORMAL means we already have logged about these kinds of errors */ + if (r == EX_OSERR) { log_notice(KBD_SETFONT " failed with a \"system error\" (EX_OSERR), ignoring."); - else if (r >= 0 && r != EXIT_SUCCESS) - log_error(KBD_SETFONT " failed with exit status %i.", r); + return 0; /* Report that we skipped this */ + } + if (r != EXIT_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(EPROTO), KBD_SETFONT " failed with exit status %i.", r); - return r; + return 1; /* Report that we did something */ } /* @@ -375,9 +429,10 @@ static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) { struct unimapdesc unimapd; _cleanup_free_ struct unipair* unipairs = NULL; _cleanup_free_ void *fontbuf = NULL; - int log_level = LOG_WARNING; int r; + assert(src_fd >= 0); + unipairs = new(struct unipair, USHRT_MAX); if (!unipairs) return (void) log_oom(); @@ -385,14 +440,7 @@ static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) { /* get metadata of the current font (width, height, count) */ r = ioctl(src_fd, KDFONTOP, &cfo); if (r < 0) { - /* We might be called to operate on the dummy console (to setup keymap - * mainly) when fbcon deferred takeover is used for example. In such case, - * setting font is not supported and is expected to fail. */ - if (errno == ENOSYS) - log_level = LOG_DEBUG; - - log_full_errno(log_level, errno, - "KD_FONT_OP_GET failed while trying to get the font metadata: %m"); + log_warning_errno(errno, "KD_FONT_OP_GET failed while trying to get the font metadata: %m"); } else { /* verify parameter sanity first */ if (cfo.width > 32 || cfo.height > 32 || cfo.charcount > 512) @@ -428,7 +476,7 @@ static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) { } if (cfo.op != KD_FONT_OP_SET) - log_full(log_level, "Fonts will not be copied to remaining consoles"); + log_warning("Fonts will not be copied to remaining consoles"); for (unsigned i = 1; i <= 63; i++) { char ttyname[sizeof("/dev/tty63")]; @@ -549,6 +597,8 @@ static int verify_source_vc(char **ret_path, const char *src_vc) { char *path; int r; + assert(ret_path); + fd = open_terminal(src_vc, O_RDWR|O_CLOEXEC|O_NOCTTY); if (fd < 0) return log_error_errno(fd, "Failed to open %s: %m", src_vc); @@ -584,9 +634,8 @@ static int run(int argc, char **argv) { _cleanup_(context_done) Context c = {}; _cleanup_free_ char *vc = NULL; _cleanup_close_ int fd = -EBADF, lock_fd = -EBADF; - bool utf8, keyboard_ok; + bool utf8; unsigned idx = 0; - int r; log_setup(); @@ -625,18 +674,19 @@ static int run(int argc, char **argv) { (void) toggle_utf8_vc(vc, fd, utf8); - r = font_load_and_wait(vc, &c); - keyboard_ok = keyboard_load_and_wait(vc, &c, utf8) == 0; + int setfont_status = font_load_and_wait(fd, vc, &c); + int loadkeys_status = keyboard_load_and_wait(vc, &c, utf8); if (idx > 0) { - if (r == 0) - setup_remaining_vcs(fd, idx, utf8); + if (setfont_status == 0) + log_notice("Configuration of first virtual console was skipped, ignoring remaining ones."); + else if (setfont_status < 0) + log_warning("Configuration of first virtual console failed, ignoring remaining ones."); else - log_full(r == EX_OSERR ? LOG_NOTICE : LOG_WARNING, - "Configuration of first virtual console failed, ignoring remaining ones."); + setup_remaining_vcs(fd, idx, utf8); } - return IN_SET(r, 0, EX_OSERR) && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE; + return (setfont_status >= 0 && loadkeys_status >= 0) ? EXIT_SUCCESS : EXIT_FAILURE; } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/veritysetup/meson.build b/src/veritysetup/meson.build index cc9ac80a61554..21432d41f1658 100644 --- a/src/veritysetup/meson.build +++ b/src/veritysetup/meson.build @@ -8,7 +8,7 @@ executables += [ libexec_template + { 'name' : 'systemd-veritysetup', 'sources' : files('veritysetup.c'), - 'dependencies' : libcryptsetup, + 'dependencies' : libcryptsetup_cflags, }, generator_template + { 'name' : 'systemd-veritysetup-generator', diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index e2135562c1f12..b5d57fd1fe80c 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -116,6 +116,8 @@ static int parse_block_size(const char *t, uint64_t *size) { uint64_t u; int r; + assert(size); + r = parse_size(t, 1024, &u); if (r < 0) return r; @@ -219,9 +221,6 @@ static int parse_options(const char *options) { arg_hash_offset = off; } else if ((val = startswith(word, "salt="))) { - if (!string_is_safe(val)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "salt= is not valid."); - if (isempty(val)) { arg_salt = mfree(arg_salt); arg_salt_size = 32; @@ -316,7 +315,7 @@ static int parse_options(const char *options) { return r; } -static int verb_attach(int argc, char *argv[], void *userdata) { +static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_free_ void *rh = NULL; struct crypt_params_verity p = {}; @@ -377,13 +376,13 @@ static int verb_attach(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Failed to decode root hash signature data from udev data device: %m"); } - r = crypt_init(&cd, verity_device); + r = sym_crypt_init(&cd, verity_device); if (r < 0) return log_error_errno(r, "Failed to open verity device %s: %m", verity_device); cryptsetup_enable_logging(cd); - status = crypt_status(cd, volume); + status = sym_crypt_status(cd, volume); if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { log_info("Volume %s already active.", volume); return 0; @@ -397,7 +396,7 @@ static int verb_attach(int argc, char *argv[], void *userdata) { .fec_roots = arg_fec_roots, }; - r = crypt_load(cd, CRYPT_VERITY, &p); + r = sym_crypt_load(cd, CRYPT_VERITY, &p); if (r < 0) return log_error_errno(r, "Failed to load verity superblock: %m"); } else { @@ -417,28 +416,28 @@ static int verb_attach(int argc, char *argv[], void *userdata) { .flags = CRYPT_VERITY_NO_HEADER, }; - r = crypt_format(cd, CRYPT_VERITY, NULL, NULL, arg_uuid, NULL, 0, &p); + r = sym_crypt_format(cd, CRYPT_VERITY, NULL, NULL, arg_uuid, NULL, 0, &p); if (r < 0) return log_error_errno(r, "Failed to format verity superblock: %m"); } - r = crypt_set_data_device(cd, data_device); + r = sym_crypt_set_data_device(cd, data_device); if (r < 0) return log_error_errno(r, "Failed to configure data device: %m"); if (arg_root_hash_signature_size > 0) { - r = crypt_activate_by_signed_key(cd, volume, rh, rh_size, arg_root_hash_signature, arg_root_hash_signature_size, arg_activate_flags); + r = sym_crypt_activate_by_signed_key(cd, volume, rh, rh_size, arg_root_hash_signature, arg_root_hash_signature_size, arg_activate_flags); if (r < 0) { log_info_errno(r, "Unable to activate verity device '%s' with root hash signature (%m), retrying without.", volume); - r = crypt_activate_by_volume_key(cd, volume, rh, rh_size, arg_activate_flags); + r = sym_crypt_activate_by_volume_key(cd, volume, rh, rh_size, arg_activate_flags); if (r < 0) return log_error_errno(r, "Failed to activate verity device '%s' both with and without root hash signature: %m", volume); log_info("Activation of verity device '%s' succeeded without root hash signature.", volume); } } else - r = crypt_activate_by_volume_key(cd, volume, rh, rh_size, arg_activate_flags); + r = sym_crypt_activate_by_volume_key(cd, volume, rh, rh_size, arg_activate_flags); if (r < 0) return log_error_errno(r, "Failed to set up verity device '%s': %m", volume); @@ -450,7 +449,7 @@ static int verb_attach(int argc, char *argv[], void *userdata) { return 0; } -static int verb_detach(int argc, char *argv[], void *userdata) { +static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; int r; @@ -461,7 +460,7 @@ static int verb_detach(int argc, char *argv[], void *userdata) { if (!filename_is_valid(volume)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - r = crypt_init_by_name(&cd, volume); + r = sym_crypt_init_by_name(&cd, volume); if (r == -ENODEV) { log_info("Volume %s 'already' inactive.", volume); return 0; @@ -471,7 +470,7 @@ static int verb_detach(int argc, char *argv[], void *userdata) { cryptsetup_enable_logging(cd); - r = crypt_deactivate(cd, volume); + r = sym_crypt_deactivate(cd, volume); if (r < 0) return log_error_errno(r, "Failed to deactivate volume '%s': %m", volume); @@ -479,12 +478,16 @@ static int verb_detach(int argc, char *argv[], void *userdata) { } static int run(int argc, char *argv[]) { + int r; + if (argv_looks_like_help(argc, argv)) return help(); log_setup(); - cryptsetup_enable_logging(NULL); + r = dlopen_cryptsetup(LOG_ERR); + if (r < 0) + return r; umask(0022); diff --git a/src/version/meson.build b/src/version/meson.build index 54db791ccf9ca..a0b03f41a65e8 100644 --- a/src/version/meson.build +++ b/src/version/meson.build @@ -14,3 +14,4 @@ version_h = custom_target('version', ]) version_include = include_directories('.') userspace_sources += [version_h] +generated_sources += [version_h] diff --git a/src/vmspawn/meson.build b/src/vmspawn/meson.build index a836b316578a0..6d08755fedf8b 100644 --- a/src/vmspawn/meson.build +++ b/src/vmspawn/meson.build @@ -6,10 +6,12 @@ endif vmspawn_sources = files( 'vmspawn.c', + 'vmspawn-qemu-config.c', + 'vmspawn-qmp.c', + 'vmspawn-varlink.c', 'vmspawn-settings.c', 'vmspawn-scope.c', 'vmspawn-mount.c', - 'vmspawn-register.c', ) vmspawn_extract_sources = files( 'vmspawn-util.c', diff --git a/src/vmspawn/vmspawn-qemu-config.c b/src/vmspawn/vmspawn-qemu-config.c new file mode 100644 index 0000000000000..08908ff294692 --- /dev/null +++ b/src/vmspawn/vmspawn-qemu-config.c @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "errno-util.h" +#include "log.h" +#include "vmspawn-qemu-config.h" + +static bool qemu_config_type_valid(const char *type) { + return !strchr(type, '\n'); +} + +static bool qemu_config_id_valid(const char *id) { + return !strpbrk(id, "\"\n"); +} + +static bool qemu_config_key_name_valid(const char *key) { + return !strpbrk(key, "=\n"); +} + +static bool qemu_config_value_valid(const char *value) { + return !strpbrk(value, "\"\n"); +} + +int qemu_config_key(FILE *f, const char *key, const char *value) { + assert(f); + assert(key); + assert(value); + + if (!qemu_config_key_name_valid(key)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "QEMU config key '%s' contains '=' or newline.", key); + if (!qemu_config_value_valid(value)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "QEMU config value '%s' contains quote or newline.", value); + + if (fprintf(f, " %s = \"%s\"\n", key, value) < 0) + return -errno_or_else(EIO); + + return 0; +} + +int qemu_config_keyf(FILE *f, const char *key, const char *format, ...) { + _cleanup_free_ char *value = NULL; + va_list ap; + int r; + + assert(f); + assert(key); + assert(format); + + va_start(ap, format); + r = vasprintf(&value, format, ap); + va_end(ap); + if (r < 0) + return -ENOMEM; + + return qemu_config_key(f, key, value); +} + +int qemu_config_section_impl(FILE *f, const char *type, const char *id, ...) { + va_list ap; + int r; + + assert(f); + assert(type); + + if (!qemu_config_type_valid(type)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "QEMU config section type '%s' contains newline.", type); + + if (id) { + if (!qemu_config_id_valid(id)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "QEMU config section id '%s' contains quote or newline.", id); + fprintf(f, "\n[%s \"%s\"]\n", type, id); + } else + fprintf(f, "\n[%s]\n", type); + + va_start(ap, id); + for (;;) { + const char *key = va_arg(ap, const char *); + if (!key) + break; + + const char *value = ASSERT_PTR(va_arg(ap, const char *)); + + r = qemu_config_key(f, key, value); + if (r < 0) { + va_end(ap); + return r; + } + } + va_end(ap); + + if (ferror(f)) + return -errno_or_else(EIO); + + return 0; +} diff --git a/src/vmspawn/vmspawn-qemu-config.h b/src/vmspawn/vmspawn-qemu-config.h new file mode 100644 index 0000000000000..cd782be80ef66 --- /dev/null +++ b/src/vmspawn/vmspawn-qemu-config.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "macro.h" + +/* Helpers for writing QEMU -readconfig INI-style config files. + * + * QEMU config format: + * [type "id"] + * key = "value" + * + * Usage: + * qemu_config_section(f, "device", "rng0", + * "driver", "virtio-rng-pci", + * "rng", "rng0"); + */ + +/* Write a single key = "value" pair (for conditional keys added after a section header) */ +int qemu_config_key(FILE *f, const char *key, const char *value); + +/* Write a single key with a printf-formatted value */ +int qemu_config_keyf(FILE *f, const char *key, const char *format, ...) _printf_(3, 4); + +/* Write a section header with key-value pairs. Varargs are alternating key, value strings. */ +int qemu_config_section_impl(FILE *f, const char *type, const char *id, ...) _sentinel_; +#define qemu_config_section(...) qemu_config_section_impl(__VA_ARGS__, NULL) diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c new file mode 100644 index 0000000000000..621c5e781a7a6 --- /dev/null +++ b/src/vmspawn/vmspawn-qmp.c @@ -0,0 +1,1555 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "sd-event.h" +#include "sd-json.h" +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "blockdev-util.h" +#include "errno-util.h" +#include "ether-addr-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "json-util.h" +#include "log.h" +#include "qmp-client.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "vmspawn-qmp.h" +#include "vmspawn-util.h" + +DEFINE_PRIVATE_HASH_OPS_FULL( + pending_job_hash_ops, + char, string_hash_func, string_compare_func, free, + PendingJob, pending_job_free); + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + block_devices_hash_ops, + char, string_hash_func, string_compare_func, + DriveInfo, drive_info_unref); + +DriveInfo* drive_info_new(void) { + DriveInfo *d = new(DriveInfo, 1); + if (!d) + return NULL; + + *d = (DriveInfo) { + .n_ref = 1, + .fd = -EBADF, + .overlay_fd = -EBADF, + .pcie_port_idx = -1, + }; + return d; +} + +static int vmspawn_qmp_bridge_allocate_pcie_port( + VmspawnQmpBridge *bridge, + const char *owner_id, + char **ret_name, + int *ret_idx) { + + assert(bridge); + assert(owner_id); + assert(ret_name); + assert(ret_idx); + + for (int i = 0; i < VMSPAWN_PCIE_HOTPLUG_SPARES; i++) { + if (bridge->hotplug_port_owner[i]) + continue; + + _cleanup_free_ char *owner = strdup(owner_id), *name = NULL; + if (!owner || asprintf(&name, "vmspawn-hotplug-pci-root-port-%d", i) < 0) + return -ENOMEM; + + bridge->hotplug_port_owner[i] = TAKE_PTR(owner); + *ret_name = TAKE_PTR(name); + *ret_idx = i; + return 0; + } + + return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), + "No free PCIe hotplug port available for owner '%s'.", + owner_id); +} + +static void vmspawn_qmp_bridge_release_pcie_port_by_idx(VmspawnQmpBridge *bridge, int idx) { + assert(bridge); + + if (idx < 0) + return; + + assert(idx < VMSPAWN_PCIE_HOTPLUG_SPARES); + + bridge->hotplug_port_owner[idx] = mfree(bridge->hotplug_port_owner[idx]); +} + +static DriveInfo* drive_info_free(DriveInfo *d) { + assert(d); + + if (d->bridge) + vmspawn_qmp_bridge_release_pcie_port_by_idx(d->bridge, d->pcie_port_idx); + + free(d->path); + free(d->format); + free(d->disk_driver); + free(d->serial); + free(d->pcie_port); + free(d->id); + free(d->qmp_node_name); + free(d->qmp_device_id); + free(d->fdset_path); + sd_varlink_unref(d->link); + safe_close(d->fd); + safe_close(d->overlay_fd); + return mfree(d); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(DriveInfo, drive_info, drive_info_free); + +void drive_infos_done(DriveInfos *infos) { + assert(infos); + FOREACH_ARRAY(d, infos->drives, infos->n_drives) + drive_info_unref(*d); + infos->drives = mfree(infos->drives); + infos->n_drives = 0; +} + +void network_info_done(NetworkInfo *info) { + assert(info); + info->ifname = mfree(info->ifname); + info->pcie_port = mfree(info->pcie_port); + info->fd = safe_close(info->fd); +} + +void virtiofs_info_done(VirtiofsInfo *info) { + assert(info); + info->id = mfree(info->id); + info->socket_path = mfree(info->socket_path); + info->tag = mfree(info->tag); + info->pcie_port = mfree(info->pcie_port); +} + +void virtiofs_infos_done(VirtiofsInfos *infos) { + assert(infos); + FOREACH_ARRAY(e, infos->entries, infos->n_entries) + virtiofs_info_done(e); + infos->entries = mfree(infos->entries); + infos->n_entries = 0; +} + +void vsock_info_done(VsockInfo *info) { + assert(info); + info->pcie_port = mfree(info->pcie_port); + info->fd = safe_close(info->fd); +} + +void machine_config_done(MachineConfig *c) { + if (!c) + return; + + drive_infos_done(&c->drives); + network_info_done(&c->network); + virtiofs_infos_done(&c->virtiofs); + vsock_info_done(&c->vsock); +} + +/* Generic completion callback; userdata is a string literal label. Exits the event loop on boot-time failures. */ +static int on_qmp_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + assert(client); + + VmspawnQmpBridge *bridge = ASSERT_PTR(qmp_client_get_userdata(client)); + const char *label = ASSERT_PTR(userdata); + + if (error < 0) { + log_error_errno(error, "%s failed: %s", label, strna(error_desc)); + + if (bridge->setup_done) + return 0; + + return sd_event_exit(qmp_client_get_event(client), error); + } + + return 0; +} + +/* Send add-fd via SCM_RIGHTS; return /dev/fdset/N and the numeric fdset id. */ +static int qmp_fdset_add( + QmpClient *qmp, + int fd_consume, + qmp_command_callback_t callback, + void *userdata, + char **ret_path, + uint64_t *ret_fdset_id) { + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + _cleanup_close_ int fd = fd_consume; + _cleanup_free_ char *path = NULL; + uint64_t id; + int r; + + assert(qmp); + assert(fd_consume >= 0); + assert(callback); + assert(ret_path); + + id = qmp_client_next_fdset_id(qmp); + + r = sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", id)); + if (r < 0) + return r; + + if (asprintf(&path, "/dev/fdset/%" PRIu64, id) < 0) + return -ENOMEM; + + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "add-fd", QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd)), + callback, userdata); + if (r < 0) + return r; + + *ret_path = TAKE_PTR(path); + if (ret_fdset_id) + *ret_fdset_id = id; + return 0; +} + +/* Issue remove-fd for an fdset whose dup is now held by a blockdev. The fdset + * persists until the dup is closed (in raw_close at blockdev-del time) — see + * QEMU's monitor/fds.c:177-181 on the fds/dup_fds split. */ +static int qmp_fdset_remove( + QmpClient *qmp, + uint64_t fdset_id, + qmp_command_callback_t callback, + void *userdata) { + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + int r; + + assert(qmp); + assert(callback); + + r = sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", fdset_id)); + if (r < 0) + return r; + + return qmp_client_invoke(qmp, /* ret_slot= */ NULL, "remove-fd", QMP_CLIENT_ARGS(args), + callback, userdata); +} + +typedef struct QmpFileNodeParams { + const char *node_name; + const char *filename; + const char *driver; /* "file" or "host_device" */ + QmpDriveFlags flags; +} QmpFileNodeParams; + +/* Build blockdev-add JSON for the protocol-level (file) node */ +static int qmp_build_blockdev_add_file(const QmpFileNodeParams *p, sd_json_variant **ret) { + assert(p); + assert(p->node_name); + assert(p->filename); + assert(p->driver); + assert(ret); + + /* cache.direct=false uses the page cache (QEMU default). cache.no-flush suppresses host + * flush on guest fsync — only safe for ephemeral/extra drives where data loss is acceptable. */ + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("node-name", p->node_name), + SD_JSON_BUILD_PAIR_STRING("driver", p->driver), + SD_JSON_BUILD_PAIR_STRING("filename", p->filename), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(p->flags, QMP_DRIVE_READ_ONLY), "read-only", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(p->flags, QMP_DRIVE_IO_URING), "aio", JSON_BUILD_CONST_STRING("io_uring")), + SD_JSON_BUILD_PAIR("cache", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_BOOLEAN("direct", false), + SD_JSON_BUILD_PAIR_BOOLEAN("no-flush", FLAGS_SET(p->flags, QMP_DRIVE_NO_FLUSH))))); +} + +typedef struct QmpFormatNodeParams { + const char *node_name; + const char *format; /* "raw", "qcow2", etc. */ + const char *file_node_name; /* reference to the underlying file node */ + const char *backing; /* reference to a backing format node (NULL if none) */ + QmpDriveFlags flags; +} QmpFormatNodeParams; + +/* Build blockdev-add JSON for the format-level node */ +static int qmp_build_blockdev_add_format(const QmpFormatNodeParams *p, sd_json_variant **ret) { + assert(p); + assert(p->node_name); + assert(p->format); + assert(p->file_node_name); + assert(ret); + + /* When "file" is a string (not an object), QEMU interprets it as a reference to an + * existing node-name. The "backing" field likewise references a format-level node. */ + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("node-name", p->node_name), + SD_JSON_BUILD_PAIR_STRING("driver", p->format), + SD_JSON_BUILD_PAIR_STRING("file", p->file_node_name), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(p->flags, QMP_DRIVE_READ_ONLY), "read-only", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(p->flags, QMP_DRIVE_DISCARD), "discard", JSON_BUILD_CONST_STRING("unmap")), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(p->flags, QMP_DRIVE_DISCARD_NO_UNREF), "discard-no-unref", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(!!p->backing, "backing", SD_JSON_BUILD_STRING(p->backing))); +} + +static int qmp_build_device_add(const DriveInfo *drive, sd_json_variant **ret) { + assert(drive); + assert(drive->qmp_node_name); + assert(drive->qmp_device_id); + assert(ret); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("driver", drive->disk_driver), + SD_JSON_BUILD_PAIR_STRING("drive", drive->qmp_node_name), + SD_JSON_BUILD_PAIR_STRING("id", drive->qmp_device_id), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(drive->flags, QMP_DRIVE_BOOT), "bootindex", SD_JSON_BUILD_INTEGER(1)), + SD_JSON_BUILD_PAIR_CONDITION(!!drive->serial, "serial", SD_JSON_BUILD_STRING(drive->serial)), + SD_JSON_BUILD_PAIR_CONDITION(STR_IN_SET(drive->disk_driver, "scsi-hd", "scsi-cd"), + "bus", JSON_BUILD_CONST_STRING("vmspawn_scsi.0")), + SD_JSON_BUILD_PAIR_CONDITION( + !STR_IN_SET(drive->disk_driver, "scsi-hd", "scsi-cd") && !!drive->pcie_port, + "bus", SD_JSON_BUILD_STRING(drive->pcie_port))); +} + +/* Inline form: one blockdev-add creates format+file; one blockdev-del tears + * down the whole tree. Used for regular boot drives and hotplug. */ +static int qmp_build_blockdev_add_inline( + const char *node_name, + const char *format, + const char *filename, + const char *file_driver, + QmpDriveFlags flags, + VmspawnQmpFeatureFlags features, + sd_json_variant **ret) { + + bool use_io_uring = FLAGS_SET(features, VMSPAWN_QMP_FEATURE_IO_URING); + bool use_discard_no_unref = FLAGS_SET(flags, QMP_DRIVE_DISCARD_NO_UNREF); + + assert(node_name); + assert(format); + assert(filename); + assert(file_driver); + assert(ret); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("node-name", node_name), + SD_JSON_BUILD_PAIR_STRING("driver", format), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, QMP_DRIVE_READ_ONLY), "read-only", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, QMP_DRIVE_DISCARD), "discard", JSON_BUILD_CONST_STRING("unmap")), + SD_JSON_BUILD_PAIR_CONDITION(use_discard_no_unref, "discard-no-unref", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR("file", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("driver", file_driver), + SD_JSON_BUILD_PAIR_STRING("filename", filename), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, QMP_DRIVE_READ_ONLY), "read-only", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(use_io_uring, "aio", JSON_BUILD_CONST_STRING("io_uring")), + SD_JSON_BUILD_PAIR("cache", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_BOOLEAN("direct", false), + SD_JSON_BUILD_PAIR_BOOLEAN("no-flush", FLAGS_SET(flags, QMP_DRIVE_NO_FLUSH))))))); +} + +/* Issue blockdev-add for a file node. */ +static int qmp_add_file_node(QmpClient *qmp, const QmpFileNodeParams *p) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + int r; + + r = qmp_build_blockdev_add_file(p, &args); + if (r < 0) + return r; + + return qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(args), on_qmp_complete, (void*) "blockdev-add"); +} + +/* Get the virtual size of an image from the fd directly. For raw images the virtual size + * equals the file/device size. For qcow2 the virtual size is a big-endian uint64 at header + * offset 24 (the "size" field in the qcow2 header). */ +static int get_image_virtual_size(int fd, const char *format, bool is_block_device, uint64_t *ret) { + int r; + + assert(fd >= 0); + assert(format); + assert(ret); + + if (streq(format, "raw")) { + if (is_block_device) + return blockdev_get_device_size(fd, ret); + + struct stat st; + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to stat image: %m"); + + r = stat_verify_regular(&st); + if (r < 0) + return log_error_errno(r, "Raw device is neither a regular file nor a block device"); + + *ret = st.st_size; + return 0; + } + + if (streq(format, "qcow2")) { + uint32_t magic = 0; + ssize_t n = pread(fd, &magic, sizeof(magic), 0); + if (n < 0) + return log_error_errno(errno, "Failed to read qcow2 magic: %m"); + if (n != sizeof(magic) || be32toh(magic) != UINT32_C(0x514649fb)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Not a valid qcow2 image (bad magic)"); + + uint64_t size_be = 0; + n = pread(fd, &size_be, sizeof(size_be), 24); + if (n < 0) + return log_error_errno(errno, "Failed to read qcow2 header: %m"); + if (n != sizeof(size_be)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read on qcow2 header"); + + *ret = be64toh(size_be); + return 0; + } + + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported image format '%s'", format); +} + +/* Forward declarations — on_ephemeral_create_concluded routes failures through + * the shared block-device add callbacks defined further below. */ +static int drive_info_add_fail(DriveInfo *d, int error, const char *error_desc); +static int on_add_blockdev_stage(QmpClient *client, sd_json_variant *result, + const char *error_desc, int error, void *userdata); +static int on_add_device_add_complete(QmpClient *client, sd_json_variant *result, + const char *error_desc, int error, void *userdata); + +/* Continuation state for on_ephemeral_create_concluded: overlay format + device_add. */ +typedef struct EphemeralDriveCtx { + DriveInfo *drive; /* ref */ + char *overlay_file_node; + char *base_fmt_node; +} EphemeralDriveCtx; + +static EphemeralDriveCtx* ephemeral_drive_ctx_free(EphemeralDriveCtx *ctx) { + if (!ctx) + return NULL; + drive_info_unref(ctx->drive); + free(ctx->overlay_file_node); + free(ctx->base_fmt_node); + return mfree(ctx); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(EphemeralDriveCtx *, ephemeral_drive_ctx_free); + +static void ephemeral_drive_ctx_free_void(void *p) { + ephemeral_drive_ctx_free(p); +} + +static int on_ephemeral_create_concluded(QmpClient *qmp, void *userdata) { + _cleanup_(ephemeral_drive_ctx_freep) EphemeralDriveCtx *ctx = ASSERT_PTR(userdata); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fmt_args = NULL, *device_args = NULL; + _cleanup_(drive_info_unrefp) DriveInfo *slot_ref = NULL; + DriveInfo *drive = ctx->drive; + int r; + + assert(qmp); + + /* Open formatted overlay as qcow2 with backing reference */ + QmpFormatNodeParams overlay_fmt_params = { + .node_name = drive->qmp_node_name, + .format = "qcow2", + .file_node_name = ctx->overlay_file_node, + .backing = ctx->base_fmt_node, + .flags = drive->flags & (QMP_DRIVE_DISCARD|QMP_DRIVE_DISCARD_NO_UNREF), + }; + r = qmp_build_blockdev_add_format(&overlay_fmt_params, &fmt_args); + if (r < 0) + return drive_info_add_fail(drive, r, NULL); + + slot_ref = drive_info_ref(drive); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(fmt_args), + on_add_blockdev_stage, slot_ref); + if (r < 0) + return drive_info_add_fail(drive, r, NULL); + TAKE_PTR(slot_ref); + + r = qmp_build_device_add(drive, &device_args); + if (r < 0) + return drive_info_add_fail(drive, r, NULL); + + slot_ref = drive_info_ref(drive); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), + on_add_device_add_complete, slot_ref); + if (r < 0) + return drive_info_add_fail(drive, r, NULL); + TAKE_PTR(slot_ref); + + log_debug("Queued ephemeral drive completion for '%s'", drive->qmp_device_id); + return 0; +} + +/* Base image (read-only) + anonymous qcow2 overlay (read-write). Overlay format + * and device_add run from the blockdev-create continuation. */ +static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, DriveInfo *drive) { + int r; + + assert(bridge); + assert(qmp); + assert(drive); + assert(drive->fd >= 0); + assert(drive->overlay_fd >= 0); + + drive->bridge = bridge; + drive->counter = bridge->next_block_counter++; + + _cleanup_free_ char *base_file_node = NULL, *base_fmt_node = NULL, *overlay_file_node = NULL; + if (asprintf(&drive->qmp_node_name, "vmspawn-%" PRIu64 "-storage", drive->counter) < 0 || + asprintf(&drive->qmp_device_id, "vmspawn-%" PRIu64 "-disk", drive->counter) < 0 || + asprintf(&base_file_node, "vmspawn-%" PRIu64 "-base-file", drive->counter) < 0 || + asprintf(&base_fmt_node, "vmspawn-%" PRIu64 "-base-fmt", drive->counter) < 0 || + asprintf(&overlay_file_node, "vmspawn-%" PRIu64 "-overlay-file", drive->counter) < 0) + return log_oom(); + + /* Auto-assigned user id reuses qmp_device_id (matching vmspawn_qmp_add_block_device). */ + if (!drive->id) { + drive->id = strdup(drive->qmp_device_id); + if (!drive->id) + return log_oom(); + } + + /* Read virtual size before passing the fd to QEMU (TAKE_FD consumes it) */ + uint64_t virtual_size; + r = get_image_virtual_size(drive->fd, drive->format, FLAGS_SET(drive->flags, QMP_DRIVE_BLOCK_DEVICE), &virtual_size); + if (r < 0) + return r; + + /* Step 1-2: Pass both fds to QEMU */ + _cleanup_free_ char *base_path = NULL; + uint64_t base_fdset_id; + r = qmp_fdset_add(qmp, TAKE_FD(drive->fd), + on_qmp_complete, (void*) "add-fd", &base_path, &base_fdset_id); + if (r < 0) + return log_error_errno(r, "Failed to send add-fd for base image '%s': %m", drive->path); + + _cleanup_free_ char *overlay_path = NULL; + uint64_t overlay_fdset_id; + r = qmp_fdset_add(qmp, TAKE_FD(drive->overlay_fd), + on_qmp_complete, (void*) "add-fd", &overlay_path, &overlay_fdset_id); + if (r < 0) + return log_error_errno(r, "Failed to send add-fd for overlay of '%s': %m", drive->path); + + /* Step 3: Base image file node (read-only) */ + QmpFileNodeParams base_file_params = { + .node_name = base_file_node, + .filename = base_path, + .driver = FLAGS_SET(drive->flags, QMP_DRIVE_BLOCK_DEVICE) ? "host_device" : "file", + .flags = QMP_DRIVE_READ_ONLY | (drive->flags & QMP_DRIVE_NO_FLUSH), + }; + if (FLAGS_SET(bridge->features, VMSPAWN_QMP_FEATURE_IO_URING)) + base_file_params.flags |= QMP_DRIVE_IO_URING; + r = qmp_add_file_node(qmp, &base_file_params); + if (r < 0) + return log_error_errno(r, "Failed to send blockdev-add for base file '%s': %m", drive->path); + + /* The base file node now holds a dup of the fd; release the monitor's + * original so the fdset auto-frees when raw_close runs at teardown. */ + r = qmp_fdset_remove(qmp, base_fdset_id, on_qmp_complete, (void*) "remove-fd"); + if (r < 0) + return log_error_errno(r, "Failed to send remove-fd for base image '%s': %m", drive->path); + + /* Step 4: Base image format node (read-only) */ + QmpFormatNodeParams base_fmt_params = { + .node_name = base_fmt_node, + .format = drive->format, + .file_node_name = base_file_node, + .flags = QMP_DRIVE_READ_ONLY, + }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *base_fmt_args = NULL; + r = qmp_build_blockdev_add_format(&base_fmt_params, &base_fmt_args); + if (r < 0) + return r; + + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(base_fmt_args), on_qmp_complete, (void*) "blockdev-add"); + if (r < 0) + return log_error_errno(r, "Failed to send blockdev-add for base format '%s': %m", drive->path); + + /* Step 5: Overlay file node (read-write, no io_uring for anon overlay) */ + QmpFileNodeParams overlay_file_params = { + .node_name = overlay_file_node, + .filename = overlay_path, + .driver = "file", + .flags = QMP_DRIVE_NO_FLUSH, + }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *overlay_file_args = NULL; + r = qmp_build_blockdev_add_file(&overlay_file_params, &overlay_file_args); + if (r < 0) + return r; + + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(overlay_file_args), on_qmp_complete, (void*) "blockdev-add"); + if (r < 0) + return log_error_errno(r, "Failed to send blockdev-add for overlay file '%s': %m", drive->path); + + /* Same as for base: the overlay file node has the dup. */ + r = qmp_fdset_remove(qmp, overlay_fdset_id, on_qmp_complete, (void*) "remove-fd"); + if (r < 0) + return log_error_errno(r, "Failed to send remove-fd for overlay of '%s': %m", drive->path); + + /* Step 6: Fire blockdev-create to format the overlay */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *create_options = NULL; + r = sd_json_buildo(&create_options, + SD_JSON_BUILD_PAIR_STRING("driver", "qcow2"), + SD_JSON_BUILD_PAIR_STRING("file", overlay_file_node), + SD_JSON_BUILD_PAIR_UNSIGNED("size", virtual_size), + SD_JSON_BUILD_PAIR_STRING("backing-file", base_fmt_node), + SD_JSON_BUILD_PAIR_STRING("backing-fmt", drive->format)); + if (r < 0) + return log_error_errno(r, "Failed to build blockdev-create options: %m"); + + _cleanup_free_ char *job_id = NULL; + if (asprintf(&job_id, "vmspawn-%" PRIu64 "-overlay-create", drive->counter) < 0) + return log_oom(); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd_args = NULL; + r = sd_json_buildo(&cmd_args, + SD_JSON_BUILD_PAIR_STRING("job-id", job_id), + SD_JSON_BUILD_PAIR_VARIANT("options", create_options)); + if (r < 0) + return log_error_errno(r, "Failed to build blockdev-create JSON: %m"); + + /* Fold DISCARD_NO_UNREF into drive->flags so the continuation's overlay format blockdev-add + * picks it up via drive->flags. */ + if (FLAGS_SET(drive->flags, QMP_DRIVE_DISCARD) && + FLAGS_SET(bridge->features, VMSPAWN_QMP_FEATURE_DISCARD_NO_UNREF)) + drive->flags |= QMP_DRIVE_DISCARD_NO_UNREF; + + /* Register continuation: when the job concludes, fire overlay format + device_add */ + _cleanup_(ephemeral_drive_ctx_freep) EphemeralDriveCtx *ectx = new(EphemeralDriveCtx, 1); + if (!ectx) + return log_oom(); + + *ectx = (EphemeralDriveCtx) { + .drive = drive_info_ref(drive), + .overlay_file_node = strdup(overlay_file_node), + .base_fmt_node = strdup(base_fmt_node), + }; + if (!ectx->overlay_file_node || !ectx->base_fmt_node) + return log_oom(); + + r = vmspawn_qmp_bridge_register_job(bridge, job_id, + on_ephemeral_create_concluded, ectx, + ephemeral_drive_ctx_free_void); + if (r < 0) + return log_error_errno(r, "Failed to register job continuation: %m"); + + TAKE_PTR(ectx); + + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-create", QMP_CLIENT_ARGS(cmd_args), on_qmp_complete, (void*) "blockdev-create"); + if (r < 0) { + _unused_ _cleanup_(pending_job_freep) PendingJob *dead = hashmap_remove(bridge->pending_jobs, job_id); + return log_error_errno(r, "Failed to send blockdev-create for '%s': %m", drive->path); + } + + log_debug("Queued ephemeral drive setup for '%s' (job %s)", drive->path, job_id); + return 0; +} + +static int reply_qmp_error(sd_varlink *link, const char *error_desc, int error) { + assert(link); + + if (ERRNO_IS_DISCONNECT(error)) + return sd_varlink_error(link, "io.systemd.MachineInstance.NotConnected", NULL); + if (error_desc) + log_warning("QMP error: %s", error_desc); + return sd_varlink_error_errno(link, error < 0 ? error : -EIO); +} + +/* After the pipelined remove-fd at add time, QEMU auto-frees the fdset when + * raw_close (during blockdev-del) releases the last dup. So teardown only + * needs to fire blockdev-del. */ +static void vmspawn_qmp_block_device_teardown(QmpClient *client, const char *qmp_node_name, BlockDeviceStateFlags stages) { + assert(client); + assert(qmp_node_name); + + if (!FLAGS_SET(stages, BLOCK_DEVICE_STATE_BLOCKDEV_ADDED)) + return; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + if (sd_json_buildo(&args, SD_JSON_BUILD_PAIR_STRING("node-name", qmp_node_name)) >= 0) + (void) qmp_client_invoke(client, /* ret_slot= */ NULL, "blockdev-del", QMP_CLIENT_ARGS(args), + on_qmp_complete, (void*) "teardown blockdev-del"); +} + +/* Insert into the owning primary map and the non-owning qmp_device_id view. On + * secondary-put failure, roll back the primary so neither map carries a stale entry. */ +static int bridge_register_drive(VmspawnQmpBridge *b, DriveInfo *d) { + int r; + + assert(b); + assert(d); + assert(d->id); + assert(d->qmp_device_id); + + r = hashmap_ensure_put(&b->block_devices, &block_devices_hash_ops, + d->id, drive_info_ref(d)); + if (r < 0) { + drive_info_unref(d); + return r; + } + + r = hashmap_ensure_put(&b->block_devices_by_qmp_id, &string_hash_ops, + d->qmp_device_id, d); + if (r < 0) { + drive_info_unref(hashmap_remove(b->block_devices, d->id)); + return r; + } + + return 0; +} + +/* Drop the drive from both maps; returns the pointer removed from the primary + * (NULL if it wasn't there) so the caller can decide whether to unref. */ +static DriveInfo* bridge_unregister_drive(VmspawnQmpBridge *b, DriveInfo *d) { + assert(b); + assert(d); + + hashmap_remove_value(b->block_devices_by_qmp_id, d->qmp_device_id, d); + return hashmap_remove_value(b->block_devices, d->id, d); +} + +/* First-error entry point: marks FAILED so cascading callbacks no-op, drops + * the registry slot, then replies on the link or exits the loop. */ +static int drive_info_add_fail(DriveInfo *d, int error, const char *error_desc) { + assert(d); + + if (FLAGS_SET(d->state, BLOCK_DEVICE_STATE_ADD_FAILED)) + return 0; + + vmspawn_qmp_block_device_teardown(d->bridge->qmp, d->qmp_node_name, d->state); + d->state = BLOCK_DEVICE_STATE_ADD_FAILED; + + if (bridge_unregister_drive(d->bridge, d)) + drive_info_unref(d); + + if (d->link) { + (void) reply_qmp_error(d->link, error_desc, error); + d->link = sd_varlink_unref(d->link); + return 0; + } + + log_error_errno(error, "Block device '%s' setup failed: %s", + strna(d->id), strna(error_desc)); + + /* Boot-time (link == NULL) is always fatal — even for late-arriving ephemeral replies. */ + return sd_event_exit(qmp_client_get_event(d->bridge->qmp), error); +} + +/* Rolls back the up-front registry insert on a sync error path. */ +static void drive_info_unregister_on_failurep(DriveInfo **dp) { + assert(dp); + + DriveInfo *d = *dp; + if (!d) + return; + d->state |= BLOCK_DEVICE_STATE_ADD_FAILED; + if (bridge_unregister_drive(d->bridge, d)) + drive_info_unref(d); +} + +/* Shared by the intermediate stages that don't need to record a rollback bit + * (add-fd, remove-fd). Just forwards errors to drive_info_add_fail so cascades + * from earlier stage failures get suppressed via the FAILED sentinel. */ +static int on_add_observe_stage( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(drive_info_unrefp) DriveInfo *d = ASSERT_PTR(userdata); + assert(client); + + if (error < 0) + return drive_info_add_fail(d, error, error_desc); + return 0; +} + +static int on_add_blockdev_stage( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(drive_info_unrefp) DriveInfo *d = ASSERT_PTR(userdata); + assert(client); + + if (error < 0) + return drive_info_add_fail(d, error, error_desc); + + /* A sync error after blockdev-add was queued may have marked the chain FAILED. + * The blockdev node we just created is orphaned — tear it down retroactively + * and don't claim BLOCKDEV_ADDED on a drive that's already been unregistered. */ + if (FLAGS_SET(d->state, BLOCK_DEVICE_STATE_ADD_FAILED)) { + vmspawn_qmp_block_device_teardown(d->bridge->qmp, d->qmp_node_name, + BLOCK_DEVICE_STATE_BLOCKDEV_ADDED); + return 0; + } + + d->state |= BLOCK_DEVICE_STATE_BLOCKDEV_ADDED; + return 0; +} + +static int on_add_device_add_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(drive_info_unrefp) DriveInfo *d = ASSERT_PTR(userdata); + + assert(client); + + if (error < 0) + return drive_info_add_fail(d, error, error_desc); + + if (FLAGS_SET(d->state, BLOCK_DEVICE_STATE_ADD_FAILED)) + return 0; + + if (d->link) { + (void) sd_varlink_replybo(d->link, SD_JSON_BUILD_PAIR_STRING("id", d->id)); + d->link = sd_varlink_unref(d->link); + } + + log_info("Block device '%s' attached", d->id); + return 0; +} + +static int on_scsi_controller_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + assert(client); + + VmspawnQmpBridge *bridge = ASSERT_PTR(qmp_client_get_userdata(client)); + + if (error < 0) { + /* QEMU's device_add is transactional — on error it calls object_unparent() + * before replying, so the "vmspawn_scsi" id is free for the next retry. */ + vmspawn_qmp_bridge_release_pcie_port_by_idx(bridge, bridge->scsi_controller_port_idx); + bridge->scsi_controller_port_idx = -1; + bridge->scsi_controller_created = false; + log_warning("virtio-scsi-pci controller setup failed: %s", strna(error_desc)); + if (!bridge->setup_done) + return sd_event_exit(qmp_client_get_event(client), error); + } + + return 0; +} + +static int qmp_setup_scsi_controller(VmspawnQmpBridge *bridge, const char *pcie_port) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + int r; + + assert(bridge); + + r = sd_json_buildo( + &args, + SD_JSON_BUILD_PAIR_STRING("driver", "virtio-scsi-pci"), + SD_JSON_BUILD_PAIR_STRING("id", "vmspawn_scsi"), + SD_JSON_BUILD_PAIR_CONDITION(!!pcie_port, "bus", SD_JSON_BUILD_STRING(pcie_port))); + if (r < 0) + return log_error_errno(r, "Failed to build SCSI controller JSON: %m"); + + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(args), + on_scsi_controller_complete, NULL); + if (r < 0) + return log_error_errno(r, "Failed to send SCSI controller device_add: %m"); + + log_debug("Queued virtio-scsi-pci controller setup"); + return 0; +} + +static int vmspawn_qmp_add_block_device(VmspawnQmpBridge *bridge, DriveInfo *drive) { + int r; + + assert(bridge); + assert(drive); + assert(drive->format); + assert(drive->disk_driver); + assert(drive->fd >= 0); + + _unused_ _cleanup_(drive_info_unrefp) DriveInfo *owned = drive; + _cleanup_(drive_info_unrefp) DriveInfo *slot_ref = NULL; + _cleanup_(drive_info_unregister_on_failurep) DriveInfo *registered = NULL; + + drive->bridge = bridge; + drive->counter = bridge->next_block_counter++; + if (asprintf(&drive->qmp_node_name, "vmspawn-%" PRIu64 "-storage", drive->counter) < 0) + return log_oom(); + if (asprintf(&drive->qmp_device_id, "vmspawn-%" PRIu64 "-disk", drive->counter) < 0) + return log_oom(); + /* Auto-assigned user ids reuse qmp_device_id. */ + if (!drive->id) { + drive->id = strdup(drive->qmp_device_id); + if (!drive->id) + return log_oom(); + } + + /* Reserve the registry slot up-front so the device_add callback's commit can't fail. */ + r = bridge_register_drive(bridge, drive); + if (r < 0) + return r; + registered = drive; + + /* First SCSI hotplug needs a virtio-scsi-pci controller to attach to. */ + if (STR_IN_SET(drive->disk_driver, "scsi-hd", "scsi-cd") && !bridge->scsi_controller_created) { + _cleanup_free_ char *controller_port = NULL; + int controller_port_idx = -1; + if (ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS) { + r = vmspawn_qmp_bridge_allocate_pcie_port(bridge, "vmspawn_scsi", + &controller_port, &controller_port_idx); + if (r == -EBUSY) + return log_error_errno(r, "No PCIe hotplug ports left for SCSI controller"); + if (r < 0) + return log_error_errno(r, "Failed to allocate PCIe hotplug port for SCSI controller: %m"); + } + + r = qmp_setup_scsi_controller(bridge, controller_port); + if (r < 0) { + vmspawn_qmp_bridge_release_pcie_port_by_idx(bridge, controller_port_idx); + return r; + } + + /* Set before the reply so a second SCSI hotplug queued in the meantime + * doesn't re-create the controller; reset in on_scsi_controller_complete on error. */ + bridge->scsi_controller_port_idx = controller_port_idx; + bridge->scsi_controller_created = true; + } + + uint64_t fdset_id; + slot_ref = drive_info_ref(drive); + r = qmp_fdset_add(bridge->qmp, TAKE_FD(drive->fd), + on_add_observe_stage, slot_ref, &drive->fdset_path, &fdset_id); + if (r < 0) + return r; + TAKE_PTR(slot_ref); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *blockdev_args = NULL; + r = qmp_build_blockdev_add_inline( + drive->qmp_node_name, drive->format, drive->fdset_path, + FLAGS_SET(drive->flags, QMP_DRIVE_BLOCK_DEVICE) ? "host_device" : "file", + drive->flags, bridge->features, &blockdev_args); + if (r < 0) + return r; + + slot_ref = drive_info_ref(drive); + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(blockdev_args), + on_add_blockdev_stage, slot_ref); + if (r < 0) + return r; + TAKE_PTR(slot_ref); + + /* Release the monitor's original fd; the blockdev-add above took a dup. */ + slot_ref = drive_info_ref(drive); + r = qmp_fdset_remove(bridge->qmp, fdset_id, on_add_observe_stage, slot_ref); + if (r < 0) + return r; + TAKE_PTR(slot_ref); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *device_args = NULL; + r = qmp_build_device_add(drive, &device_args); + if (r < 0) + return r; + + slot_ref = drive_info_ref(drive); + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), + on_add_device_add_complete, slot_ref); + if (r < 0) + return r; + TAKE_PTR(slot_ref); + + TAKE_PTR(registered); + return 0; +} + +static int qmp_setup_regular_drive(VmspawnQmpBridge *bridge, DriveInfo *drive) { + assert(bridge); + assert(drive); + assert(drive->fd >= 0); + assert(!drive->id); + + return vmspawn_qmp_add_block_device(bridge, drive); +} + +/* device_del completion is just QEMU acking the request; teardown happens + * in vmspawn_qmp_dispatch_device_deleted() once the guest acks the eject. */ +static int on_remove_device_del_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(drive_info_unrefp) DriveInfo *drive = ASSERT_PTR(userdata); + _cleanup_(sd_varlink_unrefp) sd_varlink *link = TAKE_PTR(drive->link); + + assert(client); + assert(link); + + if (error < 0) { + /* device_del rejected: clear the pending bit so the caller can retry. */ + drive->state &= ~BLOCK_DEVICE_STATE_REMOVE_PENDING; + + return reply_qmp_error(link, error_desc, error); + } + + return sd_varlink_reply(link, NULL); +} + +int vmspawn_qmp_remove_block_device(VmspawnQmpBridge *bridge, sd_varlink *link, const char *id) { + int r; + + assert(bridge); + assert(link); + assert(id); + + DriveInfo *drive = hashmap_get(bridge->block_devices, id); + if (!drive) + return reply_qmp_error(link, "Unknown block device id", -ENOENT); + if (!FLAGS_SET(drive->state, BLOCK_DEVICE_STATE_BLOCKDEV_ADDED)) + return reply_qmp_error(link, "Block device add pending", -EBUSY); + if (FLAGS_SET(drive->state, BLOCK_DEVICE_STATE_REMOVE_PENDING)) + return reply_qmp_error(link, "Block device removal pending", -EBUSY); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + r = sd_json_buildo(&args, SD_JSON_BUILD_PAIR_STRING("id", drive->qmp_device_id)); + if (r < 0) + return sd_varlink_error_errno(link, r); + + assert(!drive->link); + drive->link = sd_varlink_ref(link); + drive->state |= BLOCK_DEVICE_STATE_REMOVE_PENDING; + + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "device_del", QMP_CLIENT_ARGS(args), + on_remove_device_del_complete, drive_info_ref(drive)); + if (r < 0) { + drive->link = sd_varlink_unref(drive->link); + drive->state &= ~BLOCK_DEVICE_STATE_REMOVE_PENDING; + drive_info_unref(drive); + return sd_varlink_error_errno(link, r); + } + return 0; +} + +/* DEVICE_DELETED arrives once the guest has acked the eject; only then is it + * safe to drop the blockdev node and release the registry slot (and PCIe port). */ +int vmspawn_qmp_dispatch_device_deleted(VmspawnQmpBridge *bridge, sd_json_variant *data) { + assert(bridge); + + if (!data) + return 0; + + const char *qmp_device_id = sd_json_variant_string(sd_json_variant_by_key(data, "device")); + if (!qmp_device_id) + return 0; + + DriveInfo *drive = hashmap_get(bridge->block_devices_by_qmp_id, qmp_device_id); + if (!drive) + return 0; + + vmspawn_qmp_block_device_teardown(bridge->qmp, drive->qmp_node_name, drive->state); + + assert_se(bridge_unregister_drive(bridge, drive) == drive); + drive_info_unref(drive); + return 0; +} + +int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *netdev_args = NULL, *device_args = NULL; + bool tap_by_fd; + int r; + + assert(bridge); + + QmpClient *qmp = vmspawn_qmp_bridge_get_qmp(bridge); + assert(network); + assert(network->type); + + tap_by_fd = streq(network->type, "tap") && network->fd >= 0; + + /* For TAP-by-fd: pass the TAP fd to QEMU via getfd + SCM_RIGHTS, then reference it by name + * in netdev_add. QEMU stores the received fd under the given fdname and closes it on removal. */ + if (tap_by_fd) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *getfd_args = NULL; + + r = sd_json_buildo( + &getfd_args, + SD_JSON_BUILD_PAIR_STRING("fdname", "vmspawn_tap")); + if (r < 0) + return log_error_errno(r, "Failed to build getfd JSON: %m"); + + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "getfd", QMP_CLIENT_ARGS_FD(getfd_args, TAKE_FD(network->fd)), + on_qmp_complete, (void*) "getfd"); + if (r < 0) + return log_error_errno(r, "Failed to send getfd for TAP fd: %m"); + } + + /* netdev_add: create the network backend */ + r = sd_json_buildo( + &netdev_args, + SD_JSON_BUILD_PAIR_STRING("type", network->type), + SD_JSON_BUILD_PAIR_STRING("id", "net0"), + SD_JSON_BUILD_PAIR_CONDITION(tap_by_fd, + "fd", JSON_BUILD_CONST_STRING("vmspawn_tap")), + SD_JSON_BUILD_PAIR_CONDITION(!tap_by_fd && !!network->ifname, + "ifname", SD_JSON_BUILD_STRING(network->ifname)), + SD_JSON_BUILD_PAIR_CONDITION(!tap_by_fd && streq(network->type, "tap"), + "script", JSON_BUILD_CONST_STRING("no")), + SD_JSON_BUILD_PAIR_CONDITION(!tap_by_fd && streq(network->type, "tap"), + "downscript", JSON_BUILD_CONST_STRING("no"))); + if (r < 0) + return log_error_errno(r, "Failed to build netdev_add JSON: %m"); + + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "netdev_add", QMP_CLIENT_ARGS(netdev_args), on_qmp_complete, (void*) "netdev_add"); + if (r < 0) + return log_error_errno(r, "Failed to send netdev_add: %m"); + + /* device_add: attach NIC frontend */ + r = sd_json_buildo( + &device_args, + SD_JSON_BUILD_PAIR_STRING("driver", "virtio-net-pci"), + SD_JSON_BUILD_PAIR_STRING("netdev", "net0"), + SD_JSON_BUILD_PAIR_STRING("id", "nic0"), + SD_JSON_BUILD_PAIR_CONDITION(network->mac_set, + "mac", SD_JSON_BUILD_STRING(network->mac_set ? ETHER_ADDR_TO_STR(&network->mac) : NULL)), + SD_JSON_BUILD_PAIR_CONDITION(!!network->pcie_port, + "bus", SD_JSON_BUILD_STRING(network->pcie_port))); + if (r < 0) + return log_error_errno(r, "Failed to build NIC device_add JSON: %m"); + + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_complete, (void*) "device_add"); + if (r < 0) + return log_error_errno(r, "Failed to send NIC device_add: %m"); + + log_debug("Queued %s network setup%s", network->type, tap_by_fd ? " (fd via getfd)" : ""); + return 0; +} + +static int vmspawn_qmp_setup_one_virtiofs(QmpClient *qmp, const VirtiofsInfo *vfs) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *chardev_args = NULL, *device_args = NULL; + int r; + + assert(qmp); + assert(vfs); + assert(vfs->id); + assert(vfs->socket_path); + assert(vfs->tag); + + /* chardev-add: connect to virtiofsd socket. + * ChardevBackend and SocketAddressLegacy are QAPI legacy unions with explicit "data" + * wrapper objects at each level — the nesting is mandatory on the wire. */ + r = sd_json_buildo( + &chardev_args, + SD_JSON_BUILD_PAIR_STRING("id", vfs->id), + SD_JSON_BUILD_PAIR("backend", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("type", "socket"), + SD_JSON_BUILD_PAIR("data", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("type", "unix"), + SD_JSON_BUILD_PAIR("data", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("path", vfs->socket_path))))), + SD_JSON_BUILD_PAIR_BOOLEAN("server", false)))))); + if (r < 0) + return log_error_errno(r, "Failed to build chardev-add JSON for '%s': %m", vfs->id); + + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "chardev-add", QMP_CLIENT_ARGS(chardev_args), on_qmp_complete, (void*) "chardev-add"); + if (r < 0) + return log_error_errno(r, "Failed to send chardev-add '%s': %m", vfs->id); + + /* device_add: create vhost-user-fs-pci device */ + r = sd_json_buildo( + &device_args, + SD_JSON_BUILD_PAIR_STRING("driver", "vhost-user-fs-pci"), + SD_JSON_BUILD_PAIR_STRING("id", vfs->id), + SD_JSON_BUILD_PAIR_STRING("chardev", vfs->id), + SD_JSON_BUILD_PAIR_STRING("tag", vfs->tag), + SD_JSON_BUILD_PAIR_UNSIGNED("queue-size", 1024), + SD_JSON_BUILD_PAIR_CONDITION(!!vfs->pcie_port, + "bus", SD_JSON_BUILD_STRING(vfs->pcie_port))); + if (r < 0) + return log_error_errno(r, "Failed to build virtiofs device_add JSON for '%s': %m", vfs->id); + + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_complete, (void*) "device_add"); + if (r < 0) + return log_error_errno(r, "Failed to send virtiofs device_add '%s': %m", vfs->id); + + log_debug("Queued virtiofs device '%s' (tag=%s)", vfs->id, vfs->tag); + return 0; +} + +int vmspawn_qmp_setup_virtiofs(VmspawnQmpBridge *bridge, const VirtiofsInfos *virtiofs) { + int r; + + assert(bridge); + + QmpClient *qmp = vmspawn_qmp_bridge_get_qmp(bridge); + assert(virtiofs); + + FOREACH_ARRAY(e, virtiofs->entries, virtiofs->n_entries) { + r = vmspawn_qmp_setup_one_virtiofs(qmp, e); + if (r < 0) + return r; + } + + return 0; +} + +int vmspawn_qmp_setup_vsock(VmspawnQmpBridge *bridge, VsockInfo *vsock) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *getfd_args = NULL, *device_args = NULL; + int r; + + assert(bridge); + assert(vsock); + + if (vsock->fd < 0) + return 0; + + QmpClient *qmp = vmspawn_qmp_bridge_get_qmp(bridge); + + /* getfd: pass the vhost-vsock fd to QEMU via SCM_RIGHTS */ + r = sd_json_buildo( + &getfd_args, + SD_JSON_BUILD_PAIR_STRING("fdname", "vmspawn_vsock")); + if (r < 0) + return log_error_errno(r, "Failed to build getfd JSON for VSOCK: %m"); + + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "getfd", QMP_CLIENT_ARGS_FD(getfd_args, TAKE_FD(vsock->fd)), + on_qmp_complete, (void*) "getfd"); + if (r < 0) + return log_error_errno(r, "Failed to send getfd for VSOCK fd: %m"); + + /* device_add: create vhost-vsock-pci device referencing the named fd */ + r = sd_json_buildo( + &device_args, + SD_JSON_BUILD_PAIR_STRING("driver", "vhost-vsock-pci"), + SD_JSON_BUILD_PAIR_STRING("id", "vsock0"), + SD_JSON_BUILD_PAIR_UNSIGNED("guest-cid", vsock->cid), + SD_JSON_BUILD_PAIR_STRING("vhostfd", "vmspawn_vsock"), + SD_JSON_BUILD_PAIR_CONDITION(!!vsock->pcie_port, + "bus", SD_JSON_BUILD_STRING(vsock->pcie_port))); + if (r < 0) + return log_error_errno(r, "Failed to build VSOCK device_add JSON: %m"); + + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_complete, (void*) "device_add"); + if (r < 0) + return log_error_errno(r, "Failed to send VSOCK device_add: %m"); + + log_debug("Queued vhost-vsock-pci device setup (cid=%u)", vsock->cid); + return 0; +} + +int vmspawn_qmp_setup_drives(VmspawnQmpBridge *bridge, DriveInfos *drives) { + int r; + + assert(bridge); + assert(drives); + + QmpClient *qmp = vmspawn_qmp_bridge_get_qmp(bridge); + + /* io_uring support was probed during vmspawn_qmp_init(). The cached result in + * bridge->features is passed to each file node setup call. SCSI controller + * creation is handled on-demand by vmspawn_qmp_add_block_device() for the first + * SCSI drive, using the hotplug-spares pool. */ + + FOREACH_ARRAY(d, drives->drives, drives->n_drives) { + if ((*d)->overlay_fd >= 0) + r = qmp_setup_ephemeral_drive(bridge, qmp, *d); + else + r = qmp_setup_regular_drive(bridge, TAKE_PTR(*d)); + if (r < 0) + return r; + } + + return 0; +} + +PendingJob* pending_job_free(PendingJob *j) { + if (!j) + return NULL; + if (j->free_userdata) + j->free_userdata(j->userdata); + return mfree(j); +} + +VmspawnQmpBridge* vmspawn_qmp_bridge_free(VmspawnQmpBridge *b) { + if (!b) + return NULL; + + /* Unref first: pending QMP callbacks may release hotplug ports through the bridge. */ + qmp_client_unref(b->qmp); + + hashmap_free(b->block_devices_by_qmp_id); + hashmap_free(b->block_devices); + hashmap_free(b->pending_jobs); + + FOREACH_ELEMENT(owner, b->hotplug_port_owner) + free(*owner); + + return mfree(b); +} + +int vmspawn_qmp_bridge_register_job( + VmspawnQmpBridge *b, + const char *job_id, + pending_job_callback_t on_concluded, + void *userdata, + pending_job_free_t free_userdata) { + + _cleanup_free_ PendingJob *job = NULL; + _cleanup_free_ char *id = NULL; + int r; + + assert(b); + assert(job_id); + + id = strdup(job_id); + if (!id) + return -ENOMEM; + + job = new(PendingJob, 1); + if (!job) + return -ENOMEM; + + *job = (PendingJob) { + .on_concluded = on_concluded, + .free_userdata = free_userdata, + .userdata = userdata, + }; + + r = hashmap_ensure_put(&b->pending_jobs, &pending_job_hash_ops, id, job); + if (r < 0) + return r; + + TAKE_PTR(id); + TAKE_PTR(job); + return 0; +} + +QmpClient* vmspawn_qmp_bridge_get_qmp(VmspawnQmpBridge *b) { + assert(b); + return b->qmp; +} + +/* Probe-reply convention: ignore -EIO (QMP rejection = "feature absent", log at debug + * and leave the feature flag clear) and transport errors (caught by the post-loop + * qmp_client_is_disconnected() check in vmspawn_qmp_probe_features()). Cleanup calls + * are best-effort — failing to delete a private probe node leaves a harmless /dev/null + * blockdev in QEMU until it exits. */ + +static int on_io_uring_probe_del_reply( + QmpClient *c, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + assert(c); + + if (error_desc) + log_debug("Failed to remove io_uring probe node: %s", error_desc); + return 0; +} + +static int on_io_uring_probe_add_reply( + QmpClient *c, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + VmspawnQmpBridge *bridge = ASSERT_PTR(userdata); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *del_args = NULL; + int r; + + assert(c); + + if (error < 0 && !error_desc) + return log_debug_errno(error, "io_uring probe did not execute: %m"); + if (error_desc) { + log_debug("QEMU does not support aio=io_uring: %s", error_desc); + return 0; + } + + bridge->features |= VMSPAWN_QMP_FEATURE_IO_URING; + log_debug("QEMU supports aio=io_uring"); + + /* Best-effort cleanup; the chained reply keeps the pump busy via the slots set. */ + r = sd_json_buildo(&del_args, + SD_JSON_BUILD_PAIR_STRING("node-name", "__io_uring_probe")); + if (r < 0) + return r; + + return qmp_client_invoke(c, /* ret_slot= */ NULL, "blockdev-del", QMP_CLIENT_ARGS(del_args), + on_io_uring_probe_del_reply, bridge); +} + +static int probe_io_uring(QmpClient *c, VmspawnQmpBridge *bridge) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + int r; + + assert(c); + assert(bridge); + + r = sd_json_buildo( + &args, + SD_JSON_BUILD_PAIR_STRING("node-name", "__io_uring_probe"), + SD_JSON_BUILD_PAIR_STRING("driver", "file"), + SD_JSON_BUILD_PAIR_STRING("filename", "/dev/null"), + SD_JSON_BUILD_PAIR_BOOLEAN("read-only", true), + SD_JSON_BUILD_PAIR_STRING("aio", "io_uring")); + if (r < 0) + return r; + + return qmp_client_invoke(c, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(args), + on_io_uring_probe_add_reply, bridge); +} + +static int on_probe_schema_reply( + QmpClient *c, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + VmspawnQmpBridge *bridge = ASSERT_PTR(userdata); + + assert(c); + + if (error < 0 && !error_desc) + return log_debug_errno(error, "query-qmp-schema probe did not execute: %m"); + if (error_desc) { + log_debug("query-qmp-schema rejected: %s", error_desc); + return 0; + } + + if (qmp_schema_has_member(result, "discard-no-unref")) { + bridge->features |= VMSPAWN_QMP_FEATURE_DISCARD_NO_UNREF; + log_debug("QEMU supports qcow2 discard-no-unref"); + } else + log_debug("QEMU does not support qcow2 discard-no-unref"); + + return 0; +} + +static int probe_schema(QmpClient *c, VmspawnQmpBridge *bridge) { + assert(c); + assert(bridge); + + return qmp_client_invoke(c, /* ret_slot= */ NULL, "query-qmp-schema", QMP_CLIENT_ARGS(NULL), + on_probe_schema_reply, bridge); +} + +int vmspawn_qmp_init(VmspawnQmpBridge **ret, int fd, sd_event *event) { + _cleanup_(vmspawn_qmp_bridge_freep) VmspawnQmpBridge *bridge = NULL; + int r; + + assert(ret); + assert(fd >= 0); + assert(event); + + bridge = new0(VmspawnQmpBridge, 1); + if (!bridge) + return log_oom(); + + bridge->scsi_controller_port_idx = -1; + + r = qmp_client_connect_fd(&bridge->qmp, fd); + if (r < 0) + return log_error_errno(r, "Failed to create QMP client: %m"); + + r = qmp_client_set_description(bridge->qmp, "vmspawn-qmp-client"); + if (r < 0) + return log_error_errno(r, "Failed to set QMP client description: %m"); + + r = qmp_client_attach_event(bridge->qmp, event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach QMP client to event loop: %m"); + + *ret = TAKE_PTR(bridge); + return 0; +} + +int vmspawn_qmp_probe_features(VmspawnQmpBridge *bridge) { + int r; + + assert(bridge); + + /* probe_io_uring() and probe_schema() both call qmp_client_invoke(), which internally + * drives the handshake to RUNNING via qmp_client_ensure_running() on its first call. */ + r = probe_io_uring(bridge->qmp, bridge); + if (r < 0) + return log_error_errno(r, "Failed to issue io_uring probe: %m"); + + r = probe_schema(bridge->qmp, bridge); + if (r < 0) + return log_error_errno(r, "Failed to issue schema probe: %m"); + + /* Canonical sync-on-async pump, matching varlink_call_internal(). The QMP client tracks + * outstanding replies in its own slots set; drain until it's idle. */ + while (!qmp_client_is_idle(bridge->qmp)) { + r = qmp_client_process(bridge->qmp); + if (r < 0) + return log_error_errno(r, "QMP probe pump failed: %m"); + if (r > 0) + continue; + + r = qmp_client_wait(bridge->qmp, USEC_INFINITY); + if (r < 0) + return log_error_errno(r, "QMP probe wait failed: %m"); + } + + /* If fail_pending() drained the slots (transport dropped mid-probe), features can't be + * trusted and we have no QMP channel for device setup anyway. */ + if (qmp_client_is_disconnected(bridge->qmp)) + return log_error_errno(SYNTHETIC_ERRNO(ECONNRESET), + "QMP connection dropped during feature probing"); + + return 0; +} + +static int on_cont_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + assert(client); + + VmspawnQmpBridge *bridge = ASSERT_PTR(userdata); + + if (error < 0) { + log_error_errno(error, "Failed to resume QEMU execution: %s", strna(error_desc)); + return sd_event_exit(qmp_client_get_event(client), error); + } + + /* VM is running — all boot-time device setup has completed. */ + bridge->setup_done = true; + return 0; +} + +int vmspawn_qmp_start(VmspawnQmpBridge *bridge) { + assert(bridge); + + return qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "cont", /* args= */ NULL, on_cont_complete, bridge); +} diff --git a/src/vmspawn/vmspawn-qmp.h b/src/vmspawn/vmspawn-qmp.h new file mode 100644 index 0000000000000..d8403520c9afe --- /dev/null +++ b/src/vmspawn/vmspawn-qmp.h @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "machine-util.h" +#include "shared-forward.h" + +#define VMSPAWN_PCIE_HOTPLUG_SPARES 10 + +/* Pending job continuation — called when a QMP background job reaches "concluded" state. + * Used by blockdev-create to chain remaining drive setup after the job completes. */ +typedef int (*pending_job_callback_t)(QmpClient *qmp, void *userdata); +typedef void (*pending_job_free_t)(void *userdata); + +typedef struct PendingJob { + pending_job_callback_t on_concluded; + pending_job_free_t free_userdata; + void *userdata; +} PendingJob; + +PendingJob* pending_job_free(PendingJob *j); +DEFINE_TRIVIAL_CLEANUP_FUNC(PendingJob *, pending_job_free); + +typedef enum VmspawnQmpFeatureFlags { + VMSPAWN_QMP_FEATURE_IO_URING = 1u << 0, + VMSPAWN_QMP_FEATURE_DISCARD_NO_UNREF = 1u << 1, +} VmspawnQmpFeatureFlags; + +typedef struct VmspawnQmpBridge { + QmpClient *qmp; + Hashmap *pending_jobs; /* blockdev-create continuations */ + Hashmap *block_devices; /* user_id (char*) → DriveInfo* (owned ref) */ + Hashmap *block_devices_by_qmp_id; /* qmp_device_id (char*) → DriveInfo* (non-owning view) */ + char *hotplug_port_owner[VMSPAWN_PCIE_HOTPLUG_SPARES]; /* owner id per port; NULL = free */ + int scsi_controller_port_idx; /* hotplug port idx taken by virtio-scsi-pci, -1 if none */ + uint64_t next_block_counter; /* monotonic counter feeding internal QMP names (vmspawn--*) */ + VmspawnQmpFeatureFlags features; + bool setup_done; + bool scsi_controller_created; /* virtio-scsi-pci has been device_add'd */ +} VmspawnQmpBridge; + +VmspawnQmpBridge* vmspawn_qmp_bridge_free(VmspawnQmpBridge *b); +DEFINE_TRIVIAL_CLEANUP_FUNC(VmspawnQmpBridge *, vmspawn_qmp_bridge_free); + +QmpClient* vmspawn_qmp_bridge_get_qmp(VmspawnQmpBridge *b); + +/* Phase 1: Connect to VMM backend. Returns an opaque bridge ready for device setup. */ +int vmspawn_qmp_init(VmspawnQmpBridge **ret, int fd, sd_event *event); + +/* Phase 1b: Feature probing. Fires one-shot QMP commands and drives the client + * synchronously until every reply has been delivered. Populates bridge->features. + * Must run before the device-setup phase; both io_uring and discard-no-unref flags + * are consumed by vmspawn_qmp_setup_drives(). */ +int vmspawn_qmp_probe_features(VmspawnQmpBridge *bridge); + +/* Phase 3: Resume vCPUs. All commands are async — responses arrive during sd_event_loop(). */ +int vmspawn_qmp_start(VmspawnQmpBridge *bridge); + +int vmspawn_qmp_bridge_register_job( + VmspawnQmpBridge *b, + const char *job_id, + pending_job_callback_t on_concluded, + void *userdata, + pending_job_free_t free_userdata); + +typedef enum QmpDriveFlags { + QMP_DRIVE_BLOCK_DEVICE = 1u << 0, + QMP_DRIVE_READ_ONLY = 1u << 1, + QMP_DRIVE_DISCARD = 1u << 2, + QMP_DRIVE_NO_FLUSH = 1u << 3, + QMP_DRIVE_BOOT = 1u << 4, + QMP_DRIVE_IO_URING = 1u << 5, + QMP_DRIVE_DISCARD_NO_UNREF = 1u << 6, /* qcow2 only */ +} QmpDriveFlags; + +typedef enum BlockDeviceStateFlags { + BLOCK_DEVICE_STATE_BLOCKDEV_ADDED = 1u << 0, + BLOCK_DEVICE_STATE_ADD_FAILED = 1u << 1, /* first error fired; suppress cascades */ + BLOCK_DEVICE_STATE_REMOVE_PENDING = 1u << 2, /* device_del in flight; reject concurrent removes */ +} BlockDeviceStateFlags; + +/* Ref-counted; each of the four add-stage QMP slots holds one ref. + * + * link == NULL → boot-time: failure calls sd_event_exit (if !setup_done). + * link != NULL → hotplug: failure replies via the varlink link. */ +typedef struct DriveInfo { + unsigned n_ref; + + /* Config */ + char *path; /* original path (for logging; not passed to QEMU) */ + char *format; /* "raw" or "qcow2" */ + char *disk_driver; /* "virtio-blk-pci", "scsi-hd", "scsi-cd", "nvme" */ + char *serial; + char *pcie_port; /* pcie-root-port id for device_add bus (NULL on non-PCIe) */ + int fd; /* pre-opened image fd (-EBADF if unused) */ + int overlay_fd; /* pre-opened anonymous overlay fd for ephemeral (-EBADF if unused) */ + QmpDriveFlags flags; + + /* Per-add-op state (populated by the add flow; zeroed at CLI-parse time) */ + VmspawnQmpBridge *bridge; /* weak */ + char *id; /* varlink-visible id (caller-supplied, or falls back to qmp_device_id) */ + DiskType disk_type; /* for the ListBlockDevices `driver` field */ + uint64_t counter; /* internal N used in qmp_node_name / qmp_device_id */ + char *qmp_node_name; /* "vmspawn--storage" */ + char *qmp_device_id; /* "vmspawn--disk" */ + char *fdset_path; /* "/dev/fdset/N" */ + int pcie_port_idx; /* hotplug port idx held by this drive; -1 once committed or unused */ + BlockDeviceStateFlags state; + sd_varlink *link; /* ref'd iff hotplug */ +} DriveInfo; + +DriveInfo* drive_info_new(void); +DECLARE_TRIVIAL_REF_UNREF_FUNC(DriveInfo, drive_info); +DEFINE_TRIVIAL_CLEANUP_FUNC(DriveInfo *, drive_info_unref); + +typedef struct DriveInfos { + DriveInfo **drives; /* array of individually heap-allocated entries */ + size_t n_drives; +} DriveInfos; + +void drive_infos_done(DriveInfos *infos); + +/* Network info for QMP-based network setup. Covers privileged TAP (by name), + * nsresourced TAP (by FD via getfd), and user-mode networking. The no-network + * case (-nic none) stays on the QEMU command line. */ +typedef struct NetworkInfo { + const char *type; /* "tap" or "user" — points to a string literal */ + char *ifname; /* owned: TAP interface name (tap by name only, NULL if unset) */ + struct ether_addr mac; /* VM-side MAC address (tap only, valid iff mac_set) */ + bool mac_set; + char *pcie_port; /* owned: pcie-root-port id for device_add bus (NULL on non-PCIe) */ + int fd; /* TAP fd to pass via getfd (tap by fd only, -EBADF if unused) */ +} NetworkInfo; + +void network_info_done(NetworkInfo *info); + +/* Virtiofs device info for QMP-based chardev + device setup */ +typedef struct VirtiofsInfo { + char *id; /* owned: chardev and device id (e.g. "rootdir", "mnt0") */ + char *socket_path; /* owned: virtiofsd listen socket path */ + char *tag; /* owned: virtiofs mount tag visible to guest */ + char *pcie_port; /* owned: pcie-root-port id for device_add bus (NULL on non-PCIe) */ +} VirtiofsInfo; + +void virtiofs_info_done(VirtiofsInfo *info); + +typedef struct VirtiofsInfos { + VirtiofsInfo *entries; + size_t n_entries; +} VirtiofsInfos; + +void virtiofs_infos_done(VirtiofsInfos *infos); + +/* VSOCK device info for QMP-based setup via getfd + device_add */ +typedef struct VsockInfo { + int fd; /* vhost-vsock fd to pass via getfd (-EBADF if unused) */ + unsigned cid; /* guest CID */ + char *pcie_port; /* owned: pcie-root-port id for device_add bus (NULL on non-PCIe) */ +} VsockInfo; + +void vsock_info_done(VsockInfo *info); + +/* Aggregate of the per-device info structures populated before the bridge-based + * device setup phase. Keeps lifetime and cleanup of all device state in one place. */ +typedef struct MachineConfig { + DriveInfos drives; + NetworkInfo network; + VirtiofsInfos virtiofs; + VsockInfo vsock; +} MachineConfig; + +void machine_config_done(MachineConfig *c); + +/* Phase 2: Device setup — call any subset in any order before vmspawn_qmp_start(). */ +int vmspawn_qmp_setup_drives(VmspawnQmpBridge *bridge, DriveInfos *drives); +int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network); +int vmspawn_qmp_setup_virtiofs(VmspawnQmpBridge *bridge, const VirtiofsInfos *virtiofs); +int vmspawn_qmp_setup_vsock(VmspawnQmpBridge *bridge, VsockInfo *vsock); +int vmspawn_qmp_remove_block_device(VmspawnQmpBridge *bridge, sd_varlink *link, const char *id); +int vmspawn_qmp_dispatch_device_deleted(VmspawnQmpBridge *bridge, sd_json_variant *data); diff --git a/src/vmspawn/vmspawn-register.c b/src/vmspawn/vmspawn-register.c deleted file mode 100644 index 46f292ce49525..0000000000000 --- a/src/vmspawn/vmspawn-register.c +++ /dev/null @@ -1,104 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include - -#include "sd-bus.h" -#include "sd-id128.h" -#include "sd-json.h" -#include "sd-varlink.h" - -#include "bus-error.h" -#include "bus-locator.h" -#include "errno-util.h" -#include "json-util.h" -#include "log.h" -#include "path-lookup.h" -#include "pidref.h" -#include "socket-util.h" -#include "string-util.h" -#include "terminal-util.h" -#include "varlink-util.h" -#include "vmspawn-register.h" - -int register_machine( - sd_bus *bus, - const char *machine_name, - sd_id128_t uuid, - const char *service, - const PidRef *pidref, - const char *directory, - unsigned cid, - const char *address, - const char *key_path, - bool allocate_unit, - RuntimeScope scope) { - - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; - int r; - - assert(machine_name); - assert(service); - - /* First try to use varlink, as it provides more features (such as SSH support). */ - _cleanup_free_ char *p = NULL; - r = runtime_directory_generic(scope, "systemd/machine/io.systemd.Machine", &p); - if (r < 0) - return r; - - r = sd_varlink_connect_address(&vl, p); - if (r == -ENOENT || ERRNO_IS_DISCONNECT(r)) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - assert(bus); - - /* In case we are running with an older machined, fallback to the existing D-Bus method. */ - r = bus_call_method( - bus, - bus_machine_mgr, - "RegisterMachine", - &error, - NULL, - "sayssus", - machine_name, - SD_BUS_MESSAGE_APPEND_ID128(uuid), - service, - "vm", - (uint32_t) (pidref_is_set(pidref) ? pidref->pid : 0), - strempty(directory)); - if (r < 0) - return log_error_errno(r, "Failed to register machine: %s", bus_error_message(&error, r)); - - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to connect to machined on %p: %m", p); - - return varlink_callbo_and_log( - vl, - "io.systemd.Machine.Register", - /* ret_reply= */ NULL, - SD_JSON_BUILD_PAIR_STRING("name", machine_name), - SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(uuid), "id", SD_JSON_BUILD_ID128(uuid)), - SD_JSON_BUILD_PAIR_STRING("service", service), - SD_JSON_BUILD_PAIR_STRING("class", "vm"), - SD_JSON_BUILD_PAIR_CONDITION(VSOCK_CID_IS_REGULAR(cid), "vSockCid", SD_JSON_BUILD_UNSIGNED(cid)), - SD_JSON_BUILD_PAIR_CONDITION(!!directory, "rootDirectory", SD_JSON_BUILD_STRING(directory)), - SD_JSON_BUILD_PAIR_CONDITION(!!address, "sshAddress", SD_JSON_BUILD_STRING(address)), - SD_JSON_BUILD_PAIR_CONDITION(!!key_path, "sshPrivateKeyPath", SD_JSON_BUILD_STRING(key_path)), - SD_JSON_BUILD_PAIR_CONDITION(isatty_safe(STDIN_FILENO), "allowInteractiveAuthentication", SD_JSON_BUILD_BOOLEAN(true)), - SD_JSON_BUILD_PAIR_CONDITION(allocate_unit, "allocateUnit", SD_JSON_BUILD_BOOLEAN(true)), - SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(pidref), "leaderProcessId", JSON_BUILD_PIDREF(pidref))); -} - -int unregister_machine(sd_bus *bus, const char *machine_name) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - - r = bus_call_method(bus, bus_machine_mgr, "UnregisterMachine", &error, NULL, "s", machine_name); - if (r < 0) - log_debug("Failed to unregister machine: %s", bus_error_message(&error, r)); - - return 0; -} diff --git a/src/vmspawn/vmspawn-register.h b/src/vmspawn/vmspawn-register.h deleted file mode 100644 index de118b7492fa2..0000000000000 --- a/src/vmspawn/vmspawn-register.h +++ /dev/null @@ -1,19 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "shared-forward.h" - -int register_machine( - sd_bus *bus, - const char *machine_name, - sd_id128_t uuid, - const char *service, - const PidRef *pidref, - const char *directory, - unsigned cid, - const char *address, - const char *key_path, - bool allocate_unit, - RuntimeScope scope); - -int unregister_machine(sd_bus *bus, const char *machine_name); diff --git a/src/vmspawn/vmspawn-settings.c b/src/vmspawn/vmspawn-settings.c index 46dda4bfc325f..502189e7bea63 100644 --- a/src/vmspawn/vmspawn-settings.c +++ b/src/vmspawn/vmspawn-settings.c @@ -3,13 +3,6 @@ #include "string-table.h" #include "vmspawn-settings.h" -static const char *const image_format_table[_IMAGE_FORMAT_MAX] = { - [IMAGE_FORMAT_RAW] = "raw", - [IMAGE_FORMAT_QCOW2] = "qcow2", -}; - -DEFINE_STRING_TABLE_LOOKUP(image_format, ImageFormat); - void extra_drive_context_done(ExtraDriveContext *ctx) { assert(ctx); @@ -24,6 +17,22 @@ static const char *const console_mode_table[_CONSOLE_MODE_MAX] = { [CONSOLE_READ_ONLY] = "read-only", [CONSOLE_NATIVE] = "native", [CONSOLE_GUI] = "gui", + [CONSOLE_HEADLESS] = "headless", }; DEFINE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode); + +static const char *const console_transport_table[_CONSOLE_TRANSPORT_MAX] = { + [CONSOLE_TRANSPORT_VIRTIO] = "virtio", + [CONSOLE_TRANSPORT_SERIAL] = "serial", +}; + +DEFINE_STRING_TABLE_LOOKUP(console_transport, ConsoleTransport); + +static const char *const firmware_table[_FIRMWARE_MAX] = { + [FIRMWARE_UEFI] = "uefi", + [FIRMWARE_BIOS] = "bios", + [FIRMWARE_NONE] = "none", +}; + +DEFINE_STRING_TABLE_LOOKUP(firmware, Firmware); diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h index ee937c993ac88..596a66cecddb8 100644 --- a/src/vmspawn/vmspawn-settings.h +++ b/src/vmspawn/vmspawn-settings.h @@ -1,18 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "machine-util.h" #include "shared-forward.h" -typedef enum ImageFormat { - IMAGE_FORMAT_RAW, - IMAGE_FORMAT_QCOW2, - _IMAGE_FORMAT_MAX, - _IMAGE_FORMAT_INVALID = -EINVAL, -} ImageFormat; - typedef struct ExtraDrive { char *path; ImageFormat format; + DiskType disk_type; } ExtraDrive; typedef struct ExtraDriveContext { @@ -27,10 +22,26 @@ typedef enum ConsoleMode { CONSOLE_READ_ONLY, /* ptyfwd, but in read-only mode */ CONSOLE_NATIVE, /* qemu's native TTY handling */ CONSOLE_GUI, /* qemu's graphical UI */ + CONSOLE_HEADLESS, /* no console */ _CONSOLE_MODE_MAX, _CONSOLE_MODE_INVALID = -EINVAL, } ConsoleMode; +typedef enum ConsoleTransport { + CONSOLE_TRANSPORT_VIRTIO, /* virtio-serial (hvc0) */ + CONSOLE_TRANSPORT_SERIAL, /* regular serial port (ttyS0/ttyAMA0) */ + _CONSOLE_TRANSPORT_MAX, + _CONSOLE_TRANSPORT_INVALID = -EINVAL, +} ConsoleTransport; + +typedef enum Firmware { + FIRMWARE_UEFI, /* load OVMF firmware */ + FIRMWARE_BIOS, /* don't load OVMF, let qemu use its built-in BIOS (e.g. SeaBIOS on x86) */ + FIRMWARE_NONE, /* no firmware at all, requires --linux= for direct kernel boot */ + _FIRMWARE_MAX, + _FIRMWARE_INVALID = -EINVAL, +} Firmware; + typedef enum SettingsMask { SETTING_START_MODE = UINT64_C(1) << 0, SETTING_MACHINE_ID = UINT64_C(1) << 6, @@ -42,4 +53,5 @@ typedef enum SettingsMask { } SettingsMask; DECLARE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode); -DECLARE_STRING_TABLE_LOOKUP(image_format, ImageFormat); +DECLARE_STRING_TABLE_LOOKUP(console_transport, ConsoleTransport); +DECLARE_STRING_TABLE_LOOKUP(firmware, Firmware); diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index 3f65fab2cc52b..72187b6731a0a 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -18,6 +18,7 @@ #include "path-lookup.h" #include "path-util.h" #include "random-util.h" +#include "set.h" #include "siphash24.h" #include "string-table.h" #include "string-util.h" @@ -107,16 +108,62 @@ int qemu_check_vsock_support(void) { return -errno; } +typedef struct FirmwareTarget { + char *architecture; + char **machines; +} FirmwareTarget; + +static FirmwareTarget* firmware_target_free(FirmwareTarget *t) { + if (!t) + return NULL; + + free(t->architecture); + strv_free(t->machines); + + return mfree(t); +} + +static FirmwareTarget** firmware_target_free_many(FirmwareTarget **targets, size_t n) { + FOREACH_ARRAY(t, targets, n) + firmware_target_free(*t); + + return mfree(targets); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(FirmwareTarget*, firmware_target_free); + /* holds the data retrieved from the QEMU firmware interop JSON data */ typedef struct FirmwareData { + char **interface_types; char **features; char *firmware; char *firmware_format; char *vars; char *vars_format; - char **architectures; + FirmwareTarget **targets; + size_t n_targets; } FirmwareData; +static bool firmware_data_matches_machine(const FirmwareData *fwd, const char *arch, const char *machine) { + assert(fwd); + + FOREACH_ARRAY(t, fwd->targets, fwd->n_targets) { + if (!streq((*t)->architecture, arch)) + continue; + + /* The machine types in firmware descriptions are glob patterns such as "pc-q35-*", but + * we pass the short alias (e.g. "q35") as the machine type to QEMU as it always points to + * the latest version. We can't use fnmatch() here because "q35" doesn't match the + * "pc-q35-*" glob, so instead we use substring matching to check if our machine type + * appears in the pattern. */ + STRV_FOREACH(m, (*t)->machines) + if (strstr(*m, machine)) + return true; + } + + return false; +} + static bool firmware_data_supports_sb(const FirmwareData *fwd) { assert(fwd); @@ -127,12 +174,13 @@ static FirmwareData* firmware_data_free(FirmwareData *fwd) { if (!fwd) return NULL; + strv_free(fwd->interface_types); strv_free(fwd->features); free(fwd->firmware); free(fwd->firmware_format); free(fwd->vars); free(fwd->vars_format); - strv_free(fwd->architectures); + firmware_target_free_many(fwd->targets, fwd->n_targets); return mfree(fwd); } @@ -162,34 +210,37 @@ static int firmware_mapping(const char *name, sd_json_variant *v, sd_json_dispat static const sd_json_dispatch_field table[] = { { "device", SD_JSON_VARIANT_STRING, NULL, 0, SD_JSON_MANDATORY }, { "executable", SD_JSON_VARIANT_OBJECT, firmware_executable, 0, SD_JSON_MANDATORY }, - { "nvram-template", SD_JSON_VARIANT_OBJECT, firmware_nvram_template, 0, SD_JSON_MANDATORY }, + { "nvram-template", SD_JSON_VARIANT_OBJECT, firmware_nvram_template, 0, 0 }, {} }; return sd_json_dispatch(v, table, flags, userdata); } -static int target_architecture(const char *name, sd_json_variant *v, sd_json_dispatch_flags_t flags, void *userdata) { - int r; +static int dispatch_targets(const char *name, sd_json_variant *v, sd_json_dispatch_flags_t flags, void *userdata) { + FirmwareData *fwd = ASSERT_PTR(userdata); sd_json_variant *e; - char ***supported_architectures = ASSERT_PTR(userdata); + int r; static const sd_json_dispatch_field table[] = { - { "architecture", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, 0, SD_JSON_MANDATORY }, - { "machines", SD_JSON_VARIANT_ARRAY, NULL, 0, SD_JSON_MANDATORY }, + { "architecture", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(FirmwareTarget, architecture), SD_JSON_MANDATORY }, + { "machines", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(FirmwareTarget, machines), SD_JSON_MANDATORY }, {} }; JSON_VARIANT_ARRAY_FOREACH(e, v) { - _cleanup_free_ char *arch = NULL; + _cleanup_(firmware_target_freep) FirmwareTarget *t = new0(FirmwareTarget, 1); + if (!t) + return -ENOMEM; - r = sd_json_dispatch(e, table, flags, &arch); + r = sd_json_dispatch(e, table, flags, t); if (r < 0) return r; - r = strv_consume(supported_architectures, TAKE_PTR(arch)); - if (r < 0) - return r; + if (!GREEDY_REALLOC(fwd->targets, fwd->n_targets + 1)) + return -ENOMEM; + + fwd->targets[fwd->n_targets++] = TAKE_PTR(t); } return 0; @@ -243,7 +294,7 @@ int list_ovmf_config(char ***ret) { return 0; } -static int load_firmware_data(const char *path, FirmwareData **ret) { +static int load_firmware_data(const char *path, FirmwareData **ret, sd_json_variant **ret_json) { int r; assert(path); @@ -253,7 +304,7 @@ static int load_firmware_data(const char *path, FirmwareData **ret) { r = sd_json_parse_file( /* f= */ NULL, path, - /* flags= */ 0, + /* flags= */ SD_JSON_PARSE_MUST_BE_OBJECT, &json, /* reterr_line= */ NULL, /* ret_column= */ NULL); @@ -261,12 +312,12 @@ static int load_firmware_data(const char *path, FirmwareData **ret) { return r; static const sd_json_dispatch_field table[] = { - { "description", SD_JSON_VARIANT_STRING, NULL, 0, SD_JSON_MANDATORY }, - { "interface-types", SD_JSON_VARIANT_ARRAY, NULL, 0, SD_JSON_MANDATORY }, - { "mapping", SD_JSON_VARIANT_OBJECT, firmware_mapping, 0, SD_JSON_MANDATORY }, - { "targets", SD_JSON_VARIANT_ARRAY, target_architecture, offsetof(FirmwareData, architectures), SD_JSON_MANDATORY }, - { "features", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(FirmwareData, features), SD_JSON_MANDATORY }, - { "tags", SD_JSON_VARIANT_ARRAY, NULL, 0, SD_JSON_MANDATORY }, + { "description", SD_JSON_VARIANT_STRING, NULL, 0, SD_JSON_MANDATORY }, + { "interface-types", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(FirmwareData, interface_types), SD_JSON_MANDATORY }, + { "mapping", SD_JSON_VARIANT_OBJECT, firmware_mapping, 0, SD_JSON_MANDATORY }, + { "targets", SD_JSON_VARIANT_ARRAY, dispatch_targets, 0, SD_JSON_MANDATORY }, + { "features", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(FirmwareData, features), SD_JSON_MANDATORY }, + { "tags", SD_JSON_VARIANT_ARRAY, NULL, 0, SD_JSON_MANDATORY }, {} }; @@ -280,6 +331,45 @@ static int load_firmware_data(const char *path, FirmwareData **ret) { return r; *ret = TAKE_PTR(fwd); + + if (ret_json) + *ret_json = TAKE_PTR(json); + + return 0; +} + +int list_ovmf_firmware_features(char ***ret) { + _cleanup_strv_free_ char **conf_files = NULL; + _cleanup_set_free_ Set *feature_set = NULL; + int r; + + assert(ret); + + r = list_ovmf_config(&conf_files); + if (r < 0) + return r; + + STRV_FOREACH(file, conf_files) { + _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL; + + r = load_firmware_data(*file, &fwd, /* ret_json= */ NULL); + if (r < 0) { + log_debug_errno(r, "Failed to load JSON file '%s', skipping: %m", *file); + continue; + } + + r = set_put_strdupv(&feature_set, fwd->features); + if (r < 0) + return log_oom_debug(); + } + + _cleanup_strv_free_ char **features = set_to_strv(&feature_set); + if (!features) + return log_oom_debug(); + + strv_sort(features); + + *ret = TAKE_PTR(features); return 0; } @@ -311,14 +401,18 @@ int load_ovmf_config(const char *path, OvmfConfig **ret) { assert(path); assert(ret); - r = load_firmware_data(path, &fwd); + r = load_firmware_data(path, &fwd, /* ret_json= */ NULL); if (r < 0) return r; return ovmf_config_make(fwd, ret); } -int find_ovmf_config(int search_sb, OvmfConfig **ret) { +int find_ovmf_config( + Set *features_include, + Set *features_exclude, + OvmfConfig **ret, + sd_json_variant **ret_firmware_json) { _cleanup_(ovmf_config_freep) OvmfConfig *config = NULL; _cleanup_strv_free_ char **conf_files = NULL; const char* native_arch_qemu; @@ -344,34 +438,73 @@ int find_ovmf_config(int search_sb, OvmfConfig **ret) { STRV_FOREACH(file, conf_files) { _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; - r = load_firmware_data(*file, &fwd); + r = load_firmware_data(*file, &fwd, ret_firmware_json ? &json : NULL); if (r < 0) { log_debug_errno(r, "Failed to load JSON file '%s', skipping: %m", *file); continue; } - if (strv_contains(fwd->features, "enrolled-keys")) { - log_debug("Skipping %s, firmware has enrolled keys which has been known to cause issues.", *file); + if (!strv_contains(fwd->interface_types, "uefi")) { + log_debug("Skipping %s, firmware is not a UEFI firmware.", *file); continue; } - if (!strv_contains(fwd->architectures, native_arch_qemu)) { - log_debug("Skipping %s, firmware doesn't support the native architecture.", *file); + if (!fwd->vars) { + log_debug("Skipping %s, firmware does not have an NVRAM template.", *file); continue; } - /* exclude firmware which doesn't match our Secure Boot requirements */ - if (search_sb >= 0 && !!search_sb != firmware_data_supports_sb(fwd)) { - log_debug("Skipping %s, firmware doesn't fit required Secure Boot configuration.", *file); + /* Check if any target matches our architecture and machine type. Machine + * patterns in firmware descriptions use globs like "pc-q35-*", so we do a + * substring check to see if our machine type (e.g. "q35") appears in any of + * the glob patterns. */ + if (!firmware_data_matches_machine(fwd, native_arch_qemu, QEMU_MACHINE_TYPE)) { + log_debug("Skipping %s, firmware doesn't support the native architecture or machine type.", *file); continue; } + /* Skip firmware that doesn't have all required features */ + if (!set_isempty(features_include)) { + const char *feature; + bool skip = false; + + SET_FOREACH(feature, features_include) + if (!strv_contains(fwd->features, feature)) { + log_debug("Skipping %s, firmware is missing required feature '%s'.", *file, feature); + skip = true; + } + + if (skip) + continue; + } + + /* Skip firmware that has any excluded features (include wins over exclude) */ + if (!set_isempty(features_exclude)) { + const char *feature; + bool skip = false; + + SET_FOREACH(feature, features_exclude) + if (strv_contains(fwd->features, feature) && + !set_contains(features_include, feature)) { + log_debug("Skipping %s, firmware has excluded feature '%s'.", *file, feature); + skip = true; + } + + if (skip) + continue; + } + r = ovmf_config_make(fwd, &config); if (r < 0) return r; log_debug("Selected firmware definition %s.", *file); + + if (ret_firmware_json) + *ret_firmware_json = TAKE_PTR(json); + break; } @@ -395,7 +528,7 @@ int find_qemu_binary(char **ret_qemu_binary) { * If the native architecture is not supported by qemu -EOPNOTSUPP will be returned; */ - FOREACH_STRING(s, "qemu", "qemu-kvm") { + FOREACH_STRING(s, "qemu", "qemu-kvm", "/usr/libexec/qemu-kvm") { r = find_executable(s, ret_qemu_binary); if (r == 0) return 0; diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h index 0d138c84c8c74..38bb331dfc340 100644 --- a/src/vmspawn/vmspawn-util.h +++ b/src/vmspawn/vmspawn-util.h @@ -33,20 +33,65 @@ # define ARCHITECTURE_SUPPORTS_HPET 0 #endif +#if defined(__x86_64__) || defined(__aarch64__) +# define ARCHITECTURE_SUPPORTS_CXL 1 +#else +# define ARCHITECTURE_SUPPORTS_CXL 0 +#endif + +#if defined(__x86_64__) || defined(__i386__) || defined(__arm__) || defined(__aarch64__) || defined(__riscv) || defined(__loongarch64) +# define ARCHITECTURE_SUPPORTS_FW_CFG 1 +#else +# define ARCHITECTURE_SUPPORTS_FW_CFG 0 +#endif + +/* QEMU's fw_cfg file path buffer is FW_CFG_MAX_FILE_PATH (56) bytes including NUL */ +#define QEMU_FW_CFG_MAX_KEY_LEN 55 + +/* These match the kernel's COMMAND_LINE_SIZE for each architecture */ +#if defined(__loongarch64) +# define KERNEL_CMDLINE_SIZE 4096 +#elif defined(__x86_64__) || defined(__i386__) || defined(__aarch64__) +# define KERNEL_CMDLINE_SIZE 2048 +#elif defined(__arm__) || defined(__riscv) +# define KERNEL_CMDLINE_SIZE 1024 +#else +# define KERNEL_CMDLINE_SIZE 512 +#endif + +/* ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS is co-located with QEMU_MACHINE_TYPE so they stay in + * sync: q35 and virt machine types need pcie-root-port bridges for QMP device_add hotplug. + * Exception: m68k's "virt" uses virtio-mmio, not PCIe, so it doesn't need root ports. */ #if defined(__x86_64__) || defined(__i386__) # define QEMU_MACHINE_TYPE "q35" -#elif defined(__arm__) || defined(__aarch64__) || defined(__riscv) || defined(__loongarch64) || defined(__m68k__) +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 1 +#elif defined(__m68k__) +# define QEMU_MACHINE_TYPE "virt" +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 0 +#elif defined(__arm__) || defined(__aarch64__) || defined(__riscv) || defined(__loongarch64) # define QEMU_MACHINE_TYPE "virt" +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 1 #elif defined(__s390__) || defined(__s390x__) # define QEMU_MACHINE_TYPE "s390-ccw-virtio" +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 0 #elif defined(__powerpc__) || defined(__powerpc64__) # define QEMU_MACHINE_TYPE "pseries" +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 0 #elif defined(__mips__) # define QEMU_MACHINE_TYPE "malta" +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 0 #elif defined(__sparc__) # define QEMU_MACHINE_TYPE "sun4u" +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 0 #else # define QEMU_MACHINE_TYPE "none" +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 0 +#endif + +#if defined(__arm__) || defined(__aarch64__) +# define QEMU_SERIAL_CONSOLE_NAME "ttyAMA0" +#else +# define QEMU_SERIAL_CONSOLE_NAME "ttyS0" #endif typedef struct OvmfConfig { @@ -87,8 +132,9 @@ DECLARE_STRING_TABLE_LOOKUP(network_stack, NetworkStack); int qemu_check_kvm_support(void); int qemu_check_vsock_support(void); int list_ovmf_config(char ***ret); +int list_ovmf_firmware_features(char ***ret); int load_ovmf_config(const char *path, OvmfConfig **ret); -int find_ovmf_config(int search_sb, OvmfConfig **ret); +int find_ovmf_config(Set *features_include, Set *features_exclude, OvmfConfig **ret, sd_json_variant **ret_firmware_json); int find_qemu_binary(char **ret_qemu_binary); int vsock_fix_child_cid(int vhost_device_fd, unsigned *machine_cid, const char *machine); diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c new file mode 100644 index 0000000000000..9aa6afeae0385 --- /dev/null +++ b/src/vmspawn/vmspawn-varlink.c @@ -0,0 +1,449 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "errno-util.h" +#include "hashmap.h" +#include "log.h" +#include "path-util.h" +#include "qmp-client.h" +#include "string-util.h" +#include "strv.h" +#include "varlink-io.systemd.MachineInstance.h" +#include "varlink-io.systemd.VirtualMachineInstance.h" +#include "varlink-util.h" +#include "vmspawn-qmp.h" +#include "vmspawn-varlink.h" + +DEFINE_PRIVATE_HASH_OPS_FULL( + varlink_subscriber_hash_ops, + void, trivial_hash_func, trivial_compare_func, sd_varlink_close_unref, + char*, strv_free); + +struct VmspawnVarlinkContext { + sd_varlink_server *varlink_server; + VmspawnQmpBridge *bridge; + /* Key: sd_varlink* (ref'd), Value: strv filter (NULL = all events). + * varlink_subscriber_hash_ops handles cleanup of both on removal. */ + Hashmap *subscribed; +}; + +/* Translate a QMP async completion into a varlink error reply */ +static int qmp_error_to_varlink(sd_varlink *link, const char *error_desc, int error) { + assert(link); + + if (ERRNO_IS_DISCONNECT(error)) + return sd_varlink_error(link, "io.systemd.MachineInstance.NotConnected", NULL); + if (error == -EIO) + log_warning("QMP command failed: %s", strna(error_desc)); + return sd_varlink_error_errno(link, error); +} + +/* Shared async completion for simple QMP commands that return no data. + * Errors are translated to varlink replies, not propagated through sd_event. */ +static int on_qmp_simple_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + sd_varlink *link = ASSERT_PTR(userdata); + + assert(client); + + if (error < 0) + (void) qmp_error_to_varlink(link, error_desc, error); + else + (void) sd_varlink_reply(link, NULL); + + sd_varlink_unref(link); + return 0; +} + +/* "quit" tells QEMU to exit, which races the QMP reply with the socket EOF — sometimes the + * disconnect lands in qmp_client_fail_pending() before the reply has been parsed. For Terminate + * that's the desired outcome, so treat disconnect-class errors as success. */ +static int on_qmp_terminate_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + sd_varlink *link = ASSERT_PTR(userdata); + + assert(client); + + if (error < 0 && !ERRNO_IS_DISCONNECT(error)) + (void) qmp_error_to_varlink(link, error_desc, error); + else + (void) sd_varlink_reply(link, NULL); + + sd_varlink_unref(link); + return 0; +} + +static int qmp_execute_varlink_async( + VmspawnVarlinkContext *ctx, + sd_varlink *link, + const char *command, + sd_json_variant *arguments, + qmp_command_callback_t callback) { + + int r; + + sd_varlink_ref(link); + + r = qmp_client_invoke(ctx->bridge->qmp, /* ret_slot= */ NULL, command, QMP_CLIENT_ARGS(arguments), callback, link); + if (r < 0) + sd_varlink_unref(link); + + return r; +} + +static int qmp_execute_simple_async(sd_varlink *link, VmspawnVarlinkContext *ctx, const char *qmp_command) { + assert(link); + assert(ctx); + assert(qmp_command); + + return qmp_execute_varlink_async(ctx, link, qmp_command, /* arguments= */ NULL, on_qmp_simple_complete); +} + +static int vl_method_terminate(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return qmp_execute_varlink_async(ASSERT_PTR(userdata), link, "quit", /* arguments= */ NULL, on_qmp_terminate_complete); +} + +static int vl_method_pause(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return qmp_execute_simple_async(link, ASSERT_PTR(userdata), "stop"); +} + +static int vl_method_resume(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return qmp_execute_simple_async(link, ASSERT_PTR(userdata), "cont"); +} + +static int vl_method_power_off(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return qmp_execute_simple_async(link, ASSERT_PTR(userdata), "system_powerdown"); +} + +static int vl_method_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return qmp_execute_simple_async(link, ASSERT_PTR(userdata), "system_reset"); +} + +/* Async completion for query-status: extract running/status from QMP result */ +static int on_qmp_describe_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(sd_varlink_unrefp) sd_varlink *link = ASSERT_PTR(userdata); + + assert(client); + + if (error < 0) { + (void) qmp_error_to_varlink(link, error_desc, error); + return 0; + } + + sd_json_variant *running_v = sd_json_variant_by_key(result, "running"); + sd_json_variant *status_v = sd_json_variant_by_key(result, "status"); + + bool running = running_v ? sd_json_variant_boolean(running_v) : false; + + const char *status = status_v && sd_json_variant_is_string(status_v) ? + sd_json_variant_string(status_v) : "unknown"; + + (void) sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_BOOLEAN("running", running), + SD_JSON_BUILD_PAIR_STRING("status", status)); + + return 0; +} + +static int vl_method_describe(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + + return qmp_execute_varlink_async(ctx, link, "query-status", /* arguments= */ NULL, on_qmp_describe_complete); +} + +static int vl_method_subscribe_events(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + _cleanup_strv_free_ char **filter = NULL; + int r; + + /* SD_VARLINK_REQUIRES_MORE in the IDL rejects non-streaming callers before we get here */ + + r = sd_varlink_dispatch(link, parameters, (const sd_json_dispatch_field[]) { + { "filter", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv, 0, SD_JSON_NULLABLE }, + {}, + }, &filter); + if (r != 0) + return r; + + /* Treat [] identically to null: deliver all events. */ + if (strv_isempty(filter)) + filter = strv_free(filter); + + sd_varlink_ref(link); + + r = hashmap_ensure_put(&ctx->subscribed, &varlink_subscriber_hash_ops, link, filter); + if (r < 0) { + sd_varlink_unref(link); + return r; + } + + TAKE_PTR(filter); + + r = sd_varlink_notifybo(link, SD_JSON_BUILD_PAIR_STRING("event", "READY")); + if (r < 0) { + strv_free(hashmap_remove(ctx->subscribed, link)); + sd_varlink_close_unref(link); + return r; + } + + return 0; +} + +static void vl_disconnect(sd_varlink_server *server, sd_varlink *link, void *userdata) { + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + + assert(server); + assert(link); + + /* Only subscribers hold an extra ref on the link (taken in vl_method_subscribe_events). + * Non-subscriber connections (one-shot commands like Pause, Describe) must not be unref'd + * here — their extra ref is consumed by the async completion callback. Only unref, never + * close — the server handles close after this callback returns (matching resolved's + * vl_on_notification_disconnect pattern). + * + * Use hashmap_remove2() so the returned key (non-NULL iff the entry was present) + * disambiguates "no filter subscriber" (value=NULL) from "not a subscriber". */ + void *removed_key = NULL; + strv_free(hashmap_remove2(ctx->subscribed, link, &removed_key)); + if (!removed_key) + return; + + sd_varlink_unref(link); +} + +static int on_job_dismiss_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + if (error < 0) + log_debug_errno(error, "job-dismiss failed: %s", strna(error_desc)); + + return 0; +} + +static int dispatch_pending_job(VmspawnQmpBridge *bridge, sd_json_variant *data) { + const char *job_id, *status; + int r; + + assert(bridge); + + if (!data) + return 0; + + job_id = sd_json_variant_string(sd_json_variant_by_key(data, "id")); + status = sd_json_variant_string(sd_json_variant_by_key(data, "status")); + + if (!job_id || !streq_ptr(status, "concluded")) + return 0; + + _cleanup_free_ char *key = NULL; + _cleanup_(pending_job_freep) PendingJob *job = hashmap_remove2(bridge->pending_jobs, job_id, (void**) &key); + if (!job) + return 0; + + log_debug("QMP job '%s' concluded, firing continuation", job_id); + + /* Dismiss the concluded job before running the continuation */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *dismiss_args = NULL; + r = sd_json_buildo(&dismiss_args, SD_JSON_BUILD_PAIR_STRING("id", job_id)); + if (r < 0) + return sd_event_exit(qmp_client_get_event(bridge->qmp), r); + + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "job-dismiss", QMP_CLIENT_ARGS(dismiss_args), + on_job_dismiss_complete, /* userdata= */ NULL); + if (r < 0) + return sd_event_exit(qmp_client_get_event(bridge->qmp), r); + + if (!job->on_concluded) + return 1; + + r = job->on_concluded(bridge->qmp, TAKE_PTR(job->userdata)); + if (r < 0) { + log_error_errno(r, "Job continuation failed: %m"); + return sd_event_exit(qmp_client_get_event(bridge->qmp), r); + } + + return 1; +} + +static int notify_event_subscribers(VmspawnVarlinkContext *ctx, const char *event_name, sd_json_variant *data) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *notification = NULL; + sd_varlink *link; + char **filter; + int r; + + assert(ctx); + assert(event_name); + + if (hashmap_isempty(ctx->subscribed)) + return 0; + + r = sd_json_buildo( + ¬ification, + SD_JSON_BUILD_PAIR_STRING("event", event_name), + SD_JSON_BUILD_PAIR_CONDITION(!!data, "data", SD_JSON_BUILD_VARIANT(data))); + if (r < 0) { + log_warning_errno(r, "Failed to build event notification, ignoring: %m"); + return 0; + } + + HASHMAP_FOREACH_KEY(filter, link, ctx->subscribed) { + if (filter && !strv_contains(filter, event_name)) + continue; + + r = sd_varlink_notify(link, notification); + if (r < 0) + log_warning_errno(r, "Failed to notify event subscriber, ignoring: %m"); + } + + return 0; +} + +static int on_qmp_event( + QmpClient *client, + const char *event, + sd_json_variant *data, + void *userdata) { + + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + + assert(client); + assert(event); + + /* Dispatch job status changes to pending continuations (e.g. blockdev-create) */ + if (streq(event, "JOB_STATUS_CHANGE")) + return dispatch_pending_job(ctx->bridge, data); + + /* Notification still fans out below. */ + if (streq(event, "DEVICE_DELETED")) + (void) vmspawn_qmp_dispatch_device_deleted(ctx->bridge, data); + + return notify_event_subscribers(ctx, event, data); +} + +/* Free all subscriber entries — varlink_subscriber_hash_ops handles + * close + unref for each key and strv_free for each value. */ +static void drain_event_subscribers(Hashmap **subscribed) { + assert(subscribed); + *subscribed = hashmap_free(*subscribed); +} + +static void on_qmp_disconnect(QmpClient *client, void *userdata) { + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + + assert(client); + + log_debug("Backend connection lost"); + + /* Propagate connection loss by closing all subscriber connections */ + drain_event_subscribers(&ctx->subscribed); +} + +int vmspawn_varlink_setup( + VmspawnVarlinkContext **ret, + VmspawnQmpBridge *bridge, + const char *runtime_dir, + char **ret_control_address) { + + _cleanup_(vmspawn_varlink_context_freep) VmspawnVarlinkContext *ctx = NULL; + _cleanup_free_ char *listen_address = NULL; + int r; + + assert(ret); + assert(bridge); + assert(runtime_dir); + + sd_event *event = qmp_client_get_event(bridge->qmp); + assert(event); + + ctx = new0(VmspawnVarlinkContext, 1); + if (!ctx) + return log_oom(); + + /* Create varlink server for VM control */ + r = varlink_server_new(&ctx->varlink_server, + SD_VARLINK_SERVER_INHERIT_USERDATA, + ctx); + if (r < 0) + return log_error_errno(r, "Failed to create varlink server: %m"); + + r = sd_varlink_server_add_interface_many( + ctx->varlink_server, + &vl_interface_io_systemd_MachineInstance, + &vl_interface_io_systemd_VirtualMachineInstance); + if (r < 0) + return log_error_errno(r, "Failed to add varlink interfaces: %m"); + + r = sd_varlink_server_bind_method_many( + ctx->varlink_server, + "io.systemd.MachineInstance.Terminate", vl_method_terminate, + "io.systemd.MachineInstance.PowerOff", vl_method_power_off, + "io.systemd.MachineInstance.Pause", vl_method_pause, + "io.systemd.MachineInstance.Resume", vl_method_resume, + "io.systemd.MachineInstance.Reboot", vl_method_reboot, + "io.systemd.MachineInstance.Describe", vl_method_describe, + "io.systemd.MachineInstance.SubscribeEvents", vl_method_subscribe_events); + if (r < 0) + return log_error_errno(r, "Failed to bind varlink methods: %m"); + + r = sd_varlink_server_bind_disconnect(ctx->varlink_server, vl_disconnect); + if (r < 0) + return log_error_errno(r, "Failed to bind disconnect handler: %m"); + + listen_address = path_join(runtime_dir, "control"); + if (!listen_address) + return log_oom(); + + r = sd_varlink_server_listen_address(ctx->varlink_server, listen_address, 0600); + if (r < 0) + return log_error_errno(r, "Failed to listen on %s: %m", listen_address); + + r = sd_varlink_server_attach_event(ctx->varlink_server, event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach varlink server to event loop: %m"); + + ctx->bridge = bridge; + qmp_client_bind_event(ctx->bridge->qmp, on_qmp_event, ctx); + qmp_client_bind_disconnect(ctx->bridge->qmp, on_qmp_disconnect, ctx); + qmp_client_set_userdata(ctx->bridge->qmp, ctx->bridge); + + log_debug("Varlink control server listening on %s", listen_address); + + if (ret_control_address) + *ret_control_address = TAKE_PTR(listen_address); + + *ret = TAKE_PTR(ctx); + return 0; +} + +VmspawnVarlinkContext* vmspawn_varlink_context_free(VmspawnVarlinkContext *ctx) { + if (!ctx) + return NULL; + + sd_varlink_server_unref(ctx->varlink_server); + vmspawn_qmp_bridge_free(ctx->bridge); + + drain_event_subscribers(&ctx->subscribed); + + return mfree(ctx); +} diff --git a/src/vmspawn/vmspawn-varlink.h b/src/vmspawn/vmspawn-varlink.h new file mode 100644 index 0000000000000..1833416a56dcc --- /dev/null +++ b/src/vmspawn/vmspawn-varlink.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "cleanup-util.h" +#include "shared-forward.h" +#include "vmspawn-qmp.h" + +typedef struct VmspawnVarlinkContext VmspawnVarlinkContext; + +/* Varlink server for VM control on top of an established bridge connection */ +int vmspawn_varlink_setup( + VmspawnVarlinkContext **ret, + VmspawnQmpBridge *bridge, + const char *runtime_dir, + char **ret_control_address); + +VmspawnVarlinkContext* vmspawn_varlink_context_free(VmspawnVarlinkContext *ctx); + +DEFINE_TRIVIAL_CLEANUP_FUNC(VmspawnVarlinkContext *, vmspawn_varlink_context_free); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index d68a621e060de..1a349a1d634f3 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -35,7 +34,9 @@ #include "event-util.h" #include "extract-word.h" #include "fd-util.h" +#include "fileio.h" #include "fork-notify.h" +#include "format-table.h" #include "format-util.h" #include "fs-util.h" #include "gpt.h" @@ -44,15 +45,19 @@ #include "hostname-setup.h" #include "hostname-util.h" #include "id128-util.h" +#include "kernel-image.h" #include "log.h" #include "machine-bind-user.h" #include "machine-credential.h" +#include "machine-register.h" #include "main-func.h" +#include "memfd-util.h" #include "mkdir.h" #include "namespace-util.h" #include "netif-util.h" #include "nsresource.h" #include "osc-context.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -65,6 +70,8 @@ #include "ptyfwd.h" #include "random-util.h" #include "rm-rf.h" +#include "set.h" +#include "sha256.h" #include "signal-util.h" #include "snapshot-util.h" #include "socket-util.h" @@ -72,6 +79,7 @@ #include "stdio-util.h" #include "string-util.h" #include "strv.h" +#include "swtpm-util.h" #include "sync-util.h" #include "terminal-util.h" #include "tmpfile-util.h" @@ -81,20 +89,28 @@ #include "user-util.h" #include "utf8.h" #include "vmspawn-mount.h" -#include "vmspawn-register.h" +#include "vmspawn-qemu-config.h" +#include "vmspawn-qmp.h" #include "vmspawn-scope.h" #include "vmspawn-settings.h" #include "vmspawn-util.h" +#include "vmspawn-varlink.h" #define VM_TAP_HASH_KEY SD_ID128_MAKE(01,d0,c6,4c,2b,df,24,fb,c0,f8,b2,09,7d,59,b2,93) -typedef enum TpmStateMode { - TPM_STATE_OFF, /* keep no state around */ - TPM_STATE_AUTO, /* keep state around if not ephemeral, derive path from image/directory */ - TPM_STATE_PATH, /* explicitly specified location */ - _TPM_STATE_MODE_MAX, - _TPM_STATE_MODE_INVALID = -EINVAL, -} TpmStateMode; +#define DISK_SERIAL_MAX_LEN_SCSI 30 +#define DISK_SERIAL_MAX_LEN_NVME 20 +#define DISK_SERIAL_MAX_LEN_VIRTIO_BLK 20 + +/* An enum controlling how auxiliary state for the VM are maintained, i.e. the TPM state and the EFI variable + * NVRAM. */ +typedef enum StateMode { + STATE_OFF, /* keep no state around */ + STATE_AUTO, /* keep state around if not ephemeral, derive path from image/directory */ + STATE_PATH, /* explicitly specified location */ + _STATE_MODE_MAX, + _STATE_MODE_INVALID = -EINVAL, +} StateMode; typedef struct SSHInfo { unsigned cid; @@ -117,21 +133,32 @@ static char *arg_slice = NULL; static char **arg_property = NULL; static char *arg_cpus = NULL; static uint64_t arg_ram = UINT64_C(2) * U64_GB; +static uint64_t arg_ram_max = 0; +static unsigned arg_ram_slots = 0; static int arg_kvm = -1; static int arg_vsock = -1; static unsigned arg_vsock_cid = VMADDR_CID_ANY; static int arg_tpm = -1; static char *arg_linux = NULL; +static KernelImageType arg_linux_image_type = _KERNEL_IMAGE_TYPE_INVALID; static char **arg_initrds = NULL; static ConsoleMode arg_console_mode = CONSOLE_INTERACTIVE; +static ConsoleTransport arg_console_transport = CONSOLE_TRANSPORT_VIRTIO; static NetworkStack arg_network_stack = NETWORK_STACK_NONE; -static int arg_secure_boot = -1; static MachineCredentialContext arg_credentials = {}; static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; static RuntimeMountContext arg_runtime_mounts = {}; static char *arg_firmware = NULL; +static Firmware arg_firmware_type = _FIRMWARE_INVALID; +static bool arg_firmware_describe = false; +static Set *arg_firmware_features_include = NULL; +static Set *arg_firmware_features_exclude = NULL; static char *arg_forward_journal = NULL; -static bool arg_register = true; +static uint64_t arg_forward_journal_max_use = UINT64_MAX; +static uint64_t arg_forward_journal_keep_free = UINT64_MAX; +static uint64_t arg_forward_journal_max_file_size = UINT64_MAX; +static uint64_t arg_forward_journal_max_files = UINT64_MAX; +static int arg_register = -1; static bool arg_keep_unit = false; static sd_id128_t arg_uuid = {}; static char **arg_kernel_cmdline_extra = NULL; @@ -140,11 +167,15 @@ static char *arg_background = NULL; static bool arg_pass_ssh_key = true; static char *arg_ssh_key_type = NULL; static bool arg_discard_disk = true; -struct ether_addr arg_network_provided_mac = {}; +static DiskType arg_image_disk_type = DISK_TYPE_VIRTIO_BLK; +static struct ether_addr arg_network_provided_mac = {}; static char **arg_smbios11 = NULL; static uint64_t arg_grow_image = 0; static char *arg_tpm_state_path = NULL; -static TpmStateMode arg_tpm_state_mode = TPM_STATE_AUTO; +static StateMode arg_tpm_state_mode = STATE_AUTO; +static char *arg_efi_nvram_template = NULL; +static char *arg_efi_nvram_state_path = NULL; +static StateMode arg_efi_nvram_state_mode = STATE_AUTO; static bool arg_ask_password = true; static bool arg_notify_ready = true; static char **arg_bind_user = NULL; @@ -161,6 +192,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_slice, freep); STATIC_DESTRUCTOR_REGISTER(arg_cpus, freep); STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done); STATIC_DESTRUCTOR_REGISTER(arg_firmware, freep); +STATIC_DESTRUCTOR_REGISTER(arg_firmware_features_include, set_freep); +STATIC_DESTRUCTOR_REGISTER(arg_firmware_features_exclude, set_freep); STATIC_DESTRUCTOR_REGISTER(arg_linux, freep); STATIC_DESTRUCTOR_REGISTER(arg_initrds, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_runtime_mounts, runtime_mount_context_done); @@ -171,6 +204,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_background, freep); STATIC_DESTRUCTOR_REGISTER(arg_ssh_key_type, freep); STATIC_DESTRUCTOR_REGISTER(arg_smbios11, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm_state_path, freep); +STATIC_DESTRUCTOR_REGISTER(arg_efi_nvram_template, freep); +STATIC_DESTRUCTOR_REGISTER(arg_efi_nvram_state_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_property, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_shell, freep); @@ -186,88 +221,46 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [ARGUMENTS...]\n\n" - "%5$sSpawn a command or OS in a virtual machine.%6$s\n\n" - " -h --help Show this help\n" - " --version Print version string\n" - " -q --quiet Do not show status information\n" - " --no-pager Do not pipe output into a pager\n" - " --no-ask-password Do not prompt for password\n" - " --user Interact with user manager\n" - " --system Interact with system manager\n" - "\n%3$sImage:%4$s\n" - " -D --directory=PATH Root directory for the VM\n" - " -x --ephemeral Run VM with snapshot of the disk or directory\n" - " -i --image=FILE|DEVICE Root file system disk image or device for the VM\n" - " --image-format=FORMAT Specify disk image format (raw, qcow2; default: raw)\n" - "\n%3$sHost Configuration:%4$s\n" - " --cpus=CPUS Configure number of CPUs in guest\n" - " --ram=BYTES Configure guest's RAM size\n" - " --kvm=BOOL Enable use of KVM\n" - " --vsock=BOOL Override autodetection of VSOCK support\n" - " --vsock-cid=CID Specify the CID to use for the guest's VSOCK support\n" - " --tpm=BOOL Enable use of a virtual TPM\n" - " --tpm-state=off|auto|PATH\n" - " Where to store TPM state\n" - " --linux=PATH Specify the linux kernel for direct kernel boot\n" - " --initrd=PATH Specify the initrd for direct kernel boot\n" - " -n --network-tap Create a TAP device for networking\n" - " --network-user-mode Use user mode networking\n" - " --secure-boot=BOOL Enable searching for firmware supporting SecureBoot\n" - " --firmware=PATH|list Select firmware definition file (or list available)\n" - " --discard-disk=BOOL Control processing of discard requests\n" - " -G --grow-image=BYTES Grow image file to specified size in bytes\n" - "\n%3$sExecution:%4$s\n" - " -s --smbios11=STRING Pass an arbitrary SMBIOS Type #11 string to the VM\n" - " --notify-ready=BOOL Wait for ready notification from the VM\n" - "\n%3$sSystem Identity:%4$s\n" - " -M --machine=NAME Set the machine name for the VM\n" - " --uuid=UUID Set a specific machine UUID for the VM\n" - "\n%3$sProperties:%4$s\n" - " -S --slice=SLICE Place the VM in the specified slice\n" - " --property=NAME=VALUE Set scope unit property\n" - " --register=BOOLEAN Register VM as machine\n" - " --keep-unit Do not register a scope for the machine, reuse\n" - " the service unit vmspawn is running in\n" - "\n%3$sUser Namespacing:%4$s\n" - " --private-users=UIDBASE[:NUIDS]\n" - " Configure the UID/GID range to map into the\n" - " virtiofsd namespace\n" - "\n%3$sMounts:%4$s\n" - " --bind=SOURCE[:TARGET]\n" - " Mount a file or directory from the host into the VM\n" - " --bind-ro=SOURCE[:TARGET]\n" - " Mount a file or directory, but read-only\n" - " --extra-drive=PATH[:FORMAT]\n" - " Adds an additional disk to the virtual machine\n" - " (format: raw, qcow2; default: raw)\n" - " --bind-user=NAME Bind user from host to virtual machine\n" - " --bind-user-shell=BOOL|PATH\n" - " Configure the shell to use for --bind-user= users\n" - " --bind-user-group=GROUP\n" - " Add an auxiliary group to --bind-user= users\n" - "\n%3$sIntegration:%4$s\n" - " --forward-journal=FILE|DIR\n" - " Forward the VM's journal to the host\n" - " --pass-ssh-key=BOOL Create an SSH key to access the VM\n" - " --ssh-key-type=TYPE Choose what type of SSH key to pass\n" - "\n%3$sInput/Output:%4$s\n" - " --console=MODE Console mode (interactive, native, gui)\n" - " --background=COLOR Set ANSI color for background\n" - "\n%3$sCredentials:%4$s\n" - " --set-credential=ID:VALUE\n" - " Pass a credential with literal value to the VM\n" - " --load-credential=ID:PATH\n" - " Load credential for the VM from file or AF_UNIX\n" - " stream socket.\n" - "\nSee the %2$s for details.\n", + static const char* const groups[] = { + NULL, + "Image", + "Host Configuration", + "Execution", + "System Identity", + "Properties", + "User Namespacing", + "Mounts", + "Integration", + "Input/Output", + "Credentials", + }; + + _cleanup_(table_unref_many) Table* tables[ELEMENTSOF(groups) + 1] = {}; + + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + r = option_parser_get_help_table_group(groups[i], &tables[i]); + if (r < 0) + return r; + } + + (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], tables[3], tables[4], + tables[5], tables[6], tables[7], tables[8], tables[9], tables[10]); + + printf("%s [OPTIONS...] [ARGUMENTS...]\n\n" + "%sSpawn a command or OS in a virtual machine.%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + printf("\n%s%s:%s\n", ansi_underline(), groups[i] ?: "Options", ansi_normal()); + + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + + printf("\nSee the %s for details.\n", link); return 0; } @@ -285,200 +278,159 @@ static int parse_environment(void) { return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_CPUS, - ARG_RAM, - ARG_KVM, - ARG_VSOCK, - ARG_VSOCK_CID, - ARG_TPM, - ARG_LINUX, - ARG_INITRD, - ARG_QEMU_GUI, - ARG_NETWORK_USER_MODE, - ARG_UUID, - ARG_REGISTER, - ARG_KEEP_UNIT, - ARG_BIND, - ARG_BIND_RO, - ARG_EXTRA_DRIVE, - ARG_SECURE_BOOT, - ARG_PRIVATE_USERS, - ARG_FORWARD_JOURNAL, - ARG_PASS_SSH_KEY, - ARG_SSH_KEY_TYPE, - ARG_SET_CREDENTIAL, - ARG_LOAD_CREDENTIAL, - ARG_FIRMWARE, - ARG_DISCARD_DISK, - ARG_CONSOLE, - ARG_BACKGROUND, - ARG_TPM_STATE, - ARG_NO_ASK_PASSWORD, - ARG_PROPERTY, - ARG_NOTIFY_READY, - ARG_BIND_USER, - ARG_BIND_USER_SHELL, - ARG_BIND_USER_GROUP, - ARG_SYSTEM, - ARG_USER, - ARG_IMAGE_FORMAT, - }; +static int parse_ram(const char *s) { + _cleanup_free_ char *ram = NULL, *ram_max = NULL, *ram_slots = NULL; + int r; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "quiet", no_argument, NULL, 'q' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "image", required_argument, NULL, 'i' }, - { "image-format", required_argument, NULL, ARG_IMAGE_FORMAT }, - { "ephemeral", no_argument, NULL, 'x' }, - { "directory", required_argument, NULL, 'D' }, - { "machine", required_argument, NULL, 'M' }, - { "slice", required_argument, NULL, 'S' }, - { "cpus", required_argument, NULL, ARG_CPUS }, - { "qemu-smp", required_argument, NULL, ARG_CPUS }, /* Compat alias */ - { "ram", required_argument, NULL, ARG_RAM }, - { "qemu-mem", required_argument, NULL, ARG_RAM }, /* Compat alias */ - { "kvm", required_argument, NULL, ARG_KVM }, - { "qemu-kvm", required_argument, NULL, ARG_KVM }, /* Compat alias */ - { "vsock", required_argument, NULL, ARG_VSOCK }, - { "qemu-vsock", required_argument, NULL, ARG_VSOCK }, /* Compat alias */ - { "vsock-cid", required_argument, NULL, ARG_VSOCK_CID }, - { "tpm", required_argument, NULL, ARG_TPM }, - { "linux", required_argument, NULL, ARG_LINUX }, - { "initrd", required_argument, NULL, ARG_INITRD }, - { "console", required_argument, NULL, ARG_CONSOLE }, - { "qemu-gui", no_argument, NULL, ARG_QEMU_GUI }, /* compat option */ - { "network-tap", no_argument, NULL, 'n' }, - { "network-user-mode", no_argument, NULL, ARG_NETWORK_USER_MODE }, - { "uuid", required_argument, NULL, ARG_UUID }, - { "register", required_argument, NULL, ARG_REGISTER }, - { "keep-unit", no_argument, NULL, ARG_KEEP_UNIT }, - { "bind", required_argument, NULL, ARG_BIND }, - { "bind-ro", required_argument, NULL, ARG_BIND_RO }, - { "extra-drive", required_argument, NULL, ARG_EXTRA_DRIVE }, - { "secure-boot", required_argument, NULL, ARG_SECURE_BOOT }, - { "private-users", required_argument, NULL, ARG_PRIVATE_USERS }, - { "forward-journal", required_argument, NULL, ARG_FORWARD_JOURNAL }, - { "pass-ssh-key", required_argument, NULL, ARG_PASS_SSH_KEY }, - { "ssh-key-type", required_argument, NULL, ARG_SSH_KEY_TYPE }, - { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL }, - { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL }, - { "firmware", required_argument, NULL, ARG_FIRMWARE }, - { "discard-disk", required_argument, NULL, ARG_DISCARD_DISK }, - { "background", required_argument, NULL, ARG_BACKGROUND }, - { "smbios11", required_argument, NULL, 's' }, - { "grow-image", required_argument, NULL, 'G' }, - { "tpm-state", required_argument, NULL, ARG_TPM_STATE }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "property", required_argument, NULL, ARG_PROPERTY }, - { "notify-ready", required_argument, NULL, ARG_NOTIFY_READY }, - { "bind-user", required_argument, NULL, ARG_BIND_USER }, - { "bind-user-shell", required_argument, NULL, ARG_BIND_USER_SHELL }, - { "bind-user-group", required_argument, NULL, ARG_BIND_USER_GROUP }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; + assert(s); + + const char *p = s; + r = extract_many_words(&p, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &ram, &ram_max, &ram_slots); + if (r < 0) + return log_error_errno(r, "Failed to parse --ram=%s: %m", s); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse --ram=%s", s); + if (!isempty(p)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected trailing data in --ram=%s", s); + + r = parse_size(ram, 1024, &arg_ram); + if (r < 0) + return log_error_errno(r, "Failed to parse --ram=%s: %m", s); + + if (!isempty(ram_max)) { + r = parse_size(ram_max, 1024, &arg_ram_max); + if (r < 0) + return log_error_errno(r, "Failed to parse --ram=%s: %m", s); + } else + arg_ram_max = 0; + + if (!isempty(ram_slots)) { + r = safe_atou(ram_slots, &arg_ram_slots); + if (r < 0) + return log_error_errno(r, "Failed to parse --ram=%s: %m", s); + } else + arg_ram_slots = 0; + + return 0; +} - int c, r; +static int parse_argv(int argc, char *argv[]) { + int r; + + /* Firmware with enrolled keys has been known to cause issues, skip by default */ + r = set_put_strdup(&arg_firmware_features_exclude, "enrolled-keys"); + if (r < 0) + return log_oom(); assert(argc >= 0); assert(argv); - optind = 0; - while ((c = getopt_long(argc, argv, "+hD:i:xM:nqs:G:S:", options, NULL)) >= 0) + OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + const Option *current; + const char *arg; + + FOREACH_OPTION_FULL(&state, c, ¤t, &arg, /* on_error= */ return c) switch (c) { - case 'h': + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'q': + OPTION('q', "quiet", NULL, "Do not show status information"): arg_quiet = true; break; - case 'D': - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_directory); + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + OPTION_LONG("system", NULL, "Run in the system service manager scope"): + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + OPTION_LONG("user", NULL, "Run in the user service manager scope"): + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + + OPTION_GROUP("Image"): {} + + OPTION('D', "directory", "PATH", "Root directory for the VM"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_directory); if (r < 0) return r; + break; + OPTION('x', "ephemeral", NULL, "Run VM with snapshot of the disk or directory"): + arg_ephemeral = true; break; - case 'i': - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + OPTION('i', "image", "FILE|DEVICE", "Root file system disk image or device for the VM"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; - break; - case ARG_IMAGE_FORMAT: - arg_image_format = image_format_from_string(optarg); + OPTION_LONG("image-format", "FORMAT", "Specify disk image format (raw, qcow2; default: raw)"): + arg_image_format = image_format_from_string(arg); if (arg_image_format < 0) return log_error_errno(arg_image_format, - "Invalid image format: %s", optarg); - break; - - case 'M': - if (isempty(optarg)) - arg_machine = mfree(arg_machine); - else { - if (!hostname_is_valid(optarg, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid machine name: %s", optarg); - - r = free_and_strdup(&arg_machine, optarg); - if (r < 0) - return log_oom(); - } + "Invalid image format: %s", arg); break; - case 'x': - arg_ephemeral = true; + OPTION_LONG("image-disk-type", "TYPE", + "Specify disk type (virtio-blk, virtio-scsi, nvme, scsi-cd; default: virtio-blk)"): + arg_image_disk_type = disk_type_from_string(arg); + if (arg_image_disk_type < 0) + return log_error_errno(arg_image_disk_type, + "Invalid image disk type: %s", arg); break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; + OPTION_GROUP("Host Configuration"): {} - case ARG_CPUS: - r = free_and_strdup_warn(&arg_cpus, optarg); + OPTION_LONG("cpus", "CPUS", "Configure number of CPUs in guest"): {} + OPTION_LONG("qemu-smp", "CPUS", /* help= */ NULL): /* Compat alias */ + r = free_and_strdup_warn(&arg_cpus, arg); if (r < 0) return r; break; - case ARG_RAM: - r = parse_size(optarg, 1024, &arg_ram); + OPTION_LONG("ram", "BYTES[:MAXBYTES[:SLOTS]]", + "Configure guest's RAM size (and max/slots for hotplug)"): {} + OPTION_LONG("qemu-mem", "BYTES", /* help= */ NULL): /* Compat alias */ + r = parse_ram(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --ram=%s: %m", optarg); + return r; break; - case ARG_KVM: - r = parse_tristate_argument_with_auto("--kvm=", optarg, &arg_kvm); + OPTION_LONG("kvm", "BOOL", "Enable use of KVM"): {} + OPTION_LONG("qemu-kvm", "BOOL", /* help= */ NULL): /* Compat alias */ + r = parse_tristate_argument_with_auto("--kvm=", arg, &arg_kvm); if (r < 0) return r; break; - case ARG_VSOCK: - r = parse_tristate_argument_with_auto("--vsock=", optarg, &arg_vsock); + OPTION_LONG("vsock", "BOOL", "Override autodetection of VSOCK support"): {} + OPTION_LONG("qemu-vsock", "BOOL", /* help= */ NULL): /* Compat alias */ + r = parse_tristate_argument_with_auto("--vsock=", arg, &arg_vsock); if (r < 0) return r; break; - case ARG_VSOCK_CID: - if (isempty(optarg)) + OPTION_LONG("vsock-cid", "CID", "Specify the CID to use for the guest's VSOCK support"): + if (isempty(arg)) arg_vsock_cid = VMADDR_CID_ANY; else { unsigned cid; - r = vsock_parse_cid(optarg, &cid); + r = vsock_parse_cid(arg, &cid); if (r < 0) - return log_error_errno(r, "Failed to parse --vsock-cid: %s", optarg); + return log_error_errno(r, "Failed to parse --vsock-cid: %s", arg); if (!VSOCK_CID_IS_REGULAR(cid)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified CID is not regular, refusing: %u", cid); @@ -486,160 +438,121 @@ static int parse_argv(int argc, char *argv[]) { } break; - case ARG_TPM: - r = parse_tristate_argument_with_auto("--tpm=", optarg, &arg_tpm); - if (r < 0) - return r; - break; - - case ARG_LINUX: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_linux); + OPTION_LONG("tpm", "BOOL", "Enable use of a virtual TPM"): + r = parse_tristate_argument_with_auto("--tpm=", arg, &arg_tpm); if (r < 0) return r; break; - case ARG_INITRD: { - _cleanup_free_ char *initrd_path = NULL; - r = parse_path_argument(optarg, /* suppress_root= */ false, &initrd_path); - if (r < 0) - return r; - - r = strv_consume(&arg_initrds, TAKE_PTR(initrd_path)); - if (r < 0) - return log_oom(); - - break; - } - - case ARG_CONSOLE: - arg_console_mode = console_mode_from_string(optarg); - if (arg_console_mode < 0) - return log_error_errno(arg_console_mode, "Failed to parse specified console mode: %s", optarg); - - break; - - case ARG_QEMU_GUI: - arg_console_mode = CONSOLE_GUI; - break; - - case 'n': - arg_network_stack = NETWORK_STACK_TAP; - break; - - case ARG_NETWORK_USER_MODE: - arg_network_stack = NETWORK_STACK_USER; - break; + OPTION_LONG("tpm-state", "off|auto|PATH", "Where to store TPM state"): + r = isempty(arg) ? false : + streq(arg, "auto") ? true : + parse_boolean(arg); + if (r >= 0) { + arg_tpm_state_mode = r ? STATE_AUTO : STATE_OFF; + arg_tpm_state_path = mfree(arg_tpm_state_path); + break; + } - case ARG_UUID: - r = id128_from_string_nonzero(optarg, &arg_uuid); - if (r == -ENXIO) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine UUID may not be all zeroes."); - if (r < 0) - return log_error_errno(r, "Invalid UUID: %s", optarg); + if (!path_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --tpm-state= parameter: %s", arg); - break; + if (!path_is_absolute(arg) && !startswith(arg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --tpm-state= parameter must be absolute or start with './': %s", arg); - case ARG_REGISTER: - r = parse_boolean_argument("--register=", optarg, &arg_register); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm_state_path); if (r < 0) return r; + arg_tpm_state_mode = STATE_PATH; break; - case ARG_KEEP_UNIT: - arg_keep_unit = true; - break; + OPTION_LONG("efi-nvram-template", "PATH", "Set the path to the EFI NVRAM template file to use"): + if (!isempty(arg) && !path_is_absolute(arg) && !startswith(arg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required."); - case ARG_BIND: - case ARG_BIND_RO: - r = runtime_mount_parse(&arg_runtime_mounts, optarg, c == ARG_BIND_RO); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_efi_nvram_template); if (r < 0) - return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", optarg); - + return r; break; - case ARG_EXTRA_DRIVE: { - _cleanup_free_ char *buf = NULL, *drive_path = NULL; - ImageFormat format = IMAGE_FORMAT_RAW; - - const char *colon = strrchr(optarg, ':'); - if (colon) { - ImageFormat f = image_format_from_string(colon + 1); - if (f < 0) - log_debug_errno(f, "Failed to parse image format '%s', assuming it is a part of path, ignoring: %m", colon + 1); - else { - format = f; - buf = strndup(optarg, colon - optarg); - if (!buf) - return log_oom(); - } + OPTION_LONG("efi-nvram-state", "off|auto|PATH", "Where to store EFI Variable NVRAM state"): + r = isempty(arg) ? false : + streq(arg, "auto") ? true : + parse_boolean(arg); + if (r >= 0) { + arg_efi_nvram_state_mode = r ? STATE_AUTO : STATE_OFF; + arg_efi_nvram_state_path = mfree(arg_efi_nvram_state_path); + break; } - r = parse_path_argument(buf ?: optarg, /* suppress_root= */ false, &drive_path); - if (r < 0) - return r; - - if (!GREEDY_REALLOC(arg_extra_drives.drives, arg_extra_drives.n_drives + 1)) - return log_oom(); - - arg_extra_drives.drives[arg_extra_drives.n_drives++] = (ExtraDrive) { - .path = TAKE_PTR(drive_path), - .format = format, - }; + if (!path_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --efi-nvram-state= parameter: %s", arg); - break; - } + if (!path_is_absolute(arg) && !startswith(arg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --efi-nvram-state= parameter must be absolute or start with './': %s", arg); - case ARG_SECURE_BOOT: - r = parse_tristate_argument_with_auto("--secure-boot=", optarg, &arg_secure_boot); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_efi_nvram_state_path); if (r < 0) return r; + + arg_efi_nvram_state_mode = STATE_PATH; break; - case ARG_PRIVATE_USERS: - r = parse_userns_uid_range(optarg, &arg_uid_shift, &arg_uid_range); + OPTION_LONG("linux", "PATH", "Specify the linux kernel for direct kernel boot"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_linux); if (r < 0) return r; break; - case ARG_FORWARD_JOURNAL: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_forward_journal); + OPTION_LONG("initrd", "PATH", "Specify the initrd for direct kernel boot"): { + _cleanup_free_ char *initrd_path = NULL; + r = parse_path_argument(arg, /* suppress_root= */ false, &initrd_path); if (r < 0) return r; - break; - case ARG_PASS_SSH_KEY: - r = parse_boolean_argument("--pass-ssh-key=", optarg, &arg_pass_ssh_key); + r = strv_consume(&arg_initrds, TAKE_PTR(initrd_path)); if (r < 0) - return r; + return log_oom(); break; + } - case ARG_SSH_KEY_TYPE: - if (!string_is_safe(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value for --arg-ssh-key-type=: %s", optarg); - - r = free_and_strdup_warn(&arg_ssh_key_type, optarg); - if (r < 0) - return r; + OPTION('n', "network-tap", NULL, "Create a TAP device for networking"): + arg_network_stack = NETWORK_STACK_TAP; break; - case ARG_SET_CREDENTIAL: { - r = machine_credential_set(&arg_credentials, optarg); - if (r < 0) - return r; + OPTION_LONG("network-user-mode", NULL, "Use user mode networking"): + arg_network_stack = NETWORK_STACK_USER; break; - } - case ARG_LOAD_CREDENTIAL: { - r = machine_credential_load(&arg_credentials, optarg); + OPTION_LONG("secure-boot", "BOOL|auto", "Enable searching for firmware supporting SecureBoot"): { + int b; + + r = parse_tristate_argument_with_auto("--secure-boot=", arg, &b); if (r < 0) return r; + free(set_remove(arg_firmware_features_include, "secure-boot")); + free(set_remove(arg_firmware_features_exclude, "secure-boot")); + + if (b >= 0) { + r = set_put_strdup(b > 0 ? &arg_firmware_features_include : &arg_firmware_features_exclude, "secure-boot"); + if (r < 0) + return log_oom(); + } break; } - case ARG_FIRMWARE: - if (streq(optarg, "list")) { + OPTION_LONG("firmware", "auto|uefi|bios|none|PATH|list|describe", + "Select firmware to use, or a firmware definition file (or list/describe available)"): { + if (isempty(arg) || streq(arg, "auto")) { + arg_firmware = mfree(arg_firmware); + arg_firmware_type = _FIRMWARE_INVALID; + arg_firmware_describe = false; + break; + } + + if (streq(arg, "list")) { _cleanup_strv_free_ char **l = NULL; r = list_ovmf_config(&l); @@ -654,163 +567,355 @@ static int parse_argv(int argc, char *argv[]) { return 0; } - if (!isempty(optarg) && !path_is_absolute(optarg) && !startswith(optarg, "./")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required."); + if (streq(arg, "describe")) { + /* Handled after argument parsing so that --firmware-features= is + * taken into account. */ + arg_firmware = mfree(arg_firmware); + /* We only look for UEFI firmware when "describe" is specified. */ + arg_firmware_type = FIRMWARE_UEFI; + arg_firmware_describe = true; + break; + } - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_firmware); - if (r < 0) - return r; + Firmware f = firmware_from_string(arg); + if (f >= 0) { + arg_firmware = mfree(arg_firmware); + arg_firmware_type = f; + arg_firmware_describe = false; + break; + } - break; + if (!path_is_absolute(arg) && !startswith(arg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Expected one of 'auto', 'uefi', 'bios', 'none', 'list', 'describe', or an absolute path or path starting with './', got: %s", + arg); - case ARG_DISCARD_DISK: - r = parse_boolean_argument("--discard-disk=", optarg, &arg_discard_disk); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_firmware); if (r < 0) return r; - break; - case ARG_BACKGROUND: - r = parse_background_argument(optarg, &arg_background); - if (r < 0) - return r; + arg_firmware_type = FIRMWARE_UEFI; + arg_firmware_describe = false; break; + } - case 's': - if (isempty(optarg)) { - arg_smbios11 = strv_free(arg_smbios11); + OPTION_LONG("firmware-features", "FEATURE,...|list", + "Require/exclude specific firmware features"): { + if (isempty(arg)) { + arg_firmware_features_include = set_free(arg_firmware_features_include); + arg_firmware_features_exclude = set_free(arg_firmware_features_exclude); break; } - if (!utf8_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SMBIOS Type 11 string is not UTF-8 clean, refusing: %s", optarg); + if (streq(arg, "list")) { + _cleanup_strv_free_ char **l = NULL; + + r = list_ovmf_firmware_features(&l); + if (r < 0) + return log_error_errno(r, "Failed to list firmware features: %m"); + + bool nl = false; + fputstrv(stdout, l, "\n", &nl); + if (nl) + putchar('\n'); + + return 0; + } - if (strv_extend(&arg_smbios11, optarg) < 0) + _cleanup_strv_free_ char **features = strv_split(arg, ","); + if (!features) return log_oom(); + STRV_FOREACH(feature, features) { + const char *e = startswith(*feature, "~"); + r = set_put_strdup(e ? &arg_firmware_features_exclude : &arg_firmware_features_include, e ?: *feature); + if (r < 0) + return log_oom(); + } + break; + } + + OPTION_LONG("discard-disk", "BOOL", "Control processing of discard requests"): + r = parse_boolean_argument("--discard-disk=", arg, &arg_discard_disk); + if (r < 0) + return r; break; - case 'G': - if (isempty(optarg)) { + OPTION('G', "grow-image", "BYTES", "Grow image file to specified size in bytes"): + if (isempty(arg)) { arg_grow_image = 0; break; } - r = parse_size(optarg, 1024, &arg_grow_image); + r = parse_size(arg, 1024, &arg_grow_image); if (r < 0) - return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", optarg); - + return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", arg); break; - case ARG_TPM_STATE: - if (path_is_valid(optarg) && (path_is_absolute(optarg) || path_startswith(optarg, "./"))) { - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm_state_path); - if (r < 0) - return r; + OPTION_GROUP("Execution"): {} - arg_tpm_state_mode = TPM_STATE_PATH; + OPTION('s', "smbios11", "STRING", "Pass an arbitrary SMBIOS Type #11 string to the VM"): + if (isempty(arg)) { + arg_smbios11 = strv_free(arg_smbios11); break; } - r = isempty(optarg) ? false : - streq(optarg, "auto") ? true : - parse_boolean(optarg); + if (!utf8_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SMBIOS Type 11 string is not UTF-8 clean, refusing: %s", arg); + + if (strv_extend(&arg_smbios11, arg) < 0) + return log_oom(); + break; + + OPTION_LONG("notify-ready", "BOOL", "Wait for ready notification from the VM"): + r = parse_boolean_argument("--notify-ready=", arg, &arg_notify_ready); if (r < 0) - return log_error_errno(r, "Failed to parse --tpm-state= parameter: %s", optarg); + return r; + break; + + OPTION_GROUP("System Identity"): {} + + OPTION('M', "machine", "NAME", "Set the machine name for the VM"): + if (isempty(arg)) + arg_machine = mfree(arg_machine); + else { + if (!hostname_is_valid(arg, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid machine name: %s", arg); - arg_tpm_state_mode = r ? TPM_STATE_AUTO : TPM_STATE_OFF; - arg_tpm_state_path = mfree(arg_tpm_state_path); + r = free_and_strdup(&arg_machine, arg); + if (r < 0) + return log_oom(); + } break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION_LONG("uuid", "UUID", "Set a specific machine UUID for the VM"): + r = id128_from_string_nonzero(arg, &arg_uuid); + if (r == -ENXIO) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine UUID may not be all zeroes."); + if (r < 0) + return log_error_errno(r, "Invalid UUID: %s", arg); break; - case 'S': { + OPTION_GROUP("Properties"): {} + + OPTION('S', "slice", "SLICE", "Place the VM in the specified slice"): { _cleanup_free_ char *mangled = NULL; - r = unit_name_mangle_with_suffix(optarg, /* operation= */ NULL, UNIT_NAME_MANGLE_WARN, ".slice", &mangled); + r = unit_name_mangle_with_suffix(arg, /* operation= */ NULL, UNIT_NAME_MANGLE_WARN, ".slice", &mangled); if (r < 0) - return log_error_errno(r, "Failed to turn '%s' into unit name: %m", optarg); + return log_error_errno(r, "Failed to turn '%s' into unit name: %m", arg); free_and_replace(arg_slice, mangled); break; } - case ARG_PROPERTY: - if (strv_extend(&arg_property, optarg) < 0) + OPTION_LONG("property", "NAME=VALUE", "Set scope unit property"): + if (strv_extend(&arg_property, arg) < 0) return log_oom(); + break; + OPTION_LONG("register", "BOOLEAN", "Register VM as machine"): + r = parse_tristate_argument_with_auto("--register=", arg, &arg_register); + if (r < 0) + return r; + break; + + OPTION_LONG("keep-unit", NULL, + "Do not register a scope for the machine, reuse the service unit vmspawn is running in"): + arg_keep_unit = true; break; - case ARG_NOTIFY_READY: - r = parse_boolean_argument("--notify-ready=", optarg, &arg_notify_ready); + OPTION_GROUP("User Namespacing"): {} + + OPTION_LONG("private-users", "UIDBASE[:NUIDS]", + "Configure the UID/GID range to map into the virtiofsd namespace"): + r = parse_userns_uid_range(arg, &arg_uid_shift, &arg_uid_range); if (r < 0) return r; + break; + + OPTION_GROUP("Mounts"): {} + OPTION_LONG("bind", "SOURCE[:TARGET]", "Mount a file or directory from the host into the VM"): {} + OPTION_LONG("bind-ro", "SOURCE[:TARGET]", "Mount a file or directory, but read-only"): { + bool read_only = streq(current->long_code, "bind-ro"); + r = runtime_mount_parse(&arg_runtime_mounts, arg, read_only); + if (r < 0) + return log_error_errno(r, "Failed to parse --%s= argument %s: %m", current->long_code, arg); break; + } + + OPTION_LONG("extra-drive", "[FORMAT:][DISKTYPE:]PATH", "Adds an additional disk to the VM"): { + ImageFormat format = IMAGE_FORMAT_RAW; + DiskType extra_disk_type = _DISK_TYPE_INVALID; + _cleanup_free_ char *drive_path = NULL; - case ARG_BIND_USER: - if (!valid_user_group_name(optarg, /* flags= */ 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", optarg); + r = parse_disk_spec(arg, &format, &extra_disk_type, &drive_path); + if (r < 0) + return r; - if (strv_extend(&arg_bind_user, optarg) < 0) + if (!GREEDY_REALLOC(arg_extra_drives.drives, arg_extra_drives.n_drives + 1)) return log_oom(); + arg_extra_drives.drives[arg_extra_drives.n_drives++] = (ExtraDrive) { + .path = TAKE_PTR(drive_path), + .format = format, + .disk_type = extra_disk_type, + }; + break; + } + + OPTION_LONG("bind-user", "NAME", "Bind user from host to virtual machine"): + if (!valid_user_group_name(arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", arg); + + if (strv_extend(&arg_bind_user, arg) < 0) + return log_oom(); break; - case ARG_BIND_USER_SHELL: { + OPTION_LONG("bind-user-shell", "BOOL|PATH", + "Configure the shell to use for --bind-user= users"): { bool copy = false; char *sh = NULL; - r = parse_user_shell(optarg, &sh, ©); + r = parse_user_shell(arg, &sh, ©); if (r == -ENOMEM) return log_oom(); if (r < 0) - return log_error_errno(r, "Invalid user shell to bind: %s", optarg); + return log_error_errno(r, "Invalid user shell to bind: %s", arg); free_and_replace(arg_bind_user_shell, sh); arg_bind_user_shell_copy = copy; - break; } - case ARG_BIND_USER_GROUP: - if (!valid_user_group_name(optarg, /* flags= */ 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", optarg); + OPTION_LONG("bind-user-group", "GROUP", "Add an auxiliary group to --bind-user= users"): + if (!valid_user_group_name(arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", arg); - if (strv_extend(&arg_bind_user_groups, optarg) < 0) + if (strv_extend(&arg_bind_user_groups, arg) < 0) return log_oom(); + break; + OPTION_GROUP("Integration"): {} + + OPTION_LONG("forward-journal", "FILE|DIR", "Forward the VM's journal to the host"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_forward_journal); + if (r < 0) + return r; break; - case ARG_SYSTEM: - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + OPTION_LONG("forward-journal-max-use", "BYTES", "Maximum disk space for forwarded journal"): + r = parse_size(arg, 1024, &arg_forward_journal_max_use); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-max-use= value: %s", optarg); break; - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; + OPTION_LONG("forward-journal-keep-free", "BYTES", "Minimum disk space to keep free"): + r = parse_size(arg, 1024, &arg_forward_journal_keep_free); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-keep-free= value: %s", optarg); break; - case '?': - return -EINVAL; + OPTION_LONG("forward-journal-max-file-size", "BYTES", "Maximum size of individual journal files"): + r = parse_size(arg, 1024, &arg_forward_journal_max_file_size); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-max-file-size= value: %s", optarg); + break; - default: - assert_not_reached(); - } + OPTION_LONG("forward-journal-max-files", "N", "Maximum number of journal files to keep"): + r = safe_atou64(arg, &arg_forward_journal_max_files); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-max-files= value: %s", optarg); + break; - /* Drop duplicate --bind-user= and --bind-user-group= entries */ - strv_uniq(arg_bind_user); - strv_uniq(arg_bind_user_groups); + OPTION_LONG("pass-ssh-key", "BOOL", "Create an SSH key to access the VM"): + r = parse_boolean_argument("--pass-ssh-key=", arg, &arg_pass_ssh_key); + if (r < 0) + return r; + break; - if (arg_bind_user_shell && strv_isempty(arg_bind_user)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-shell= without --bind-user="); + OPTION_LONG("ssh-key-type", "TYPE", "Choose what type of SSH key to pass"): + if (isempty(arg)) { + arg_ssh_key_type = mfree(arg_ssh_key_type); + break; + } - if (!strv_isempty(arg_bind_user_groups) && strv_isempty(arg_bind_user)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-group= without --bind-user="); + if (!string_is_safe(arg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value for --ssh-key-type=: %s", arg); - if (arg_ephemeral && arg_extra_drives.n_drives > 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --ephemeral with --extra-drive="); + r = free_and_strdup_warn(&arg_ssh_key_type, arg); + if (r < 0) + return r; + break; - if (arg_uid_shift != UID_INVALID && !arg_directory) + OPTION_GROUP("Input/Output"): {} + + OPTION_LONG("console", "MODE", + "Console mode (interactive, native, gui, read-only or headless)"): + arg_console_mode = console_mode_from_string(arg); + if (arg_console_mode < 0) + return log_error_errno(arg_console_mode, "Failed to parse specified console mode: %s", arg); + break; + + OPTION_LONG("console-transport", "TRANSPORT", "Console transport (virtio or serial)"): + arg_console_transport = console_transport_from_string(arg); + if (arg_console_transport < 0) + return log_error_errno(arg_console_transport, "Failed to parse specified console transport: %s", arg); + break; + + OPTION_LONG("qemu-gui", NULL, /* help= */ NULL): /* Compat alias */ + arg_console_mode = CONSOLE_GUI; + break; + + OPTION_LONG("background", "COLOR", "Set ANSI color for background"): + r = parse_background_argument(arg, &arg_background); + if (r < 0) + return r; + break; + + OPTION_GROUP("Credentials"): {} + + OPTION_LONG("set-credential", "ID:VALUE", "Pass a credential with literal value to the VM"): + r = machine_credential_set(&arg_credentials, arg); + if (r < 0) + return r; + break; + + OPTION_LONG("load-credential", "ID:PATH", + "Load credential for the VM from file or AF_UNIX stream socket"): + r = machine_credential_load(&arg_credentials, arg); + if (r < 0) + return r; + break; + } + + /* Drop duplicate --bind-user= and --bind-user-group= entries */ + strv_uniq(arg_bind_user); + strv_uniq(arg_bind_user_groups); + + if (arg_bind_user_shell && strv_isempty(arg_bind_user)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-shell= without --bind-user="); + + if (!strv_isempty(arg_bind_user_groups) && strv_isempty(arg_bind_user)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-group= without --bind-user="); + + if (arg_ram_max > 0 && arg_ram_max < arg_ram) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Maximum RAM size must be greater than or equal to initial RAM size"); + + if (arg_ram_slots > 0 && arg_ram_max == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Memory hotplug slots require a maximum RAM size"); + + if ((arg_forward_journal_max_use != UINT64_MAX || + arg_forward_journal_keep_free != UINT64_MAX || + arg_forward_journal_max_file_size != UINT64_MAX || + arg_forward_journal_max_files != UINT64_MAX) && !arg_forward_journal) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--forward-journal-max-use=/--forward-journal-keep-free=/--forward-journal-max-file-size=/--forward-journal-max-files= require --forward-journal=."); + + if (arg_ephemeral && arg_extra_drives.n_drives > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --ephemeral with --extra-drive="); + + if (arg_uid_shift != UID_INVALID && !arg_directory) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--private-users= is only supported in combination with --directory=."); if (arg_directory && arg_uid_shift == UID_INVALID) { @@ -826,8 +931,9 @@ static int parse_argv(int argc, char *argv[]) { arg_uid_range = 0x10000; } - if (argc > optind) { - arg_kernel_cmdline_extra = strv_copy(argv + optind); + char **args = option_parser_get_args(&state); + if (!strv_isempty(args)) { + arg_kernel_cmdline_extra = strv_copy(args); if (!arg_kernel_cmdline_extra) return log_oom(); } @@ -956,6 +1062,8 @@ static int vmspawn_dispatch_vsock_connections(sd_event_source *source, int fd, u sd_event *event; int r; + assert(source); + assert(fd >= 0); assert(userdata); if (revents != EPOLLIN) { @@ -1142,6 +1250,7 @@ static int shutdown_vm_graceful(sd_event_source *s, const struct signalfd_siginf } static int on_child_exit(sd_event_source *s, const siginfo_t *si, void *userdata) { + assert(s); assert(si); /* Let's first do some logging about the exit status of the child. */ @@ -1175,12 +1284,15 @@ static int on_child_exit(sd_event_source *s, const siginfo_t *si, void *userdata return 0; } -static int cmdline_add_vsock(char ***cmdline, int vsock_fd) { - int r; +static bool smbios_supported(void) { + /* SMBIOS is always available on x86 (via SeaBIOS fallback), but on + * other architectures it requires UEFI firmware to be loaded. */ + return ARCHITECTURE_SUPPORTS_SMBIOS && + (IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64) || arg_firmware_type == FIRMWARE_UEFI); +} - r = strv_extend(cmdline, "-smbios"); - if (r < 0) - return r; +static int add_vsock_credential(int vsock_fd) { + assert(vsock_fd >= 0); union sockaddr_union addr; socklen_t addr_len = sizeof addr.vm; @@ -1190,17 +1302,19 @@ static int cmdline_add_vsock(char ***cmdline, int vsock_fd) { assert(addr_len >= sizeof addr.vm); assert(addr.vm.svm_family == AF_VSOCK); - r = strv_extendf(cmdline, "type=11,value=io.systemd.credential:vmm.notify_socket=vsock-stream:%u:%u", (unsigned) VMADDR_CID_HOST, addr.vm.svm_port); - if (r < 0) - return r; + _cleanup_free_ char *value = NULL; + if (asprintf(&value, "vsock-stream:%u:%u", (unsigned) VMADDR_CID_HOST, addr.vm.svm_port) < 0) + return -ENOMEM; - return 0; + return machine_credential_add(&arg_credentials, "vmm.notify_socket", value, SIZE_MAX); } -static int cmdline_add_kernel_cmdline(char ***cmdline, const char *kernel, const char *smbios_dir) { +static int cmdline_add_kernel_cmdline(char ***cmdline, int smbios_dir_fd, const char *smbios_dir) { int r; assert(cmdline); + assert(smbios_dir_fd >= 0); + assert(smbios_dir); if (strv_isempty(arg_kernel_cmdline_extra)) return 0; @@ -1209,27 +1323,36 @@ static int cmdline_add_kernel_cmdline(char ***cmdline, const char *kernel, const if (!kcl) return log_oom(); - if (kernel) { + size_t kcl_len = strlen(kcl); + if (kcl_len >= KERNEL_CMDLINE_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), + "Kernel command line length (%zu) exceeds the kernel's COMMAND_LINE_SIZE (%d).", + kcl_len, KERNEL_CMDLINE_SIZE); + + if (arg_linux_image_type >= 0 && arg_linux_image_type != KERNEL_IMAGE_TYPE_UKI) { if (strv_extend_many(cmdline, "-append", kcl) < 0) return log_oom(); } else { - if (!ARCHITECTURE_SUPPORTS_SMBIOS) { + if (!smbios_supported()) { log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS, ignoring."); return 0; } FOREACH_STRING(id, "io.systemd.stub.kernel-cmdline-extra", "io.systemd.boot.kernel-cmdline-extra") { - _cleanup_free_ char *p = path_join(smbios_dir, id); - if (!p) + _cleanup_free_ char *content = strjoin(id, "=", kcl); + if (!content) return log_oom(); - r = write_string_filef( - p, - WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE|WRITE_STRING_FILE_MODE_0600, - "%s=%s", id, kcl); + r = write_string_file_at( + smbios_dir_fd, id, content, + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE|WRITE_STRING_FILE_MODE_0600); if (r < 0) return log_error_errno(r, "Failed to write smbios kernel command line to file: %m"); + _cleanup_free_ char *p = path_join(smbios_dir, id); + if (!p) + return log_oom(); + if (strv_extend(cmdline, "-smbios") < 0) return log_oom(); @@ -1241,15 +1364,112 @@ static int cmdline_add_kernel_cmdline(char ***cmdline, const char *kernel, const return 0; } -static int cmdline_add_smbios11(char ***cmdline, const char* smbios_dir) { +static int cmdline_add_credentials(char ***cmdline, int smbios_dir_fd, const char *smbios_dir) { + int r; + + assert(cmdline); + assert(smbios_dir_fd >= 0); + assert(smbios_dir); + + FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) { + _cleanup_free_ char *cred_data_b64 = NULL; + ssize_t n; + + n = base64mem(cred->data, cred->size, &cred_data_b64); + if (n < 0) + return log_oom(); + + if (smbios_supported()) { + _cleanup_free_ char *content = NULL; + if (asprintf(&content, "io.systemd.credential.binary:%s=%s", cred->id, cred_data_b64) < 0) + return log_oom(); + + r = write_string_file_at( + smbios_dir_fd, cred->id, content, + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE|WRITE_STRING_FILE_MODE_0600); + if (r < 0) + return log_error_errno(r, "Failed to write smbios credential file: %m"); + + _cleanup_free_ char *p = path_join(smbios_dir, cred->id); + if (!p) + return log_oom(); + + if (strv_extend(cmdline, "-smbios") < 0) + return log_oom(); + + if (strv_extend_joined(cmdline, "type=11,path=", p) < 0) + return log_oom(); + + } else if (ARCHITECTURE_SUPPORTS_FW_CFG) { + /* fw_cfg keys are limited to 55 characters */ + _cleanup_free_ char *key = strjoin("opt/io.systemd.credentials/", cred->id); + if (!key) + return log_oom(); + + if (strlen(key) <= QEMU_FW_CFG_MAX_KEY_LEN) { + r = write_data_file_atomic_at( + smbios_dir_fd, cred->id, + &IOVEC_MAKE(cred->data, cred->size), + WRITE_DATA_FILE_MODE_0400); + if (r < 0) + return log_error_errno(r, "Failed to write fw_cfg credential file: %m"); + + _cleanup_free_ char *p = path_join(smbios_dir, cred->id); + if (!p) + return log_oom(); + + if (strv_extend(cmdline, "-fw_cfg") < 0) + return log_oom(); + + if (strv_extendf(cmdline, "name=%s,file=%s", key, p) < 0) + return log_oom(); + + continue; + } + + /* Fall through to kernel command line if key is too long */ + log_notice("fw_cfg key '%s' exceeds %d character limit, passing credential via kernel command line. " + "Note that this will make literal credentials readable to unprivileged userspace.", + key, QEMU_FW_CFG_MAX_KEY_LEN); + + if (arg_linux_image_type < 0) + return log_error_errno( + SYNTHETIC_ERRNO(E2BIG), + "Cannot pass credential '%s' to VM, fw_cfg key exceeds %d character limit and no kernel for direct boot specified.", + cred->id, + QEMU_FW_CFG_MAX_KEY_LEN); + + if (strv_extendf(&arg_kernel_cmdline_extra, + "systemd.set_credential_binary=%s:%s", cred->id, cred_data_b64) < 0) + return log_oom(); + + } else if (arg_linux_image_type >= 0) { + log_notice("Both SMBIOS and fw_cfg are not supported, passing credential via kernel command line. " + "Note that this will make literal credentials readable to unprivileged userspace."); + if (strv_extendf(&arg_kernel_cmdline_extra, + "systemd.set_credential_binary=%s:%s", cred->id, cred_data_b64) < 0) + return log_oom(); + } else + return log_error_errno( + SYNTHETIC_ERRNO(EOPNOTSUPP), + "Cannot pass credential '%s' to VM, native architecture doesn't support SMBIOS or fw_cfg and no kernel for direct boot specified.", + cred->id); + } + + return 0; +} + +static int cmdline_add_smbios11(char ***cmdline, int smbios_dir_fd, const char *smbios_dir) { int r; assert(cmdline); + assert(smbios_dir_fd >= 0); + assert(smbios_dir); if (strv_isempty(arg_smbios11)) return 0; - if (!ARCHITECTURE_SUPPORTS_SMBIOS) { + if (!smbios_supported()) { log_warning("Cannot issue SMBIOS Type #11 strings, native architecture doesn't support SMBIOS, ignoring."); return 0; } @@ -1261,8 +1481,13 @@ static int cmdline_add_smbios11(char ***cmdline, const char* smbios_dir) { if (r < 0) return r; - r = write_string_file( - p, *i, + _cleanup_free_ char *fn = NULL; + r = path_extract_filename(p, &fn); + if (r < 0) + return r; + + r = write_string_file_at( + smbios_dir_fd, fn, *i, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE|WRITE_STRING_FILE_MODE_0600); if (r < 0) return log_error_errno(r, "Failed to write smbios data to smbios file %s: %m", p); @@ -1292,6 +1517,7 @@ static int start_tpm( assert(scope); assert(swtpm); assert(runtime_dir); + assert(sd_socket_activate); _cleanup_free_ char *scope_prefix = NULL; r = unit_name_to_prefix(scope, &scope_prefix); @@ -1322,48 +1548,11 @@ static int start_tpm( if (r < 0) return log_error_errno(r, "Failed to create TPM state directory '%s': %m", state_dir); - _cleanup_free_ char *swtpm_setup = NULL; - r = find_executable("swtpm_setup", &swtpm_setup); - if (r < 0) - return log_error_errno(r, "Failed to find swtpm_setup binary: %m"); - - /* Try passing --profile-name default-v2 first, in order to support RSA4096 pcrsig keys, which was - * added in 0.11. */ - _cleanup_strv_free_ char **argv = strv_new( - swtpm_setup, - "--tpm-state", state_dir, - "--tpm2", - "--pcr-banks", "sha256", - "--not-overwrite", - "--profile-name", "default-v2"); - if (!argv) - return log_oom(); - - r = pidref_safe_fork("(swtpm-setup)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, /* ret= */ NULL); - if (r == 0) { - /* Child */ - execvp(argv[0], argv); - log_error_errno(errno, "Failed to execute '%s': %m", argv[0]); - _exit(EXIT_FAILURE); - } - if (r == -EPROTO) { - /* If swtpm_setup fails, try again removing the default-v2 profile, as it might be an older - * version. */ - strv_remove(argv, "--profile-name"); - strv_remove(argv, "default-v2"); - - r = pidref_safe_fork("(swtpm-setup)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, /* ret= */ NULL); - if (r == 0) { - /* Child */ - execvp(argv[0], argv); - log_error_errno(errno, "Failed to execute '%s': %m", argv[0]); - _exit(EXIT_FAILURE); - } - } + r = manufacture_swtpm(state_dir, /* secret= */ NULL); if (r < 0) - return log_error_errno(r, "Failed to run swtpm_setup: %m"); + return r; - strv_free(argv); + _cleanup_strv_free_ char **argv = NULL; argv = strv_new(sd_socket_activate, "--listen", listen_address, swtpm, "socket", "--tpm2", "--tpmstate"); if (!argv) return log_oom(); @@ -1386,57 +1575,6 @@ static int start_tpm( return 0; } -static int start_systemd_journal_remote( - const char *scope, - unsigned port, - const char *sd_socket_activate, - char **ret_listen_address, - PidRef *ret_pidref) { - - int r; - - assert(scope); - - _cleanup_free_ char *scope_prefix = NULL; - r = unit_name_to_prefix(scope, &scope_prefix); - if (r < 0) - return log_error_errno(r, "Failed to strip .scope suffix from scope: %m"); - - _cleanup_free_ char *listen_address = NULL; - if (asprintf(&listen_address, "vsock:2:%u", port) < 0) - return log_oom(); - - _cleanup_free_ char *sd_journal_remote = NULL; - r = find_executable_full( - "systemd-journal-remote", - /* root= */ NULL, - STRV_MAKE(LIBEXECDIR), - /* use_path_envvar= */ true, /* systemd-journal-remote should be installed in - * LIBEXECDIR, but for supporting fancy setups. */ - &sd_journal_remote, - /* ret_fd= */ NULL); - if (r < 0) - return log_error_errno(r, "Failed to find systemd-journal-remote binary: %m"); - - _cleanup_strv_free_ char **argv = strv_new( - sd_socket_activate, - "--listen", listen_address, - sd_journal_remote, - "--output", arg_forward_journal, - "--split-mode", endswith(arg_forward_journal, ".journal") ? "none" : "host"); - if (!argv) - return log_oom(); - - r = fork_notify(argv, ret_pidref); - if (r < 0) - return r; - - if (ret_listen_address) - *ret_listen_address = TAKE_PTR(listen_address); - - return 0; -} - static int discover_root(char **ret) { int r; _cleanup_(dissected_image_unrefp) DissectedImage *image = NULL; @@ -1471,33 +1609,30 @@ static int discover_root(char **ret) { static int find_virtiofsd(char **ret) { int r; - _cleanup_free_ char *virtiofsd = NULL; assert(ret); - r = find_executable("virtiofsd", &virtiofsd); - if (r < 0 && r != -ENOENT) + r = find_executable("virtiofsd", ret); + if (r >= 0) + return 0; + if (r != -ENOENT) return log_error_errno(r, "Error while searching for virtiofsd: %m"); - if (!virtiofsd) { - FOREACH_STRING(file, "/usr/libexec/virtiofsd", "/usr/lib/virtiofsd") { - if (access(file, X_OK) >= 0) { - virtiofsd = strdup(file); - if (!virtiofsd) - return log_oom(); - break; - } + FOREACH_STRING(file, "/usr/libexec/virtiofsd", "/usr/lib/virtiofsd") { + if (access(file, X_OK) >= 0) { + _cleanup_free_ char *copy = strdup(file); + if (!copy) + return log_oom(); - if (!IN_SET(errno, ENOENT, EACCES)) - return log_error_errno(errno, "Error while searching for virtiofsd: %m"); + *ret = TAKE_PTR(copy); + return 0; } - } - if (!virtiofsd) - return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to find virtiofsd binary."); + if (!IN_SET(errno, ENOENT, EACCES)) + return log_error_errno(errno, "Error while searching for virtiofsd: %m"); + } - *ret = TAKE_PTR(virtiofsd); - return 0; + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to find virtiofsd binary."); } static int start_virtiofsd( @@ -1555,8 +1690,9 @@ static int start_virtiofsd( "--shared-dir", source_uid == FOREIGN_UID_MIN ? "/run/systemd/mount-rootfs" : directory, "--xattr", "--fd", sockstr, - "--sandbox=chroot", - "--no-announce-submounts"); + "--no-announce-submounts", + "--log-level=error", + "--modcaps=-mknod"); if (!argv) return log_oom(); @@ -1707,22 +1843,20 @@ static int bind_user_setup( static int kernel_cmdline_maybe_append_root(void) { int r; - bool cmdline_contains_root = strv_find_startswith(arg_kernel_cmdline_extra, "root=") - || strv_find_startswith(arg_kernel_cmdline_extra, "mount.usr="); - if (!cmdline_contains_root) { - _cleanup_free_ char *root = NULL; + if (strv_find_startswith(arg_kernel_cmdline_extra, "root=") || + strv_find_startswith(arg_kernel_cmdline_extra, "mount.usr=")) + return 0; - r = discover_root(&root); - if (r < 0) - return r; + _cleanup_free_ char *root = NULL; + r = discover_root(&root); + if (r < 0) + return r; - log_debug("Determined root file system %s from dissected image", root); + log_debug("Determined root file system '%s' from dissected image", root); - r = strv_consume(&arg_kernel_cmdline_extra, TAKE_PTR(root)); - if (r < 0) - return log_oom(); - } + if (strv_consume(&arg_kernel_cmdline_extra, TAKE_PTR(root)) < 0) + return log_oom(); return 0; } @@ -1946,194 +2080,656 @@ static int on_request_stop(sd_bus_message *m, void *userdata, sd_bus_error *erro return 0; } -static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { - _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; - _cleanup_free_ char *qemu_binary = NULL, *mem = NULL, *kernel = NULL; - _cleanup_(rm_rf_physical_and_freep) char *ssh_private_key_path = NULL, *ssh_public_key_path = NULL; - _cleanup_(rm_rf_subvolume_and_freep) char *snapshot_directory = NULL; - _cleanup_(release_lock_file) LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT; - _cleanup_close_ int notify_sock_fd = -EBADF; - _cleanup_strv_free_ char **cmdline = NULL; - _cleanup_free_ int *pass_fds = NULL; - sd_event_source **children = NULL; - size_t n_children = 0, n_pass_fds = 0; - const char *accel; +static int make_sidecar_path(const char *suffix, char **ret) { int r; - CLEANUP_ARRAY(children, n_children, fork_notify_terminate_many); + assert(suffix); + assert(ret); - polkit_agent_open(); + const char *p = ASSERT_PTR(arg_image ?: arg_directory); - /* Registration always happens on the system bus */ - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *system_bus = NULL; - if (arg_register || arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) { - r = sd_bus_default_system(&system_bus); - if (r < 0) - return log_error_errno(r, "Failed to open system bus: %m"); + _cleanup_free_ char *parent = NULL, *filename = NULL; + r = path_split_prefix_filename(p, &parent, &filename); + if (r < 0) + return log_error_errno(r, "Failed to extract parent directory and filename from '%s': %m", p); - r = sd_bus_set_close_on_exit(system_bus, false); - if (r < 0) - return log_error_errno(r, "Failed to disable close-on-exit behaviour: %m"); + if (!strextend(&filename, suffix)) + return log_oom(); - (void) sd_bus_set_allow_interactive_authorization(system_bus, arg_ask_password); - } + _cleanup_free_ char *j = path_join(parent, filename); + if (!j) + return log_oom(); - /* Scope allocation happens on the user bus if we are unpriv, otherwise system bus. */ - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *user_bus = NULL; - _cleanup_(sd_bus_unrefp) sd_bus *runtime_bus = NULL; - if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) - runtime_bus = sd_bus_ref(system_bus); - else { - r = sd_bus_default_user(&user_bus); - if (r < 0) - return log_error_errno(r, "Failed to open system bus: %m"); + *ret = TAKE_PTR(j); + return 0; +} - r = sd_bus_set_close_on_exit(user_bus, false); - if (r < 0) - return log_error_errno(r, "Failed to disable close-on-exit behaviour: %m"); +/* Device serial numbers have length limits (e.g. 20 for NVMe, 30 for SCSI). + * If the filename fits, use it directly; otherwise hash it with SHA-256 and + * take the first max_len hex characters. max_len must be even and <= 64. + * The filename should already be QEMU-escaped (commas doubled) so that the + * result can be embedded directly in a -device argument. */ +static int disk_serial(const char *filename, size_t max_len, char **ret) { + assert(filename); + assert(ret); + assert(max_len % 2 == 0); + assert(max_len <= SHA256_DIGEST_SIZE * 2); - runtime_bus = sd_bus_ref(user_bus); - } + if (strlen(filename) <= max_len) + return strdup_to(ret, filename); - bool use_kvm = arg_kvm > 0; - if (arg_kvm < 0) { - r = qemu_check_kvm_support(); - if (r < 0) - return log_error_errno(r, "Failed to check for KVM support: %m"); - use_kvm = r; - } + uint8_t hash[SHA256_DIGEST_SIZE]; + sha256_direct(filename, strlen(filename), hash); - if (arg_firmware) - r = load_ovmf_config(arg_firmware, &ovmf_config); - else - r = find_ovmf_config(arg_secure_boot, &ovmf_config); - if (r < 0) - return log_error_errno(r, "Failed to find OVMF config: %m"); + _cleanup_free_ char *serial = hexmem(hash, max_len / 2); + if (!serial) + return -ENOMEM; - if (arg_secure_boot > 0 && !ovmf_config->supports_sb) { - assert(arg_firmware); + *ret = TAKE_PTR(serial); + return 0; +} - return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), - "Secure Boot requested, but supplied OVMF firmware blob doesn't support it."); - } +static int cmdline_add_ovmf(FILE *config_file, const OvmfConfig *ovmf_config, char **ret_ovmf_vars) { + int r; - if (arg_secure_boot < 0) - log_debug("Using OVMF firmware %s Secure Boot support.", ovmf_config->supports_sb ? "with" : "without"); + assert(config_file); + assert(ret_ovmf_vars); - _cleanup_(machine_bind_user_context_freep) MachineBindUserContext *bind_user_context = NULL; - r = machine_bind_user_prepare( - /* directory= */ NULL, - arg_bind_user, - arg_bind_user_shell, - arg_bind_user_shell_copy, - "/run/vmhost/home", - arg_bind_user_groups, - &bind_user_context); - if (r < 0) - return r; + if (!ovmf_config) { + *ret_ovmf_vars = NULL; + return 0; + } - r = bind_user_setup(bind_user_context, &arg_credentials, &arg_runtime_mounts); + r = qemu_config_section(config_file, "drive", "ovmf-code", + "if", "pflash", + "format", ovmf_config_format(ovmf_config), + "readonly", "on", + "file", ovmf_config->path); if (r < 0) return r; - _cleanup_free_ char *machine = NULL; - const char *shm = arg_directory || arg_runtime_mounts.n_mounts != 0 ? ",memory-backend=mem" : ""; - const char *hpet = ARCHITECTURE_SUPPORTS_HPET ? ",hpet=off" : ""; - if (ARCHITECTURE_SUPPORTS_SMM) - machine = strjoin("type=" QEMU_MACHINE_TYPE ",smm=", on_off(ovmf_config->supports_sb), shm, hpet); - else - machine = strjoin("type=" QEMU_MACHINE_TYPE, shm, hpet); - if (!machine) - return log_oom(); + if (!ovmf_config->vars && !arg_efi_nvram_template) { + *ret_ovmf_vars = NULL; + return 0; + } - if (arg_linux) { - kernel = strdup(arg_linux); - if (!kernel) - return log_oom(); - } else if (arg_directory) { - /* a kernel is required for directory type images so attempt to locate a UKI under /boot and /efi */ - r = discover_boot_entry(arg_directory, &kernel, &arg_initrds); + if (arg_efi_nvram_state_mode == STATE_AUTO && !arg_ephemeral) { + assert(!arg_efi_nvram_state_path); + + r = make_sidecar_path(".efinvramstate", &arg_efi_nvram_state_path); if (r < 0) - return log_error_errno(r, "Failed to locate UKI in directory type image, please specify one with --linux=."); + return r; - log_debug("Discovered UKI image at %s", kernel); + log_debug("Storing EFI NVRAM state persistently under '%s'.", arg_efi_nvram_state_path); } - r = find_qemu_binary(&qemu_binary); - if (r == -EOPNOTSUPP) - return log_error_errno(r, "Native architecture is not supported by qemu."); - if (r < 0) - return log_error_errno(r, "Failed to find QEMU binary: %m"); + const char *vars_source = arg_efi_nvram_template ?: ovmf_config->vars; + _cleanup_close_ int target_fd = -EBADF; + _cleanup_(unlink_and_freep) char *destroy_path = NULL; + bool newly_created; + const char *state; + if (arg_efi_nvram_state_path) { + _cleanup_free_ char *d = strdup(arg_efi_nvram_state_path); + if (!d) + return log_oom(); - if (asprintf(&mem, "%" PRIu64 "M", DIV_ROUND_UP(arg_ram, U64_MB)) < 0) - return log_oom(); + target_fd = openat_report_new(AT_FDCWD, arg_efi_nvram_state_path, O_WRONLY|O_CREAT|O_CLOEXEC, 0600, &newly_created); + if (target_fd < 0) + return log_error_errno(target_fd, "Failed to open file for OVMF vars at %s: %m", arg_efi_nvram_state_path); - cmdline = strv_new( - qemu_binary, - "-machine", machine, - "-smp", arg_cpus ?: "1", - "-m", mem, - "-object", "rng-random,filename=/dev/urandom,id=rng0", - "-device", "virtio-rng-pci,rng=rng0,id=rng-device0", - "-device", "virtio-balloon,free-page-reporting=on" - ); - if (!cmdline) + if (newly_created) + destroy_path = TAKE_PTR(d); + + r = fd_verify_regular(target_fd); + if (r < 0) + return log_error_errno(r, "Not a regular file for OVMF variables at %s: %m", arg_efi_nvram_state_path); + + state = arg_efi_nvram_state_path; + } else { + _cleanup_free_ char *t = NULL; + r = tempfn_random_child(/* p= */ NULL, "vmspawn-", &t); + if (r < 0) + return log_error_errno(r, "Failed to create temporary filename: %m"); + + target_fd = open(t, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0600); + if (target_fd < 0) + return log_error_errno(errno, "Failed to create regular file for OVMF vars at %s: %m", t); + + newly_created = true; + state = *ret_ovmf_vars = TAKE_PTR(t); + } + + if (newly_created) { + _cleanup_close_ int source_fd = open(vars_source, O_RDONLY|O_CLOEXEC); + if (source_fd < 0) + return log_error_errno(errno, "Failed to open OVMF vars file %s: %m", vars_source); + + r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", vars_source, state); + + /* This isn't always available so don't raise an error if it fails */ + (void) copy_times(source_fd, target_fd, 0); + } + + destroy_path = mfree(destroy_path); /* disarm auto-destroy */ + + /* Mark the UEFI variable store pflash as requiring SMM access. This + * prevents the guest OS from writing to pflash directly, ensuring all + * variable updates go through the firmware's validation checks. Without + * this, secure boot keys could be overwritten by the OS. */ + if (ARCHITECTURE_SUPPORTS_SMM) { + r = qemu_config_section(config_file, "global", /* id= */ NULL, + "driver", "cfi.pflash01", + "property", "secure", + "value", "on"); + if (r < 0) + return r; + } + + r = qemu_config_section(config_file, "drive", "ovmf-vars", + "file", state, + "if", "pflash", + "format", ovmf_config_format(ovmf_config)); + if (r < 0) + return r; + + return 0; +} + +/* Create a QMP control socketpair, add QEMU's end to pass_fds, and write the chardev + monitor + * config sections. Returns with bridge_fds populated: [0] is vmspawn's end, [1] is QEMU's end + * (also in pass_fds). FORK_CLOEXEC_OFF clears CLOEXEC on pass_fds in the child. */ +static int qemu_config_add_qmp_monitor(FILE *config_file, int bridge_fds[2], int **pass_fds, size_t *n_pass_fds) { + int r; + + assert(config_file); + assert(bridge_fds); + assert(pass_fds); + assert(n_pass_fds); + + if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, bridge_fds) < 0) + return log_error_errno(errno, "Failed to create QMP socketpair: %m"); + + if (!GREEDY_REALLOC(*pass_fds, *n_pass_fds + 1)) return log_oom(); + (*pass_fds)[(*n_pass_fds)++] = bridge_fds[1]; - if (!sd_id128_is_null(arg_uuid)) - if (strv_extend_many(&cmdline, "-uuid", SD_ID128_TO_UUID_STRING(arg_uuid)) < 0) - return log_oom(); + r = qemu_config_section(config_file, "chardev", "qmp", + "backend", "socket"); + if (r < 0) + return r; - if (ARCHITECTURE_SUPPORTS_VMGENID) { - /* Derive a vmgenid automatically from the invocation ID, in a deterministic way. */ - sd_id128_t vmgenid; - r = sd_id128_get_invocation_app_specific(SD_ID128_MAKE(bd,84,6d,e3,e4,7d,4b,6c,a6,85,4a,87,0f,3c,a3,a0), &vmgenid); - if (r < 0) { - log_debug_errno(r, "Failed to get invocation ID, making up randomized vmgenid: %m"); + r = qemu_config_keyf(config_file, "fd", "%d", bridge_fds[1]); + if (r < 0) + return r; - r = sd_id128_randomize(&vmgenid); - if (r < 0) - return log_error_errno(r, "Failed to make up randomized vmgenid: %m"); + return qemu_config_section(config_file, "mon", "qmp", + "chardev", "qmp", + "mode", "control"); +} + +static int resolve_disk_driver(DiskType dt, const char *filename, DriveInfo *info) { + size_t serial_max; + int r; + + assert(filename); + assert(info); + + switch (dt) { + case DISK_TYPE_VIRTIO_BLK: + serial_max = DISK_SERIAL_MAX_LEN_VIRTIO_BLK; + break; + case DISK_TYPE_VIRTIO_SCSI: + serial_max = DISK_SERIAL_MAX_LEN_SCSI; + break; + case DISK_TYPE_NVME: + serial_max = DISK_SERIAL_MAX_LEN_NVME; + break; + case DISK_TYPE_VIRTIO_SCSI_CDROM: + serial_max = DISK_SERIAL_MAX_LEN_SCSI; + info->flags |= QMP_DRIVE_READ_ONLY; + break; + default: + assert_not_reached(); + } + + info->disk_driver = strdup(ASSERT_PTR(qemu_device_driver_to_string(dt))); + if (!info->disk_driver) + return log_oom(); + + r = disk_serial(filename, serial_max, &info->serial); + if (r < 0) + return r; + + return 0; +} + +static int prepare_primary_drive(const char *runtime_dir, DriveInfos *drives) { + int r; + + assert(runtime_dir); + assert(drives); + + if (!arg_image) + return 0; + + _cleanup_free_ char *image_fn = NULL; + r = path_extract_filename(arg_image, &image_fn); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from path '%s': %m", arg_image); + + _cleanup_(drive_info_unrefp) DriveInfo *d = drive_info_new(); + if (!d) + return log_oom(); + + r = resolve_disk_driver(arg_image_disk_type, image_fn, d); + if (r < 0) + return log_error_errno(r, "Failed to resolve disk driver for '%s': %m", image_fn); + + int open_flags = ((arg_ephemeral || FLAGS_SET(d->flags, QMP_DRIVE_READ_ONLY)) ? O_RDONLY : O_RDWR) | O_CLOEXEC | O_NOCTTY; + + _cleanup_close_ int image_fd = open(arg_image, open_flags); + if (image_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", arg_image); + + struct stat st; + if (fstat(image_fd, &st) < 0) + return log_error_errno(errno, "Failed to stat '%s': %m", arg_image); + + r = stat_verify_regular_or_block(&st); + if (r < 0) + return log_error_errno(r, "Expected regular file or block device for image: %s", arg_image); + + d->path = strdup(arg_image); + d->format = strdup(ASSERT_PTR(image_format_to_string(arg_image_format))); + if (!d->path || !d->format) + return log_oom(); + d->fd = TAKE_FD(image_fd); + if (S_ISBLK(st.st_mode)) + d->flags |= QMP_DRIVE_BLOCK_DEVICE; + if (arg_discard_disk && !FLAGS_SET(d->flags, QMP_DRIVE_READ_ONLY)) + d->flags |= QMP_DRIVE_DISCARD; + d->flags |= QMP_DRIVE_BOOT; + + /* For ephemeral mode, create an anonymous overlay file. QEMU will format it + * as qcow2 via blockdev-create, so no filesystem path is needed. + * Skip for read-only drives (e.g. CDROM) where overlays are not meaningful. */ + if (arg_ephemeral && !FLAGS_SET(d->flags, QMP_DRIVE_READ_ONLY)) { + _cleanup_close_ int overlay_fd = open(runtime_dir, O_TMPFILE | O_RDWR | O_CLOEXEC, 0600); + if (overlay_fd < 0) { + if (!ERRNO_IS_NOT_SUPPORTED(errno)) + return log_error_errno(errno, "Failed to create ephemeral overlay in '%s': %m", runtime_dir); + + /* Fallback to memfd if O_TMPFILE is not supported */ + overlay_fd = memfd_new("vmspawn-overlay"); + if (overlay_fd < 0) + return log_error_errno(overlay_fd, "Failed to create ephemeral overlay via memfd: %m"); } + d->overlay_fd = TAKE_FD(overlay_fd); + d->flags |= QMP_DRIVE_NO_FLUSH; + } - if (strv_extend(&cmdline, "-device") < 0) + drives->drives[drives->n_drives++] = TAKE_PTR(d); + return 0; +} + +static int prepare_extra_drives(DriveInfos *drives) { + int r; + + assert(drives); + + FOREACH_ARRAY(drive, arg_extra_drives.drives, arg_extra_drives.n_drives) { + _cleanup_free_ char *drive_fn = NULL; + r = path_extract_filename(drive->path, &drive_fn); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from path '%s': %m", drive->path); + + DiskType dt = drive->disk_type >= 0 ? drive->disk_type : arg_image_disk_type; + + _cleanup_(drive_info_unrefp) DriveInfo *d = drive_info_new(); + if (!d) return log_oom(); - if (strv_extendf(&cmdline, "vmgenid,guid=" SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(vmgenid)) < 0) + r = resolve_disk_driver(dt, drive_fn, d); + if (r < 0) + return log_error_errno(r, "Failed to resolve disk driver for '%s': %m", drive_fn); + + _cleanup_close_ int drive_fd = open(drive->path, (FLAGS_SET(d->flags, QMP_DRIVE_READ_ONLY) ? O_RDONLY : O_RDWR) | O_CLOEXEC | O_NOCTTY); + if (drive_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", drive->path); + + struct stat drive_st; + if (fstat(drive_fd, &drive_st) < 0) + return log_error_errno(errno, "Failed to stat '%s': %m", drive->path); + r = stat_verify_regular_or_block(&drive_st); + if (r < 0) + return log_error_errno(r, "Expected regular file or block device, not '%s'.", drive->path); + if (S_ISBLK(drive_st.st_mode) && drive->format == IMAGE_FORMAT_QCOW2) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Block device '%s' cannot be used with 'qcow2' format, only 'raw' is supported.", + drive->path); + + d->path = strdup(drive->path); + d->format = strdup(ASSERT_PTR(image_format_to_string(drive->format))); + if (!d->path || !d->format) return log_oom(); + d->fd = TAKE_FD(drive_fd); + if (S_ISBLK(drive_st.st_mode)) + d->flags |= QMP_DRIVE_BLOCK_DEVICE; + d->flags |= QMP_DRIVE_NO_FLUSH; + + drives->drives[drives->n_drives++] = TAKE_PTR(d); } - /* if we are going to be starting any units with state then create our runtime dir */ - _cleanup_free_ char *runtime_dir = NULL; - _cleanup_(rm_rf_physical_and_freep) char *runtime_dir_destroy = NULL; - if (arg_tpm != 0 || arg_directory || arg_runtime_mounts.n_mounts != 0 || arg_pass_ssh_key) { - _cleanup_free_ char *subdir = NULL; + return 0; +} + +/* Assign PCIe root port names to devices. The ports were pre-allocated in the config + * file. Each PCI device that will be hotplugged via QMP device_add gets a port. */ +static int assign_pcie_ports(MachineConfig *c) { + assert(c); + + if (!ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS) + return 0; - if (asprintf(&subdir, "systemd/vmspawn.%" PRIx64, random_u64()) < 0) + DriveInfos *drives = &c->drives; + NetworkInfo *network = &c->network; + VirtiofsInfos *virtiofs = &c->virtiofs; + VsockInfo *vsock = &c->vsock; + + size_t port = 0; + + /* Non-SCSI drives get individual ports. SCSI controllers (if any) allocate + * from the hotplug-spares pool on demand at device-add time. */ + FOREACH_ARRAY(d, drives->drives, drives->n_drives) { + DriveInfo *drive = *d; + if (STR_IN_SET(drive->disk_driver, "scsi-hd", "scsi-cd")) + continue; + if (asprintf(&drive->pcie_port, "vmspawn-pcieport-%zu", port++) < 0) + return log_oom(); + } + + if (network->type) + if (asprintf(&network->pcie_port, "vmspawn-pcieport-%zu", port++) < 0) + return log_oom(); + + FOREACH_ARRAY(v, virtiofs->entries, virtiofs->n_entries) + if (asprintf(&v->pcie_port, "vmspawn-pcieport-%zu", port++) < 0) + return log_oom(); + + if (vsock->fd >= 0) + if (asprintf(&vsock->pcie_port, "vmspawn-pcieport-%zu", port++) < 0) return log_oom(); - r = runtime_directory(arg_runtime_scope, subdir, &runtime_dir); + return 0; +} + +static int prepare_device_info(const char *runtime_dir, MachineConfig *c) { + int r; + + assert(runtime_dir); + assert(c); + + DriveInfos *drives = &c->drives; + + /* Build drive info for QMP-based setup. vmspawn opens all image files and + * passes fds to QEMU via add-fd — QEMU never needs filesystem access. */ + drives->drives = new0(DriveInfo*, 1 + arg_extra_drives.n_drives); + if (!drives->drives) + return log_oom(); + + r = prepare_primary_drive(runtime_dir, drives); + if (r < 0) + return r; + + r = prepare_extra_drives(drives); + if (r < 0) + return r; + + return assign_pcie_ports(c); +} + +static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { + _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; + _cleanup_free_ char *qemu_binary = NULL, *mem = NULL; + _cleanup_(rm_rf_physical_and_freep) char *ssh_private_key_path = NULL, *ssh_public_key_path = NULL; + _cleanup_(rm_rf_subvolume_and_freep) char *snapshot_directory = NULL; + _cleanup_(release_lock_file) LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT; + _cleanup_close_ int notify_sock_fd = -EBADF; + _cleanup_strv_free_ char **cmdline = NULL; + _cleanup_free_ int *pass_fds = NULL; + _cleanup_(machine_config_done) MachineConfig config = { + .network = { .fd = -EBADF }, + .vsock = { .fd = -EBADF }, + }; + sd_event_source **children = NULL; + size_t n_children = 0, n_pass_fds = 0; + int r; + + CLEANUP_ARRAY(children, n_children, fork_notify_terminate_many); + + polkit_agent_open(); + + /* Registration always happens on the system bus */ + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *system_bus = NULL; + if (arg_register != 0 || arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) { + r = sd_bus_default_system(&system_bus); if (r < 0) - return log_error_errno(r, "Failed to lookup runtime directory: %m"); - if (r > 0) { /* We need to create our own runtime dir */ - r = mkdir_p(runtime_dir, 0755); + return log_error_errno(r, "Failed to open system bus: %m"); + + r = sd_bus_set_close_on_exit(system_bus, false); + if (r < 0) + return log_error_errno(r, "Failed to disable close-on-exit behaviour: %m"); + + (void) sd_bus_set_allow_interactive_authorization(system_bus, arg_ask_password); + } + + /* Scope allocation and machine registration happen on the user bus if we are unpriv, otherwise system bus. */ + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *user_bus = NULL; + _cleanup_(sd_bus_unrefp) sd_bus *runtime_bus = NULL; + if (arg_register != 0 || !arg_keep_unit) { + if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) + runtime_bus = sd_bus_ref(system_bus); + else { + r = sd_bus_default_user(&user_bus); if (r < 0) - return log_error_errno(r, "Failed to create runtime directory '%s': %m", runtime_dir); + return log_error_errno(r, "Failed to open user bus: %m"); - /* We created this, hence also destroy it */ - runtime_dir_destroy = TAKE_PTR(runtime_dir); + r = sd_bus_set_close_on_exit(user_bus, false); + if (r < 0) + return log_error_errno(r, "Failed to disable close-on-exit behaviour: %m"); - runtime_dir = strdup(runtime_dir_destroy); - if (!runtime_dir) - return log_oom(); + runtime_bus = sd_bus_ref(user_bus); + } + } + + bool use_kvm = arg_kvm > 0; + if (arg_kvm < 0) { + r = qemu_check_kvm_support(); + if (r < 0) + return log_error_errno(r, "Failed to check for KVM support: %m"); + use_kvm = r; + } + + if (arg_firmware_type == FIRMWARE_UEFI) { + if (arg_firmware) + r = load_ovmf_config(arg_firmware, &ovmf_config); + else + r = find_ovmf_config(arg_firmware_features_include, arg_firmware_features_exclude, &ovmf_config, /* ret_firmware_json= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to find OVMF config: %m"); + + if (set_contains(arg_firmware_features_include, "secure-boot") && !ovmf_config->supports_sb) + return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), + "Secure Boot requested, but selected OVMF firmware doesn't support it."); + + log_debug("Using OVMF firmware %s Secure Boot support.", ovmf_config->supports_sb ? "with" : "without"); + } + + _cleanup_(machine_bind_user_context_freep) MachineBindUserContext *bind_user_context = NULL; + r = machine_bind_user_prepare( + /* directory= */ NULL, + arg_bind_user, + arg_bind_user_shell, + arg_bind_user_shell_copy, + "/run/vmhost/home", + arg_bind_user_groups, + &bind_user_context); + if (r < 0) + return r; + + r = bind_user_setup(bind_user_context, &arg_credentials, &arg_runtime_mounts); + if (r < 0) + return r; + + r = find_qemu_binary(&qemu_binary); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "Native architecture is not supported by qemu."); + if (r < 0) + return log_error_errno(r, "Failed to find QEMU binary: %m"); + + if (asprintf(&mem, "%" PRIu64 "M", DIV_ROUND_UP(arg_ram, U64_MB)) < 0) + return log_oom(); + + /* Create our runtime directory. We need this for the QMP varlink control socket, the QEMU + * config file, TPM state, virtiofsd sockets, runtime mounts, and SSH key material. */ + _cleanup_free_ char *runtime_dir = NULL, *runtime_dir_suffix = NULL; + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir_destroy = NULL; + + runtime_dir_suffix = path_join("systemd/vmspawn", arg_machine); + if (!runtime_dir_suffix) + return log_oom(); + + r = runtime_directory_make(arg_runtime_scope, runtime_dir_suffix, &runtime_dir, &runtime_dir_destroy); + if (r < 0) + return log_error_errno(r, "Failed to create runtime directory: %m"); + + /* If a previous vmspawn instance was killed without cleanup (e.g. SIGKILL), the directory may + * already exist with stale contents. This is harmless: varlink's sockaddr_un_unlink() removes stale + * sockets before bind(), and other files (QEMU config, SSH keys) are created fresh. This matches + * nspawn's approach of not proactively cleaning stale runtime directories. */ + + log_debug("Using runtime directory: %s", runtime_dir); + + /* Build a QEMU config file for -readconfig. Items that can be expressed as QemuOpts sections go + * here; things that require cmdline-only switches (e.g. -kernel, -smbios, -nographic, --add-fd) + * are added to the cmdline strv below. */ + _cleanup_fclose_ FILE *config_file = NULL; + _cleanup_(unlink_and_freep) char *config_path = NULL; + r = fopen_temporary_child(runtime_dir, &config_file, &config_path); + if (r < 0) + return log_error_errno(r, "Failed to create QEMU config file: %m"); + + r = qemu_config_section(config_file, "machine", /* id= */ NULL, + "type", QEMU_MACHINE_TYPE); + if (r < 0) + return r; + + if (ovmf_config && ARCHITECTURE_SUPPORTS_SMM) { + r = qemu_config_key(config_file, "smm", on_off(ovmf_config->supports_sb)); + if (r < 0) + return r; + } + + if (ARCHITECTURE_SUPPORTS_CXL) { + r = qemu_config_key(config_file, "cxl", "on"); + if (r < 0) + return r; + } + + if (arg_directory || arg_runtime_mounts.n_mounts != 0) { + r = qemu_config_key(config_file, "memory-backend", "mem"); + if (r < 0) + return r; + } + + if (ARCHITECTURE_SUPPORTS_HPET) { + r = qemu_config_key(config_file, "hpet", "off"); + if (r < 0) + return r; + } + + r = qemu_config_section(config_file, "smp-opts", /* id= */ NULL, + "cpus", arg_cpus ?: "1"); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "memory", /* id= */ NULL, + "size", mem); + if (r < 0) + return r; + + if (arg_ram_max > 0) { + r = qemu_config_keyf(config_file, "maxmem", "%" PRIu64 "M", DIV_ROUND_UP(arg_ram_max, U64_MB)); + if (r < 0) + return r; + + r = qemu_config_keyf(config_file, "slots", "%u", arg_ram_slots > 0 ? arg_ram_slots : 1u); + if (r < 0) + return r; + } + + r = qemu_config_section(config_file, "object", "rng0", + "qom-type", "rng-random", + "filename", "/dev/urandom"); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "device", "rng-device0", + "driver", "virtio-rng-pci", + "rng", "rng0"); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "device", "balloon0", + "driver", "virtio-balloon", + "free-page-reporting", "on"); + if (r < 0) + return r; + + if (ARCHITECTURE_SUPPORTS_VMGENID) { + sd_id128_t vmgenid; + r = sd_id128_get_invocation_app_specific(SD_ID128_MAKE(bd,84,6d,e3,e4,7d,4b,6c,a6,85,4a,87,0f,3c,a3,a0), &vmgenid); + if (r < 0) { + log_debug_errno(r, "Failed to get invocation ID, making up randomized vmgenid: %m"); + + r = sd_id128_randomize(&vmgenid); + if (r < 0) + return log_error_errno(r, "Failed to make up randomized vmgenid: %m"); } - log_debug("Using runtime directory: %s", runtime_dir); + r = qemu_config_section(config_file, "device", "vmgenid0", + "driver", "vmgenid"); + if (r < 0) + return r; + + r = qemu_config_keyf(config_file, "guid", SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(vmgenid)); + if (r < 0) + return r; } + /* Start building the cmdline for items that must remain as command line arguments. + * -S starts QEMU with vCPUs paused — we set up devices via QMP then resume with "cont". */ + cmdline = strv_new(qemu_binary, + "-S", + "-no-user-config"); + if (!cmdline) + return log_oom(); + + if (!sd_id128_is_null(arg_uuid)) + if (strv_extend_many(&cmdline, "-uuid", SD_ID128_TO_UUID_STRING(arg_uuid)) < 0) + return log_oom(); + _cleanup_close_ int delegate_userns_fd = -EBADF, tap_fd = -EBADF; + _cleanup_free_ char *tap_name = NULL; + struct ether_addr mac_vm = {}; + if (arg_network_stack == NETWORK_STACK_TAP) { if (have_effective_cap(CAP_NET_ADMIN) <= 0) { + /* Without CAP_NET_ADMIN we use nsresourced to create a TAP device. + * The TAP fd is passed to QEMU via QMP getfd + SCM_RIGHTS after + * the handshake, then referenced by name in netdev_add. */ delegate_userns_fd = userns_acquire_self_root(); if (delegate_userns_fd < 0) return log_error_errno(delegate_userns_fd, "Failed to acquire userns: %m"); @@ -2155,62 +2751,56 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (tap_fd < 0) return log_error_errno(tap_fd, "Failed to allocate network tap device: %m"); - r = strv_extend(&cmdline, "-nic"); - if (r < 0) - return log_oom(); - - r = strv_extendf(&cmdline, "tap,fd=%i,model=virtio-net-pci", tap_fd); - if (r < 0) - return log_oom(); - - if (!GREEDY_REALLOC(pass_fds, n_pass_fds + 1)) - return log_oom(); - - pass_fds[n_pass_fds++] = tap_fd; + config.network = (NetworkInfo) { + .type = "tap", + .fd = TAKE_FD(tap_fd), + }; } else { - _cleanup_free_ char *tap_name = NULL; - struct ether_addr mac_vm = {}; - + /* With CAP_NET_ADMIN we create the TAP interface by name. + * Configure via QMP after QEMU starts. */ tap_name = strjoin("vt-", arg_machine); if (!tap_name) return log_oom(); (void) net_shorten_ifname(tap_name, /* check_naming_scheme= */ false); - if (ether_addr_is_null(&arg_network_provided_mac)){ + if (ether_addr_is_null(&arg_network_provided_mac)) { r = net_generate_mac(arg_machine, &mac_vm, VM_TAP_HASH_KEY, 0); if (r < 0) return log_error_errno(r, "Failed to generate predictable MAC address for VM side: %m"); } else mac_vm = arg_network_provided_mac; - r = strv_extend(&cmdline, "-nic"); - if (r < 0) - return log_oom(); - - r = strv_extendf(&cmdline, "tap,ifname=%s,script=no,downscript=no,model=virtio-net-pci,mac=%s", tap_name, ETHER_ADDR_TO_STR(&mac_vm)); - if (r < 0) - return log_oom(); + config.network = (NetworkInfo) { + .type = "tap", + .ifname = TAKE_PTR(tap_name), + .mac = mac_vm, + .mac_set = true, + .fd = -EBADF, + }; } - } else if (arg_network_stack == NETWORK_STACK_USER) - r = strv_extend_many(&cmdline, "-nic", "user,model=virtio-net-pci"); - else + } else if (arg_network_stack == NETWORK_STACK_USER) { + config.network = (NetworkInfo) { + .type = "user", + .fd = -EBADF, + }; + } else { r = strv_extend_many(&cmdline, "-nic", "none"); - if (r < 0) - return log_oom(); - - /* A shared memory backend might increase ram usage so only add one if actually necessary for virtiofsd. */ - if (arg_directory || arg_runtime_mounts.n_mounts != 0) { - r = strv_extend(&cmdline, "-object"); if (r < 0) return log_oom(); + } - r = strv_extendf(&cmdline, "memory-backend-memfd,id=mem,size=%s,share=on", mem); + /* A shared memory backend might increase ram usage so only add one if actually necessary for virtiofsd. */ + if (arg_directory || arg_runtime_mounts.n_mounts != 0) { + r = qemu_config_section(config_file, "object", "mem", + "qom-type", "memory-backend-memfd", + "size", mem, + "share", "on"); if (r < 0) - return log_oom(); + return r; } - bool use_vsock = arg_vsock > 0 && ARCHITECTURE_SUPPORTS_SMBIOS; + bool use_vsock = arg_vsock > 0; if (arg_vsock < 0) { r = qemu_check_vsock_support(); if (r < 0) @@ -2225,10 +2815,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } if (use_kvm && kvm_device_fd >= 0) { - /* /dev/fdset/1 is magic string to tell qemu where to find the fd for /dev/kvm - * we use this so that we can take a fd to /dev/kvm and then give qemu that fd */ - accel = "kvm,device=/dev/fdset/1"; - r = strv_extend(&cmdline, "--add-fd"); if (r < 0) return log_oom(); @@ -2241,46 +2827,37 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_oom(); pass_fds[n_pass_fds++] = kvm_device_fd; - } else if (use_kvm) - accel = "kvm"; - else - accel = "tcg"; - r = strv_extend_many(&cmdline, "-accel", accel); - if (r < 0) - return log_oom(); + r = qemu_config_section(config_file, "accel", /* id= */ NULL, + "accel", "kvm", + "device", "/dev/fdset/1"); + if (r < 0) + return r; + } else { + r = qemu_config_section(config_file, "accel", /* id= */ NULL, + "accel", use_kvm ? "kvm" : "tcg"); + if (r < 0) + return r; + } - _cleanup_close_ int child_vsock_fd = -EBADF; unsigned child_cid = arg_vsock_cid; if (use_vsock) { - int device_fd = vhost_device_fd; + config.vsock.fd = TAKE_FD(vhost_device_fd); - if (device_fd < 0) { - child_vsock_fd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC); - if (child_vsock_fd < 0) + if (config.vsock.fd < 0) { + config.vsock.fd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC); + if (config.vsock.fd < 0) return log_error_errno(errno, "Failed to open /dev/vhost-vsock as read/write: %m"); - - device_fd = child_vsock_fd; } - r = vsock_fix_child_cid(device_fd, &child_cid, arg_machine); + r = vsock_fix_child_cid(config.vsock.fd, &child_cid, arg_machine); if (r < 0) return log_error_errno(r, "Failed to fix CID for the guest VSOCK socket: %m"); - r = strv_extend(&cmdline, "-device"); - if (r < 0) - return log_oom(); - - r = strv_extendf(&cmdline, "vhost-vsock-pci,guest-cid=%u,vhostfd=%d", child_cid, device_fd); - if (r < 0) - return log_oom(); - - if (!GREEDY_REALLOC(pass_fds, n_pass_fds + 1)) - return log_oom(); - - pass_fds[n_pass_fds++] = device_fd; + config.vsock.cid = child_cid; } + /* -cpu stays on cmdline since not all flags are supported in config */ r = strv_extend_many(&cmdline, "-cpu", #ifdef __x86_64__ "max,hv_relaxed,hv-vapic,hv-time" @@ -2295,125 +2872,124 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { PTYForwardFlags ptyfwd_flags = 0; switch (arg_console_mode) { + case CONSOLE_NATIVE: + /* Use a PTY instead of chardev stdio to prevent QEMU from setting O_NONBLOCK on + * our stdio file descriptions (see qemu's chardev/char-stdio.c and char-fd.c). + * Use PTY_FORWARD_DUMB_TERMINAL|PTY_FORWARD_TRANSPARENT so the forwarder just + * shovels bytes without any terminal manipulation or escape sequence handling. */ + ptyfwd_flags |= PTY_FORWARD_DUMB_TERMINAL|PTY_FORWARD_TRANSPARENT; + + _fallthrough_; + case CONSOLE_READ_ONLY: - ptyfwd_flags |= PTY_FORWARD_READ_ONLY; + if (arg_console_mode == CONSOLE_READ_ONLY) + ptyfwd_flags |= PTY_FORWARD_READ_ONLY; _fallthrough_; - case CONSOLE_INTERACTIVE: { + case CONSOLE_INTERACTIVE: { _cleanup_free_ char *pty_path = NULL; master = openpt_allocate(O_RDWR|O_NONBLOCK, &pty_path); if (master < 0) return log_error_errno(master, "Failed to setup pty: %m"); - if (strv_extend_many( - &cmdline, - "-nographic", - "-nodefaults", - "-device", "virtio-serial-pci,id=vmspawn-virtio-serial-pci", - "-chardev") < 0) - return log_oom(); - - if (strv_extend_joined(&cmdline, "serial,id=console,path=", pty_path) < 0) + r = strv_extend_many(&cmdline, "-nographic", "-nodefaults"); + if (r < 0) return log_oom(); - r = strv_extend_many( - &cmdline, - "-device", "virtconsole,chardev=console"); - break; - } + /* Enable mux for native console so the QEMU monitor is accessible via Ctrl-a c */ + r = qemu_config_section(config_file, "chardev", "console", + "backend", "serial", + "path", pty_path, + "mux", on_off(arg_console_mode == CONSOLE_NATIVE)); + if (r < 0) + return r; - case CONSOLE_GUI: - /* Enable support for the qemu guest agent for clipboard sharing, resolution scaling, etc. */ - r = strv_extend_many( - &cmdline, - "-vga", - "virtio", - "-device", "virtio-serial", - "-chardev", "spicevmc,id=vdagent,debug=0,name=vdagent", - "-device", "virtserialport,chardev=vdagent,name=org.qemu.guest_agent.0"); - break; + if (arg_console_mode == CONSOLE_NATIVE) { + r = qemu_config_section(config_file, "mon", "mon0", + "chardev", "console"); + if (r < 0) + return r; + } - case CONSOLE_NATIVE: - r = strv_extend_many( - &cmdline, - "-nographic", - "-nodefaults", - "-chardev", "stdio,mux=on,id=console,signal=off", - "-device", "virtio-serial-pci,id=vmspawn-virtio-serial-pci", - "-device", "virtconsole,chardev=console", - "-mon", "console"); break; - - default: - assert_not_reached(); } - if (r < 0) - return log_oom(); - - r = strv_extend(&cmdline, "-drive"); - if (r < 0) - return log_oom(); - _cleanup_free_ char *escaped_ovmf_config_path = escape_qemu_value(ovmf_config->path); - if (!escaped_ovmf_config_path) - return log_oom(); - - r = strv_extendf(&cmdline, "if=pflash,format=%s,readonly=on,file=%s", ovmf_config_format(ovmf_config), escaped_ovmf_config_path); - if (r < 0) - return log_oom(); - - _cleanup_(unlink_and_freep) char *ovmf_vars_to = NULL; - if (ovmf_config->supports_sb) { - const char *ovmf_vars_from = ovmf_config->vars; - _cleanup_free_ char *escaped_ovmf_vars_to = NULL; - _cleanup_close_ int source_fd = -EBADF, target_fd = -EBADF; + case CONSOLE_GUI: + /* -vga is a convenience option, keep on cmdline */ + r = strv_extend_many(&cmdline, "-vga", "virtio"); + if (r < 0) + return log_oom(); - r = tempfn_random_child(NULL, "vmspawn-", &ovmf_vars_to); + r = qemu_config_section(config_file, "device", "virtio-serial0", + "driver", "virtio-serial"); if (r < 0) return r; - source_fd = open(ovmf_vars_from, O_RDONLY|O_CLOEXEC); - if (source_fd < 0) - return log_error_errno(source_fd, "Failed to open OVMF vars file %s: %m", ovmf_vars_from); - - target_fd = open(ovmf_vars_to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0600); - if (target_fd < 0) - return log_error_errno(errno, "Failed to create regular file for OVMF vars at %s: %m", ovmf_vars_to); + r = qemu_config_section(config_file, "chardev", "vdagent", + "backend", "spicevmc", + "debug", "0", + "name", "vdagent"); + if (r < 0) + return r; - r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK); + r = qemu_config_section(config_file, "device", "vdagent-port0", + "driver", "virtserialport", + "chardev", "vdagent", + "name", "org.qemu.guest_agent.0"); if (r < 0) - return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", ovmf_vars_from, ovmf_vars_to); + return r; - /* This isn't always available so don't raise an error if it fails */ - (void) copy_times(source_fd, target_fd, 0); + break; - r = strv_extend_many( - &cmdline, - "-global", "ICH9-LPC.disable_s3=1", - "-global", "driver=cfi.pflash01,property=secure,value=on", - "-drive"); + case CONSOLE_HEADLESS: + r = strv_extend_many(&cmdline, "-nographic", "-nodefaults"); if (r < 0) return log_oom(); - escaped_ovmf_vars_to = escape_qemu_value(ovmf_vars_to); - if (!escaped_ovmf_vars_to) - return log_oom(); + break; - r = strv_extendf(&cmdline, "file=%s,if=pflash,format=%s", escaped_ovmf_vars_to, ovmf_config_format(ovmf_config)); - if (r < 0) - return log_oom(); + default: + assert_not_reached(); + } + + if (!IN_SET(arg_console_mode, CONSOLE_GUI, CONSOLE_HEADLESS)) { + if (arg_console_transport == CONSOLE_TRANSPORT_SERIAL) { + /* Use -serial to connect the chardev to the platform's default serial + * device (e.g. isa-serial on x86, PL011 on ARM). On some platforms the + * serial device is a sysbus device that can only be connected via + * serial_hd() which is populated by -serial, not via the config file. */ + r = strv_extend_many(&cmdline, "-serial", "chardev:console"); + if (r < 0) + return log_oom(); + } else { + r = qemu_config_section(config_file, "device", "vmspawn-virtio-serial-pci", + "driver", "virtio-serial-pci"); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "device", "virtconsole0", + "driver", "virtconsole", + "chardev", "console"); + if (r < 0) + return r; + } } - if (kernel) { - r = strv_extend_many(&cmdline, "-kernel", kernel); + _cleanup_(unlink_and_freep) char *ovmf_vars = NULL; + r = cmdline_add_ovmf(config_file, ovmf_config, &ovmf_vars); + if (r < 0) + return r; + + if (arg_linux) { + r = strv_extend_many(&cmdline, "-kernel", arg_linux); if (r < 0) return log_oom(); /* We can't rely on gpt-auto-generator when direct kernel booting so synthesize a root= * kernel argument instead. */ - if (arg_image) { + if (arg_linux_image_type != KERNEL_IMAGE_TYPE_UKI && arg_image) { r = kernel_cmdline_maybe_append_root(); if (r < 0) return r; @@ -2431,35 +3007,14 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { arg_image); } - if (strv_extend(&cmdline, "-drive") < 0) - return log_oom(); - - _cleanup_free_ char *escaped_image = escape_qemu_value(arg_image); - if (!escaped_image) - return log_oom(); - - if (strv_extendf(&cmdline, "if=none,id=vmspawn,file=%s,format=%s,discard=%s,snapshot=%s", - escaped_image, image_format_to_string(arg_image_format), on_off(arg_discard_disk), on_off(arg_ephemeral)) < 0) - return log_oom(); - - _cleanup_free_ char *image_fn = NULL; - r = path_extract_filename(arg_image, &image_fn); - if (r < 0) - return log_error_errno(r, "Failed to extract filename from path '%s': %m", image_fn); - - _cleanup_free_ char *escaped_image_fn = escape_qemu_value(image_fn); - if (!escaped_image_fn) - return log_oom(); - - if (strv_extend(&cmdline, "-device") < 0) - return log_oom(); - - if (strv_extend_joined(&cmdline, "virtio-blk-pci,drive=vmspawn,bootindex=1,serial=", escaped_image_fn) < 0) + if (arg_image_disk_type != DISK_TYPE_VIRTIO_SCSI_CDROM) { + r = grow_image(arg_image, arg_grow_image); + if (r < 0) + return r; + /* CD-ROMs are read-only, so override any "rw" on the kernel command line. */ + } else if (strv_contains(arg_kernel_cmdline_extra, "rw") && + strv_extend(&arg_kernel_cmdline_extra, "ro") < 0) return log_oom(); - - r = grow_image(arg_image, arg_grow_image); - if (r < 0) - return r; } _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -2521,79 +3076,57 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { pidref_done(&child); children[n_children++] = TAKE_PTR(source); - _cleanup_free_ char *escaped_listen_address = escape_qemu_value(listen_address); - if (!escaped_listen_address) - return log_oom(); - - if (strv_extend(&cmdline, "-chardev") < 0) + _cleanup_free_ char *id = strdup("rootdir"), *tag = strdup("root"); + if (!id || !tag) return log_oom(); - if (strv_extendf(&cmdline, "socket,id=rootdir,path=%s", escaped_listen_address) < 0) + if (!GREEDY_REALLOC(config.virtiofs.entries, config.virtiofs.n_entries + 1)) return log_oom(); - if (strv_extend_many( - &cmdline, - "-device", - "vhost-user-fs-pci,queue-size=1024,chardev=rootdir,tag=root") < 0) - return log_oom(); + config.virtiofs.entries[config.virtiofs.n_entries++] = (VirtiofsInfo) { + .id = TAKE_PTR(id), + .socket_path = TAKE_PTR(listen_address), + .tag = TAKE_PTR(tag), + }; if (strv_extend(&arg_kernel_cmdline_extra, "root=root rootfstype=virtiofs rw") < 0) return log_oom(); } - size_t i = 0; - FOREACH_ARRAY(drive, arg_extra_drives.drives, arg_extra_drives.n_drives) { - if (strv_extend(&cmdline, "-blockdev") < 0) - return log_oom(); - - _cleanup_free_ char *escaped_drive = escape_qemu_value(drive->path); - if (!escaped_drive) - return log_oom(); - - struct stat st; - if (stat(drive->path, &st) < 0) - return log_error_errno(errno, "Failed to stat '%s': %m", drive->path); - - const char *driver = NULL; - if (S_ISREG(st.st_mode)) - driver = "file"; - else if (S_ISBLK(st.st_mode)) { - if (drive->format == IMAGE_FORMAT_QCOW2) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Block device '%s' cannot be used with 'qcow2' format, only 'raw' is supported.", - drive->path); - driver = "host_device"; - } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected regular file or block device, not '%s'.", drive->path); - - if (strv_extendf(&cmdline, "driver=%s,cache.direct=off,cache.no-flush=on,file.driver=%s,file.filename=%s,node-name=vmspawn_extra_%zu", image_format_to_string(drive->format), driver, escaped_drive, i) < 0) - return log_oom(); + /* Extra drive validation is done in the post-fork drive info construction loop + * to avoid stat()'ing each drive twice. */ - _cleanup_free_ char *drive_fn = NULL; - r = path_extract_filename(drive->path, &drive_fn); + if (!IN_SET(arg_console_mode, CONSOLE_GUI, CONSOLE_HEADLESS)) { + r = strv_prepend(&arg_kernel_cmdline_extra, + arg_console_transport == CONSOLE_TRANSPORT_SERIAL ? + "console=" QEMU_SERIAL_CONSOLE_NAME : "console=hvc0"); if (r < 0) - return log_error_errno(r, "Failed to extract filename from path '%s': %m", drive->path); - - _cleanup_free_ char *escaped_drive_fn = escape_qemu_value(drive_fn); - if (!escaped_drive_fn) return log_oom(); - if (strv_extend(&cmdline, "-device") < 0) - return log_oom(); + /* Propagate the host's $TERM into the VM via the kernel command line. TERM= is + * picked up by PID 1 and inherited by services on /dev/console, and + * systemd.tty.term.hvc0= is used by services directly attached to /dev/hvc0 (such + * as serial-getty). While systemd can auto-detect the terminal type via DCS + * XTGETTCAP, not all terminal emulators implement this, so let's always propagate + * $TERM if we have it. */ + const char *term = getenv("TERM"); + if (term_env_valid(term)) { + FOREACH_STRING(tty_key, "systemd.tty.term.hvc0", "TERM") { + _cleanup_free_ char *p = strjoin(tty_key, "=", term); + if (!p) + return log_oom(); - if (strv_extendf(&cmdline, "virtio-blk-pci,drive=vmspawn_extra_%zu,serial=%s", i++, escaped_drive_fn) < 0) - return log_oom(); + if (strv_consume_prepend(&arg_kernel_cmdline_extra, TAKE_PTR(p)) < 0) + return log_oom(); + } + } } - if (arg_console_mode != CONSOLE_GUI) { - r = strv_prepend(&arg_kernel_cmdline_extra, "console=hvc0"); - if (r < 0) - return log_oom(); - } + _cleanup_free_ char *fstab_extra = NULL; for (size_t j = 0; j < arg_runtime_mounts.n_mounts; j++) { RuntimeMount *m = arg_runtime_mounts.mounts + j; - _cleanup_free_ char *listen_address = NULL; + _cleanup_free_ char *listen_address = NULL, *id = NULL, *tag = NULL; _cleanup_(fork_notify_terminate) PidRef child = PIDREF_NULL; if (!GREEDY_REALLOC(children, n_children + 1)) @@ -2619,45 +3152,61 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { pidref_done(&child); children[n_children++] = TAKE_PTR(source); - _cleanup_free_ char *escaped_listen_address = escape_qemu_value(listen_address); - if (!escaped_listen_address) + if (asprintf(&id, "mnt%zu", j) < 0) return log_oom(); - if (strv_extend(&cmdline, "-chardev") < 0) + tag = strdup(id); + if (!tag) return log_oom(); - _cleanup_free_ char *id = NULL; - if (asprintf(&id, "mnt%zu", j) < 0) + /* fstab uses whitespace as field separator, so octal-escape spaces in paths */ + _cleanup_free_ char *escaped_target = octescape_full(m->target, SIZE_MAX, " \t"); + if (!escaped_target) return log_oom(); - if (strv_extendf(&cmdline, "socket,id=%s,path=%s", id, escaped_listen_address) < 0) + if (strextendf(&fstab_extra, "%s %s virtiofs %s,x-initrd.mount\n", + id, escaped_target, m->read_only ? "ro" : "rw") < 0) return log_oom(); - if (strv_extend(&cmdline, "-device") < 0) + if (!GREEDY_REALLOC(config.virtiofs.entries, config.virtiofs.n_entries + 1)) return log_oom(); - if (strv_extendf(&cmdline, "vhost-user-fs-pci,queue-size=1024,chardev=%1$s,tag=%1$s", id) < 0) - return log_oom(); + config.virtiofs.entries[config.virtiofs.n_entries++] = (VirtiofsInfo) { + .id = TAKE_PTR(id), + .socket_path = TAKE_PTR(listen_address), + .tag = TAKE_PTR(tag), + }; + } - _cleanup_free_ char *clean_target = xescape(m->target, "\":"); - if (!clean_target) - return log_oom(); + if (fstab_extra) { + /* If the user already specified a fstab.extra credential, combine it with ours */ + MachineCredential *existing = machine_credential_find(&arg_credentials, "fstab.extra"); + if (existing) { + _cleanup_free_ char *combined = NULL; - if (strv_extendf(&arg_kernel_cmdline_extra, "systemd.mount-extra=\"%s:%s:virtiofs:%s\"", - id, clean_target, m->read_only ? "ro" : "rw") < 0) - return log_oom(); + if (existing->size > 0 && existing->data[existing->size - 1] != '\n') + r = asprintf(&combined, "%.*s\n%s", (int) existing->size, existing->data, fstab_extra); + else + r = asprintf(&combined, "%.*s%s", (int) existing->size, existing->data, fstab_extra); + if (r < 0) + return log_oom(); + + erase_and_free(existing->data); + existing->data = TAKE_PTR(combined); + existing->size = r; + } else { + r = machine_credential_add(&arg_credentials, "fstab.extra", fstab_extra, SIZE_MAX); + if (r < 0) + return r; + } } _cleanup_(rm_rf_physical_and_freep) char *smbios_dir = NULL; - r = mkdtemp_malloc("/var/tmp/vmspawn-smbios-XXXXXX", &smbios_dir); - if (r < 0) - return log_error_errno(r, "Failed to create temporary directory: %m"); - - r = cmdline_add_kernel_cmdline(&cmdline, kernel, smbios_dir); - if (r < 0) - return r; + _cleanup_close_ int smbios_dir_fd = mkdtemp_open("/var/tmp/vmspawn-smbios-XXXXXX", /* flags= */ 0, &smbios_dir); + if (smbios_dir_fd < 0) + return log_error_errno(smbios_dir_fd, "Failed to create temporary directory: %m"); - r = cmdline_add_smbios11(&cmdline, smbios_dir); + r = cmdline_add_smbios11(&cmdline, smbios_dir_fd, smbios_dir); if (r < 0) return r; @@ -2667,33 +3216,18 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM not supported on %s, refusing", architecture_to_string(native_architecture())); if (arg_tpm < 0) { arg_tpm = false; - log_debug("TPM not support on %s, disabling tpm autodetection and continuing", architecture_to_string(native_architecture())); + log_debug("TPM not supported on %s, disabling tpm autodetection and continuing", architecture_to_string(native_architecture())); } } _cleanup_free_ char *swtpm = NULL; if (arg_tpm != 0) { - if (arg_tpm_state_mode == TPM_STATE_AUTO && !arg_ephemeral) { + if (arg_tpm_state_mode == STATE_AUTO && !arg_ephemeral) { assert(!arg_tpm_state_path); - const char *p = ASSERT_PTR(arg_image ?: arg_directory); - - _cleanup_free_ char *parent = NULL; - r = path_extract_directory(p, &parent); - if (r < 0) - return log_error_errno(r, "Failed to extract parent directory from '%s': %m", p); - - _cleanup_free_ char *filename = NULL; - r = path_extract_filename(p, &filename); + r = make_sidecar_path(".tpmstate", &arg_tpm_state_path); if (r < 0) - return log_error_errno(r, "Failed to extract filename from '%s': %m", p); - - if (!strextend(&filename, ".tpmstate")) - return log_oom(); - - arg_tpm_state_path = path_join(parent, filename); - if (!arg_tpm_state_path) - return log_oom(); + return r; log_debug("Storing TPM state persistently under '%s'.", arg_tpm_state_path); } @@ -2735,25 +3269,33 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } if (tpm_socket_address) { - _cleanup_free_ char *escaped_tpm_socket_address = escape_qemu_value(tpm_socket_address); - if (!escaped_tpm_socket_address) - return log_oom(); - - if (strv_extend(&cmdline, "-chardev") < 0) - return log_oom(); - - if (strv_extend_joined(&cmdline, "socket,id=chrtpm,path=", tpm_socket_address) < 0) - return log_oom(); + r = qemu_config_section(config_file, "chardev", "chrtpm", + "backend", "socket", + "path", tpm_socket_address); + if (r < 0) + return r; - if (strv_extend_many(&cmdline, "-tpmdev", "emulator,id=tpm0,chardev=chrtpm") < 0) - return log_oom(); + r = qemu_config_section(config_file, "tpmdev", "tpm0", + "type", "emulator", + "chardev", "chrtpm"); + if (r < 0) + return r; + const char *tpm_driver; if (native_architecture() == ARCHITECTURE_X86_64) - r = strv_extend_many(&cmdline, "-device", "tpm-tis,tpmdev=tpm0"); + tpm_driver = "tpm-tis"; else if (IN_SET(native_architecture(), ARCHITECTURE_ARM64, ARCHITECTURE_ARM64_BE)) - r = strv_extend_many(&cmdline, "-device", "tpm-tis-device,tpmdev=tpm0"); - if (r < 0) - return log_oom(); + tpm_driver = "tpm-tis-device"; + else + tpm_driver = NULL; + + if (tpm_driver) { + r = qemu_config_section(config_file, "device", "tpmdev0", + "driver", tpm_driver, + "tpmdev", "tpm0"); + if (r < 0) + return r; + } } char *initrd = NULL; @@ -2778,12 +3320,21 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (arg_forward_journal) { _cleanup_free_ char *listen_address = NULL; + if (asprintf(&listen_address, "vsock:2:%u", child_cid) < 0) + return log_oom(); if (!GREEDY_REALLOC(children, n_children + 1)) return log_oom(); _cleanup_(fork_notify_terminate) PidRef child = PIDREF_NULL; - r = start_systemd_journal_remote(unit, child_cid, sd_socket_activate, &listen_address, &child); + r = fork_journal_remote( + listen_address, + arg_forward_journal, + arg_forward_journal_max_use, + arg_forward_journal_keep_free, + arg_forward_journal_max_file_size, + arg_forward_journal_max_files, + &child); if (r < 0) return r; @@ -2853,46 +3404,90 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_error_errno(r, "Failed to set credential systemd.unit-dropin.sshd-vsock@.service: %m"); } - if (ARCHITECTURE_SUPPORTS_SMBIOS) - FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) { - _cleanup_free_ char *p = NULL, *cred_data_b64 = NULL; - ssize_t n; + if (use_vsock) { + notify_sock_fd = open_vsock(); + if (notify_sock_fd < 0) + return log_error_errno(notify_sock_fd, "Failed to open VSOCK: %m"); + + r = add_vsock_credential(notify_sock_fd); + if (r < 0) + return log_error_errno(r, "Failed to add VSOCK credential: %m"); + } + + r = cmdline_add_credentials(&cmdline, smbios_dir_fd, smbios_dir); + if (r < 0) + return r; - n = base64mem(cred->data, cred->size, &cred_data_b64); - if (n < 0) - return log_oom(); + r = cmdline_add_kernel_cmdline(&cmdline, smbios_dir_fd, smbios_dir); + if (r < 0) + return r; - p = path_join(smbios_dir, cred->id); - if (!p) - return log_oom(); + _cleanup_close_pair_ int bridge_fds[2] = EBADF_PAIR; + r = qemu_config_add_qmp_monitor(config_file, bridge_fds, &pass_fds, &n_pass_fds); + if (r < 0) + return r; + + /* Pre-allocate PCIe root ports for QMP device_add hotplug. On PCIe machine types + * (q35, virt), QMP device_add is always hotplug — the root bus (pcie.0) does not support + * it. Each root port provides one slot for hotplug. We create enough ports for all devices + * that will be set up via QMP, plus VMSPAWN_PCIE_HOTPLUG_SPARES spare ports for future + * runtime hotplug. */ + if (ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS) { + /* Count maximum possible PCI devices: root image + extra drives + SCSI controller + + * network + virtiofs mounts + vsock. The actual count may be lower (e.g. no network, + * no SCSI), but unused ports have negligible overhead. */ + size_t n_pcie_ports = 1 + + arg_extra_drives.n_drives + /* drives */ + 1 + /* SCSI controller */ + 1 + /* network */ + (arg_directory ? 1 : 0) + /* rootdir virtiofs */ + arg_runtime_mounts.n_mounts + /* extra virtiofs mounts */ + 1 + /* vsock */ + VMSPAWN_PCIE_HOTPLUG_SPARES; /* reserved for future hotplug */ + + /* Guard the unsigned subtraction below against future refactors that might drop the + * fixed additions. */ + assert(n_pcie_ports >= VMSPAWN_PCIE_HOTPLUG_SPARES); + + /* QEMU's pcie-root-port chassis/slot are uint8_t — i+1 must fit. */ + if (n_pcie_ports > UINT8_MAX) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), + "Too many PCIe root ports requested (%zu, max 255). " + "Reduce the number of extra drives or runtime mounts.", + n_pcie_ports); + + size_t n_builtin_ports = n_pcie_ports - VMSPAWN_PCIE_HOTPLUG_SPARES; + for (size_t i = 0; i < n_pcie_ports; i++) { + char id[STRLEN("vmspawn-hotplug-pci-root-port-") + DECIMAL_STR_MAX(size_t)]; + if (i < n_builtin_ports) + xsprintf(id, "vmspawn-pcieport-%zu", i); + else + xsprintf(id, "vmspawn-hotplug-pci-root-port-%zu", i - n_builtin_ports); - r = write_string_filef( - p, - WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE|WRITE_STRING_FILE_MODE_0600, - "io.systemd.credential.binary:%s=%s", cred->id, cred_data_b64); + r = qemu_config_section(config_file, "device", id, + "driver", "pcie-root-port"); if (r < 0) - return log_error_errno(r, "Failed to write smbios credential file %s: %m", p); + return r; - r = strv_extend(&cmdline, "-smbios"); + r = qemu_config_keyf(config_file, "chassis", "%zu", i + 1); if (r < 0) - return log_oom(); + return r; - r = strv_extend_joined(&cmdline, "type=11,path=", p); + r = qemu_config_keyf(config_file, "slot", "%zu", i + 1); if (r < 0) - return log_oom(); + return r; } + } - if (use_vsock) { - notify_sock_fd = open_vsock(); - if (notify_sock_fd < 0) - return log_error_errno(notify_sock_fd, "Failed to open VSOCK: %m"); + /* Finalize the config file and add -readconfig to the cmdline */ + r = fflush_and_check(config_file); + if (r < 0) + return log_error_errno(r, "Failed to write QEMU config file: %m"); + config_file = safe_fclose(config_file); - r = cmdline_add_vsock(&cmdline, notify_sock_fd); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) - return log_error_errno(r, "Failed to call getsockname on VSOCK: %m"); - } + r = strv_extend_many(&cmdline, "-readconfig", config_path); + if (r < 0) + return log_oom(); const char *e = secure_getenv("SYSTEMD_VMSPAWN_QEMU_EXTRA"); if (e) { @@ -2904,6 +3499,14 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } if (DEBUG_LOGGING) { + _cleanup_free_ char *config_contents = NULL; + + r = read_full_file(config_path, &config_contents, /* ret_size= */ NULL); + if (r < 0) + log_debug_errno(r, "Failed to read back QEMU config file, ignoring: %m"); + else + log_debug("QEMU config file %s:\n%s", config_path, config_contents); + _cleanup_free_ char *joined = quote_command_line(cmdline, SHELL_ESCAPE_EMPTY); if (!joined) return log_oom(); @@ -2911,12 +3514,21 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { log_debug("Executing: %s", joined); } - _cleanup_(pidref_done) PidRef child_pidref = PIDREF_NULL; + _cleanup_close_ int child_pty = -EBADF; + if (master >= 0) { + child_pty = pty_open_peer(master, O_RDWR|O_CLOEXEC|O_NOCTTY); + if (child_pty < 0) + return log_error_errno(child_pty, "Failed to open PTY slave: %m"); + } + + /* SIGTERM, not SIGKILL — let QEMU flush state on error-path early exits. */ + _cleanup_(pidref_done_sigterm_wait) PidRef child_pidref = PIDREF_NULL; r = pidref_safe_fork_full( qemu_binary, - /* stdio_fds= */ NULL, + child_pty >= 0 ? (const int[]) { child_pty, child_pty, child_pty } : NULL, pass_fds, n_pass_fds, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_CLOEXEC_OFF|FORK_RLIMIT_NOFILE_SAFE, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_CLOEXEC_OFF|FORK_RLIMIT_NOFILE_SAFE| + (child_pty >= 0 ? FORK_REARRANGE_STDIO : 0), &child_pidref); if (r < 0) return r; @@ -2932,9 +3544,59 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { _exit(EXIT_FAILURE); } - /* Close relevant fds we passed to qemu in the parent. We don't need them anymore. */ - child_vsock_fd = safe_close(child_vsock_fd); - tap_fd = safe_close(tap_fd); + /* Close QEMU's end of the QMP socketpair in the parent. We don't need it anymore. */ + child_pty = safe_close(child_pty); + bridge_fds[1] = safe_close(bridge_fds[1]); + + r = prepare_device_info(runtime_dir, &config); + if (r < 0) + return r; + + /* Connect to VMM backend */ + _cleanup_(vmspawn_qmp_bridge_freep) VmspawnQmpBridge *bridge = NULL; + r = vmspawn_qmp_init(&bridge, bridge_fds[0], event); + if (r < 0) + return r; + + TAKE_FD(bridge_fds[0]); + + /* Probe QEMU feature availability synchronously before device setup consumes the flags. */ + r = vmspawn_qmp_probe_features(bridge); + if (r < 0) + return r; + + /* Device setup — all before resuming vCPUs */ + r = vmspawn_qmp_setup_drives(bridge, &config.drives); + if (r < 0) + return r; + + if (config.network.type) { + r = vmspawn_qmp_setup_network(bridge, &config.network); + if (r < 0) + return r; + } + + r = vmspawn_qmp_setup_virtiofs(bridge, &config.virtiofs); + if (r < 0) + return r; + + r = vmspawn_qmp_setup_vsock(bridge, &config.vsock); + if (r < 0) + return r; + + /* Resume vCPUs and switch to async event processing */ + r = vmspawn_qmp_start(bridge); + if (r < 0) + return r; + + /* Varlink server for VM control */ + _cleanup_(vmspawn_varlink_context_freep) VmspawnVarlinkContext *varlink_ctx = NULL; + _cleanup_free_ char *control_address = NULL; + r = vmspawn_varlink_setup(&varlink_ctx, bridge, runtime_dir, &control_address); + if (r < 0) + return r; + + TAKE_PTR(bridge); if (!arg_keep_unit) { /* When a new scope is created for this container, then we'll be registered as its controller, in which @@ -2956,7 +3618,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } bool scope_allocated = false; - if (!arg_keep_unit && (!arg_register || arg_runtime_scope != RUNTIME_SCOPE_SYSTEM)) { + if (!arg_keep_unit && (arg_register == 0 || arg_runtime_scope != RUNTIME_SCOPE_SYSTEM)) { r = allocate_scope( runtime_bus, arg_machine, @@ -2980,52 +3642,35 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_error_errno(r, "Failed to get our own unit: %m"); } - bool registered_system = false, registered_runtime = false; - if (arg_register) { + MachineRegistrationContext machine_ctx = { + .scope = arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? RUNTIME_SCOPE_SYSTEM : _RUNTIME_SCOPE_INVALID, + .system_bus = system_bus, + .user_bus = runtime_bus, + }; + if (arg_register != 0) { char vm_address[STRLEN("vsock/") + DECIMAL_STR_MAX(unsigned)]; xsprintf(vm_address, "vsock/%u", child_cid); - r = register_machine( - system_bus, - arg_machine, - arg_uuid, - "systemd-vmspawn", - &child_pidref, - arg_directory, - child_cid, - child_cid != VMADDR_CID_ANY ? vm_address : NULL, - ssh_private_key_path, - !arg_keep_unit && arg_runtime_scope == RUNTIME_SCOPE_SYSTEM, - RUNTIME_SCOPE_SYSTEM); - if (r < 0) { - /* if privileged the request to register definitely failed */ - if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) - return r; - log_notice_errno(r, "Failed to register machine in system context, will try in user context."); - } else - registered_system = true; - - if (arg_runtime_scope == RUNTIME_SCOPE_USER) { - r = register_machine( - runtime_bus, - arg_machine, - arg_uuid, - "systemd-vmspawn", - &child_pidref, - arg_directory, - child_cid, - child_cid != VMADDR_CID_ANY ? vm_address : NULL, - ssh_private_key_path, - !arg_keep_unit, - RUNTIME_SCOPE_USER); - if (r < 0) { - if (!registered_system) /* neither registration worked: fail */ - return r; + const MachineRegistration reg = { + .name = arg_machine, + .id = arg_uuid, + .service = "systemd-vmspawn", + .class = "vm", + .pidref = &child_pidref, + .root_directory = arg_directory, + .vsock_cid = child_cid, + .ssh_address = child_cid != VMADDR_CID_ANY ? vm_address : NULL, + .ssh_private_key_path = ssh_private_key_path, + .control_address = control_address, + .allocate_unit = !arg_keep_unit, + }; - log_notice_errno(r, "Failed to register machine in user context, but succeeded in system context, will proceed."); - } else - registered_runtime = true; - } + r = register_machine_with_fallback_and_log( + &machine_ctx, + ®, + /* graceful= */ arg_register < 0); + if (r < 0) + return r; } /* Report that the VM is now set up */ @@ -3093,29 +3738,31 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { _cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL; _cleanup_(pty_forward_freep) PTYForward *forward = NULL; if (master >= 0) { - if (!terminal_is_dumb()) { - r = osc_context_open_vm(arg_machine, /* ret_seq= */ NULL, &osc_context_id); - if (r < 0) - return r; - } - r = pty_forward_new(event, master, ptyfwd_flags, &forward); if (r < 0) return log_error_errno(r, "Failed to create PTY forwarder: %m"); - if (!arg_background) { - _cleanup_free_ char *bg = NULL; + if (!FLAGS_SET(ptyfwd_flags, PTY_FORWARD_DUMB_TERMINAL)) { + if (!terminal_is_dumb()) { + r = osc_context_open_vm(arg_machine, /* ret_seq= */ NULL, &osc_context_id); + if (r < 0) + return r; + } - r = terminal_tint_color(130 /* green */, &bg); - if (r < 0) - log_debug_errno(r, "Failed to determine terminal background color, not tinting."); - else - (void) pty_forward_set_background_color(forward, bg); - } else if (!isempty(arg_background)) - (void) pty_forward_set_background_color(forward, arg_background); + if (!arg_background) { + _cleanup_free_ char *bg = NULL; - (void) pty_forward_set_window_title(forward, GLYPH_GREEN_CIRCLE, /* hostname= */ NULL, - STRV_MAKE("Virtual Machine", arg_machine)); + r = terminal_tint_color(130 /* green */, &bg); + if (r < 0) + log_debug_errno(r, "Failed to determine terminal background color, not tinting."); + else + (void) pty_forward_set_background_color(forward, bg); + } else if (!isempty(arg_background)) + (void) pty_forward_set_background_color(forward, arg_background); + + (void) pty_forward_set_window_title(forward, GLYPH_GREEN_CIRCLE, /* hostname= */ NULL, + STRV_MAKE("Virtual Machine", arg_machine)); + } } r = sd_event_loop(event); @@ -3126,10 +3773,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (scope_allocated) terminate_scope(runtime_bus, arg_machine); - if (registered_system) - (void) unregister_machine(system_bus, arg_machine); - if (registered_runtime) - (void) unregister_machine(runtime_bus, arg_machine); + unregister_machine_with_fallback_and_log(&machine_ctx, arg_machine); if (use_vsock) { if (exit_status == INT_MAX) { @@ -3212,10 +3856,69 @@ static int determine_names(void) { return 0; } +static int determine_kernel(void) { + int r; + + if (!arg_linux && arg_directory) { + /* A kernel is required for directory type images so attempt to find one under /boot and /efi */ + r = discover_boot_entry(arg_directory, &arg_linux, &arg_initrds); + if (r < 0) + return log_error_errno(r, "Failed to locate UKI in directory type image, please specify one with --linux=."); + + log_debug("Discovered UKI image at %s", arg_linux); + } + + if (!arg_linux) { + if (arg_firmware_type == _FIRMWARE_INVALID) + arg_firmware_type = FIRMWARE_UEFI; + return 0; + } + + r = inspect_kernel(AT_FDCWD, arg_linux, &arg_linux_image_type); + if (r < 0) + return log_error_errno(r, "Failed to determine '%s' kernel image type: %m", arg_linux); + + if (arg_linux_image_type == KERNEL_IMAGE_TYPE_UNKNOWN) { + if (arg_firmware_type == FIRMWARE_UEFI) + return log_error_errno( + SYNTHETIC_ERRNO(EINVAL), + "Kernel image '%s' is not a PE binary, --firmware=uefi (or a firmware path) is not supported.", + arg_linux); + if (arg_firmware_type == _FIRMWARE_INVALID) + arg_firmware_type = FIRMWARE_NONE; + } + + if (arg_firmware_type == _FIRMWARE_INVALID) + arg_firmware_type = FIRMWARE_UEFI; + + return 0; +} + static int verify_arguments(void) { if (!strv_isempty(arg_initrds) && !arg_linux) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --initrd= cannot be used without --linux=."); + if (arg_firmware_type != FIRMWARE_UEFI && arg_linux_image_type == KERNEL_IMAGE_TYPE_UKI) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Booting a UKI requires --firmware=uefi."); + + if (arg_firmware_type == FIRMWARE_NONE && !arg_linux) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--firmware=none requires --linux= to be specified."); + + if (arg_image_disk_type == DISK_TYPE_VIRTIO_SCSI_CDROM) { + if (arg_ephemeral) + log_warning("--ephemeral has no effect with --image-disk-type=scsi-cd (CD-ROMs are read-only)."); + if (arg_discard_disk) + log_warning("--discard-disk has no effect with --image-disk-type=scsi-cd (CD-ROMs are read-only)."); + if (arg_grow_image) + log_warning("--grow-image has no effect with --image-disk-type=scsi-cd (CD-ROMs are read-only)."); + } + + if (arg_grow_image && arg_image_format == IMAGE_FORMAT_QCOW2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--grow-image is not supported for qcow2 images, use 'qemu-img resize FILE SIZE'."); + return 0; } @@ -3235,10 +3938,29 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + if (arg_firmware_describe) { + _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; + + r = find_ovmf_config(arg_firmware_features_include, arg_firmware_features_exclude, &ovmf_config, &json); + if (r < 0) + return log_error_errno(r, "Failed to find OVMF config: %m"); + + r = sd_json_variant_dump(json, SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_COLOR_AUTO, stdout, /* prefix= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to output JSON: %m"); + + return 0; + } + r = determine_names(); if (r < 0) return r; + r = determine_kernel(); + if (r < 0) + return r; + r = verify_arguments(); if (r < 0) return r; diff --git a/src/vpick/vpick-tool.c b/src/vpick/vpick-tool.c index c20994d115dd5..595ecae68869d 100644 --- a/src/vpick/vpick-tool.c +++ b/src/vpick/vpick-tool.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include "alloc-util.h" #include "architecture.h" @@ -9,12 +8,14 @@ #include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-util.h" #include "path-util.h" #include "pretty-print.h" #include "stat-util.h" #include "string-table.h" #include "string-util.h" +#include "strv.h" #include "vpick.h" typedef enum { @@ -55,167 +56,165 @@ DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(print, Print); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *lookup_keys = NULL, *output = NULL; int r; r = terminal_urlify_man("systemd-vpick", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] PATH...\n" - "\n%5$sPick entry from versioned directory.%6$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sLookup Keys:%4$s\n" - " -B --basename=BASENAME\n" - " Look for specified basename\n" - " -V VERSION Look for specified version\n" - " -A ARCH Look for specified architecture\n" - " -S --suffix=SUFFIX Look for specified suffix\n" - " -t --type=TYPE Look for specified inode type\n" - "\n%3$sOutput:%4$s\n" - " -p --print=filename Print selected filename rather than path\n" - " -p --print=version Print selected version rather than path\n" - " -p --print=type Print selected inode type rather than path\n" - " -p --print=arch Print selected architecture rather than path\n" - " -p --print=tries Print selected tries left/tries done rather than path\n" - " -p --print=all Print all of the above\n" - " --resolve=yes Canonicalize the result path\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), ansi_normal(), - ansi_highlight(), ansi_normal()); + r = option_parser_get_help_table(&lookup_keys); + if (r < 0) + return r; - return 0; -} + r = option_parser_get_help_table_group("Output", &output); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, lookup_keys, output); -static int parse_argv(int argc, char *argv[]) { + printf("%s [OPTIONS...] PATH...\n" + "\n%sPick entry from versioned directory.%s\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal()); + + printf("\n%sLookup Keys:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(lookup_keys); + if (r < 0) + return r; - enum { - ARG_VERSION = 0x100, - ARG_RESOLVE, - }; + printf("\n%sOutput:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(output); + if (r < 0) + return r; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "basename", required_argument, NULL, 'B' }, - { "suffix", required_argument, NULL, 'S' }, - { "type", required_argument, NULL, 't' }, - { "print", required_argument, NULL, 'p' }, - { "resolve", required_argument, NULL, ARG_RESOLVE }, - {} - }; + printf("\nSee the %s for details.\n", link); + return 0; +} - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hB:V:A:S:t:p:", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(); - - case ARG_VERSION: - return version(); - - case 'B': - if (!filename_part_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid basename string: %s", optarg); + OPTION('B', "basename", "BASENAME", "Look for specified basename"): + if (!filename_part_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid basename string: %s", arg); - r = free_and_strdup_warn(&arg_filter_basename, optarg); + r = free_and_strdup_warn(&arg_filter_basename, arg); if (r < 0) return r; break; - case 'V': - if (!version_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid version string: %s", optarg); + OPTION_SHORT('V', "VERSION", "Look for specified version"): + if (!version_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid version string: %s", arg); - r = free_and_strdup_warn(&arg_filter_version, optarg); + r = free_and_strdup_warn(&arg_filter_version, arg); if (r < 0) return r; break; - case 'A': - if (streq(optarg, "native")) + OPTION_SHORT('A', "ARCH", "Look for specified architecture"): + if (streq(arg, "native")) arg_filter_architecture = native_architecture(); - else if (streq(optarg, "secondary")) { + else if (streq(arg, "secondary")) { #ifdef ARCHITECTURE_SECONDARY arg_filter_architecture = ARCHITECTURE_SECONDARY; #else return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Local architecture has no secondary architecture."); #endif - } else if (streq(optarg, "uname")) + } else if (streq(arg, "uname")) arg_filter_architecture = uname_architecture(); - else if (streq(optarg, "auto")) + else if (streq(arg, "auto")) arg_filter_architecture = _ARCHITECTURE_INVALID; else { - arg_filter_architecture = architecture_from_string(optarg); + arg_filter_architecture = architecture_from_string(arg); if (arg_filter_architecture < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown architecture: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown architecture: %s", arg); } break; - case 'S': - if (!filename_part_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid suffix string: %s", optarg); + OPTION('S', "suffix", "SUFFIX", "Look for specified suffix"): + if (!filename_part_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid suffix string: %s", arg); - r = free_and_strdup_warn(&arg_filter_suffix, optarg); + r = free_and_strdup_warn(&arg_filter_suffix, arg); if (r < 0) return r; break; - case 't': - if (isempty(optarg)) + OPTION('t', "type", "TYPE", "Look for specified inode type"): + if (isempty(arg)) arg_filter_type_mask = 0; else { mode_t m; - m = inode_type_from_string(optarg); + m = inode_type_from_string(arg); if (m == MODE_INVALID) - return log_error_errno(m, "Unknown inode type: %s", optarg); + return log_error_errno(m, "Unknown inode type: %s", arg); arg_filter_type_mask |= UINT32_C(1) << IFTODT(m); } break; - case 'p': - if (streq(optarg, "arch")) /* accept abbreviation too */ + OPTION_GROUP("Output"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('p', "print", "WHAT", + "Print selected WHAT rather than path"): {} + OPTION_LONG_FLAGS(OPTION_HELP_ENTRY, "print", "filename", + "... print selected filename"): {} + OPTION_LONG_FLAGS(OPTION_HELP_ENTRY, "print", "version", + "... print selected version"): {} + OPTION_LONG_FLAGS(OPTION_HELP_ENTRY, "print", "type", + "... print selected inode type"): {} + OPTION_LONG_FLAGS(OPTION_HELP_ENTRY, "print", "arch", + "... print selected architecture"): {} + OPTION_LONG_FLAGS(OPTION_HELP_ENTRY, "print", "tries", + "... print selected tries left/tries done"): {} + OPTION_LONG_FLAGS(OPTION_HELP_ENTRY, "print", "all", + "... print all of the above"): + + if (streq(arg, "arch")) /* accept abbreviation too */ arg_print = PRINT_ARCHITECTURE; else - arg_print = print_from_string(optarg); + arg_print = print_from_string(arg); if (arg_print < 0) - return log_error_errno(arg_print, "Unknown --print= argument: %s", optarg); + return log_error_errno(arg_print, "Unknown --print= argument: %s", arg); break; - case ARG_RESOLVE: - r = parse_boolean(optarg); + OPTION_LONG("resolve", "BOOL", "Canonicalize the result path"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse --resolve= value: %m"); SET_FLAG(arg_flags, PICK_RESOLVE, r); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } if (arg_print < 0) arg_print = PRINT_PATH; + *ret_args = option_parser_get_args(&state); return 1; } @@ -224,18 +223,19 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - if (optind >= argc) + if (strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path to resolve must be specified."); - for (int i = optind; i < argc; i++) { + STRV_FOREACH(i, args) { _cleanup_free_ char *p = NULL; - r = path_make_absolute_cwd(argv[i], &p); + r = path_make_absolute_cwd(*i, &p); if (r < 0) - return log_error_errno(r, "Failed to make path '%s' absolute: %m", argv[i]); + return log_error_errno(r, "Failed to make path '%s' absolute: %m", *i); _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; r = path_pick(/* toplevel_path= */ NULL, @@ -337,9 +337,9 @@ static int run(int argc, char *argv[]) { return table_log_add_error(r); } - r = table_print(t, stdout); + r = table_print_or_warn(t); if (r < 0) - return table_log_print_error(r); + return r; break; } diff --git a/src/xdg-autostart-generator/xdg-autostart-service.c b/src/xdg-autostart-generator/xdg-autostart-service.c index 62ddce1815e39..c3ba389dcf4d1 100644 --- a/src/xdg-autostart-generator/xdg-autostart-service.c +++ b/src/xdg-autostart-generator/xdg-autostart-service.c @@ -190,6 +190,9 @@ static int strv_strndup_unescape_and_push( const char *start, const char *end) { + assert(sv); + assert(n); + if (end == start) return 0; @@ -291,6 +294,9 @@ static int xdg_config_item_table_lookup( void *userdata) { assert(lvalue); + assert(ret_func); + assert(ret_ltype); + assert(ret_data); /* Ignore any keys with [] as those are translations. */ if (strchr(lvalue, '[')) { diff --git a/sysctl.d/50-coredump.conf.in b/sysctl.d/50-coredump.conf.in index fe8f7670b0637..0e6f370f47381 100644 --- a/sysctl.d/50-coredump.conf.in +++ b/sysctl.d/50-coredump.conf.in @@ -13,7 +13,7 @@ # the core dump. # # See systemd-coredump(8) and core(5). -kernel.core_pattern=|{{LIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h %d %F +kernel.core_pattern=|{{LIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h %d %F %I # Allow 16 coredumps to be dispatched in parallel by the kernel. # We collect metadata from /proc/%P/, and thus need to make sure the crashed diff --git a/sysusers.d/meson.build b/sysusers.d/meson.build index 84fadfe3f7020..3c2e450a183bb 100644 --- a/sysusers.d/meson.build +++ b/sysusers.d/meson.build @@ -15,7 +15,8 @@ in_files = [['basic.conf', true], ['systemd-journal.conf', true], ['systemd-network.conf', conf.get('ENABLE_NETWORKD') == 1], ['systemd-resolve.conf', conf.get('ENABLE_RESOLVE') == 1], - ['systemd-timesync.conf', conf.get('ENABLE_TIMESYNCD') == 1]] + ['systemd-timesync.conf', conf.get('ENABLE_TIMESYNCD') == 1], + ['systemd-imds.conf', conf.get('ENABLE_IMDS') == 1]] foreach tuple : in_files file = tuple[0] diff --git a/sysusers.d/systemd-imds.conf.in b/sysusers.d/systemd-imds.conf.in new file mode 100644 index 0000000000000..adb8d5b1fb1c6 --- /dev/null +++ b/sysusers.d/systemd-imds.conf.in @@ -0,0 +1,8 @@ +# This file is part of systemd. +# +# systemd 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. + +u! systemd-imds {{SYSTEMD_IMDS_UID}} "systemd Instance Metadata" diff --git a/test/fuzz/fuzz-user-record/auth-fido2.json b/test/fuzz/fuzz-user-record/auth-fido2.json new file mode 100644 index 0000000000000..cd8282f5bf293 --- /dev/null +++ b/test/fuzz/fuzz-user-record/auth-fido2.json @@ -0,0 +1,40 @@ +{ + "userName": "fido2user", + "realName": "FIDO2 Security Key User", + "uid": 2002, + "gid": 2002, + "homeDirectory": "/home/fido2user", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "imagePath": "/home/fido2user.home", + "fido2HmacCredential": [ + "AQIDBAUAAQ==", + "BQYHCAkKCw==" + ], + "privileged": { + "fido2HmacSalt": [ + { + "credential": "AQIDBAUAAQ==", + "salt": "Zmlyc3RzYWx0Zm9yZmlkbzJ0ZXN0aW5n", + "hashedPassword": "$6$fido2salt1$hashedpasswordforfido2credential1", + "up": true, + "uv": false, + "clientPin": false + }, + { + "credential": "BQYHCAkKCw==", + "salt": "c2Vjb25kc2FsdGZvcmZpZG8ydGVzdA==", + "hashedPassword": "$6$fido2salt2$hashedpasswordforfido2credential2", + "up": true, + "uv": true, + "clientPin": true + } + ] + }, + "secret": { + "tokenPin": ["1234"], + "fido2UserPresencePermitted": true, + "fido2UserVerificationPermitted": true + } +} diff --git a/test/fuzz/fuzz-user-record/auth-pkcs11.json b/test/fuzz/fuzz-user-record/auth-pkcs11.json new file mode 100644 index 0000000000000..d4604e8f0b6f1 --- /dev/null +++ b/test/fuzz/fuzz-user-record/auth-pkcs11.json @@ -0,0 +1,33 @@ +{ + "userName": "pkcs11user", + "realName": "PKCS#11 Token User", + "uid": 2001, + "gid": 2001, + "homeDirectory": "/home/pkcs11user", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "imagePath": "/home/pkcs11user.home", + "pkcs11TokenUri": [ + "pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=00000000;token=pkcs11user", + "pkcs11:model=YubiKey;manufacturer=Yubico;serial=12345678;token=PIV" + ], + "privileged": { + "pkcs11EncryptedKey": [ + { + "uri": "pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=00000000;token=pkcs11user", + "data": "SGVsbG8gV29ybGQhIFRoaXMgaXMgYSB0ZXN0IGVuY3J5cHRlZCBrZXkgZm9yIFBLQ1MjMTEu", + "hashedPassword": "$6$pkcs11salt$encryptedkeyhashforpkcs11authentication" + }, + { + "uri": "pkcs11:model=YubiKey;manufacturer=Yubico;serial=12345678;token=PIV", + "data": "QW5vdGhlciBlbmNyeXB0ZWQga2V5IGZvciBhIGRpZmZlcmVudCB0b2tlbi4=", + "hashedPassword": "$6$yubisalt$encryptedkeyhashforyubikey" + } + ] + }, + "secret": { + "tokenPin": ["123456"], + "pkcs11ProtectedAuthenticationPathPermitted": true + } +} diff --git a/test/fuzz/fuzz-user-record/auth-recovery.json b/test/fuzz/fuzz-user-record/auth-recovery.json new file mode 100644 index 0000000000000..f704e1ec1c614 --- /dev/null +++ b/test/fuzz/fuzz-user-record/auth-recovery.json @@ -0,0 +1,27 @@ +{ + "userName": "recoveryuser", + "realName": "Recovery Key User", + "uid": 2003, + "gid": 2003, + "homeDirectory": "/home/recoveryuser", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "imagePath": "/home/recoveryuser.home", + "recoveryKeyType": ["modhex64", "modhex64"], + "privileged": { + "hashedPassword": [ + "$6$mainsalt$mainpasswordhashforrecoveryuser" + ], + "recoveryKey": [ + { + "type": "modhex64", + "hashedPassword": "$6$recovery1$hashedrecoverykey1" + }, + { + "type": "modhex64", + "hashedPassword": "$6$recovery2$hashedrecoverykey2" + } + ] + } +} diff --git a/test/fuzz/fuzz-user-record/auto-resize-grow.json b/test/fuzz/fuzz-user-record/auto-resize-grow.json new file mode 100644 index 0000000000000..d72a797a11712 --- /dev/null +++ b/test/fuzz/fuzz-user-record/auto-resize-grow.json @@ -0,0 +1,14 @@ +{ + "userName": "growonly", + "realName": "Grow Only User", + "uid": 3008, + "gid": 3008, + "homeDirectory": "/home/growonly", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "imagePath": "/home/growonly.home", + "diskSize": 53687091200, + "autoResizeMode": "grow", + "rebalanceWeight": true +} diff --git a/test/fuzz/fuzz-user-record/auto-resize-modes.json b/test/fuzz/fuzz-user-record/auto-resize-modes.json new file mode 100644 index 0000000000000..75d50aed7b628 --- /dev/null +++ b/test/fuzz/fuzz-user-record/auto-resize-modes.json @@ -0,0 +1,14 @@ +{ + "userName": "autoresizeuser", + "realName": "Auto Resize Test User", + "uid": 3006, + "gid": 3006, + "homeDirectory": "/home/autoresizeuser", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "imagePath": "/home/autoresizeuser.home", + "diskSize": 53687091200, + "autoResizeMode": "shrink-and-grow", + "rebalanceWeight": 100 +} diff --git a/test/fuzz/fuzz-user-record/auto-resize-off.json b/test/fuzz/fuzz-user-record/auto-resize-off.json new file mode 100644 index 0000000000000..8479985aca74c --- /dev/null +++ b/test/fuzz/fuzz-user-record/auto-resize-off.json @@ -0,0 +1,14 @@ +{ + "userName": "noautoresize", + "realName": "No Auto Resize User", + "uid": 3007, + "gid": 3007, + "homeDirectory": "/home/noautoresize", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "imagePath": "/home/noautoresize.home", + "diskSize": 53687091200, + "autoResizeMode": "off", + "rebalanceWeight": false +} diff --git a/test/fuzz/fuzz-user-record/basic-regular.json b/test/fuzz/fuzz-user-record/basic-regular.json new file mode 100644 index 0000000000000..594f348aaefa7 --- /dev/null +++ b/test/fuzz/fuzz-user-record/basic-regular.json @@ -0,0 +1,9 @@ +{ + "userName": "testuser", + "realName": "Test User", + "uid": 1000, + "gid": 1000, + "homeDirectory": "/home/testuser", + "shell": "/bin/bash", + "disposition": "regular" +} diff --git a/test/fuzz/fuzz-user-record/basic-system.json b/test/fuzz/fuzz-user-record/basic-system.json new file mode 100644 index 0000000000000..9cfb3a2f63575 --- /dev/null +++ b/test/fuzz/fuzz-user-record/basic-system.json @@ -0,0 +1,7 @@ +{ + "userName": "httpd", + "uid": 473, + "gid": 473, + "disposition": "system", + "locked": true +} diff --git a/test/fuzz/fuzz-user-record/capabilities-full.json b/test/fuzz/fuzz-user-record/capabilities-full.json new file mode 100644 index 0000000000000..08d5f362beaca --- /dev/null +++ b/test/fuzz/fuzz-user-record/capabilities-full.json @@ -0,0 +1,56 @@ +{ + "userName": "capuser", + "realName": "Capabilities Test User", + "uid": 3010, + "gid": 3010, + "homeDirectory": "/home/capuser", + "shell": "/bin/bash", + "disposition": "regular", + "capabilityBoundingSet": [ + "CAP_AUDIT_CONTROL", + "CAP_AUDIT_READ", + "CAP_AUDIT_WRITE", + "CAP_BLOCK_SUSPEND", + "CAP_BPF", + "CAP_CHECKPOINT_RESTORE", + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_DAC_READ_SEARCH", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_IPC_LOCK", + "CAP_IPC_OWNER", + "CAP_KILL", + "CAP_LEASE", + "CAP_LINUX_IMMUTABLE", + "CAP_MAC_ADMIN", + "CAP_MAC_OVERRIDE", + "CAP_MKNOD", + "CAP_NET_ADMIN", + "CAP_NET_BIND_SERVICE", + "CAP_NET_BROADCAST", + "CAP_NET_RAW", + "CAP_PERFMON", + "CAP_SETFCAP", + "CAP_SETGID", + "CAP_SETPCAP", + "CAP_SETUID", + "CAP_SYS_ADMIN", + "CAP_SYS_BOOT", + "CAP_SYS_CHROOT", + "CAP_SYS_MODULE", + "CAP_SYS_NICE", + "CAP_SYS_PACCT", + "CAP_SYS_PTRACE", + "CAP_SYS_RAWIO", + "CAP_SYS_RESOURCE", + "CAP_SYS_TIME", + "CAP_SYS_TTY_CONFIG", + "CAP_SYSLOG", + "CAP_WAKE_ALARM" + ], + "capabilityAmbientSet": [ + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW" + ] +} diff --git a/test/fuzz/fuzz-user-record/crash-empty-image-path.json b/test/fuzz/fuzz-user-record/crash-empty-image-path.json new file mode 100644 index 0000000000000..0506a71fc2012 --- /dev/null +++ b/test/fuzz/fuzz-user-record/crash-empty-image-path.json @@ -0,0 +1,4 @@ +{ + "userName": "root", + "storage": "luks" +} diff --git a/test/fuzz/fuzz-user-record/default-area.json b/test/fuzz/fuzz-user-record/default-area.json new file mode 100644 index 0000000000000..b27f40b5d930b --- /dev/null +++ b/test/fuzz/fuzz-user-record/default-area.json @@ -0,0 +1,16 @@ +{ + "userName": "areauser", + "realName": "Default Area Test User", + "uid": 3012, + "gid": 3012, + "homeDirectory": "/home/areauser", + "shell": "/bin/bash", + "disposition": "regular", + "defaultArea": "work", + "perMachine": [ + { + "matchHostname": "personal", + "defaultArea": "personal" + } + ] +} diff --git a/test/fuzz/fuzz-user-record/disposition-container.json b/test/fuzz/fuzz-user-record/disposition-container.json new file mode 100644 index 0000000000000..67d5995467b7c --- /dev/null +++ b/test/fuzz/fuzz-user-record/disposition-container.json @@ -0,0 +1,8 @@ +{ + "userName": "containeruser", + "uid": 100000, + "gid": 100000, + "homeDirectory": "/home/containeruser", + "shell": "/bin/sh", + "disposition": "container" +} diff --git a/test/fuzz/fuzz-user-record/disposition-dynamic.json b/test/fuzz/fuzz-user-record/disposition-dynamic.json new file mode 100644 index 0000000000000..1678d994860cc --- /dev/null +++ b/test/fuzz/fuzz-user-record/disposition-dynamic.json @@ -0,0 +1,9 @@ +{ + "userName": "dynamicuser", + "uid": 61234, + "gid": 61234, + "homeDirectory": "/run/dynamicuser", + "shell": "/usr/sbin/nologin", + "disposition": "dynamic", + "locked": true +} diff --git a/test/fuzz/fuzz-user-record/disposition-foreign.json b/test/fuzz/fuzz-user-record/disposition-foreign.json new file mode 100644 index 0000000000000..9f3b2ff18f03a --- /dev/null +++ b/test/fuzz/fuzz-user-record/disposition-foreign.json @@ -0,0 +1,8 @@ +{ + "userName": "foreignuser", + "uid": 200000, + "gid": 200000, + "homeDirectory": "/home/foreign", + "shell": "/bin/sh", + "disposition": "foreign" +} diff --git a/test/fuzz/fuzz-user-record/disposition-intrinsic.json b/test/fuzz/fuzz-user-record/disposition-intrinsic.json new file mode 100644 index 0000000000000..9a66fa1b4f7b7 --- /dev/null +++ b/test/fuzz/fuzz-user-record/disposition-intrinsic.json @@ -0,0 +1,8 @@ +{ + "userName": "root", + "uid": 0, + "gid": 0, + "homeDirectory": "/root", + "shell": "/bin/bash", + "disposition": "intrinsic" +} diff --git a/test/fuzz/fuzz-user-record/disposition-reserved.json b/test/fuzz/fuzz-user-record/disposition-reserved.json new file mode 100644 index 0000000000000..74cf4085464a9 --- /dev/null +++ b/test/fuzz/fuzz-user-record/disposition-reserved.json @@ -0,0 +1,9 @@ +{ + "userName": "reserveduser", + "uid": 999, + "gid": 999, + "homeDirectory": "/nonexistent", + "shell": "/usr/sbin/nologin", + "disposition": "reserved", + "locked": true +} diff --git a/test/fuzz/fuzz-user-record/edge-empty-arrays.json b/test/fuzz/fuzz-user-record/edge-empty-arrays.json new file mode 100644 index 0000000000000..8e46c3d31ef02 --- /dev/null +++ b/test/fuzz/fuzz-user-record/edge-empty-arrays.json @@ -0,0 +1,34 @@ +{ + "userName": "emptyarrays", + "realName": "Empty Arrays Test User", + "uid": 3001, + "gid": 3001, + "homeDirectory": "/home/emptyarrays", + "shell": "/bin/bash", + "disposition": "regular", + "aliases": [], + "environment": [], + "additionalLanguages": [], + "memberOf": [], + "capabilityBoundingSet": [], + "capabilityAmbientSet": [], + "pkcs11TokenUri": [], + "fido2HmacCredential": [], + "recoveryKeyType": [], + "selfModifiableFields": [], + "selfModifiableBlobs": [], + "selfModifiablePrivileged": [], + "privileged": { + "hashedPassword": [], + "sshAuthorizedKeys": [], + "pkcs11EncryptedKey": [], + "fido2HmacSalt": [], + "recoveryKey": [] + }, + "perMachine": [], + "signature": [], + "secret": { + "password": [], + "tokenPin": [] + } +} diff --git a/test/fuzz/fuzz-user-record/edge-long-strings.json b/test/fuzz/fuzz-user-record/edge-long-strings.json new file mode 100644 index 0000000000000..b7883637e70d3 --- /dev/null +++ b/test/fuzz/fuzz-user-record/edge-long-strings.json @@ -0,0 +1,24 @@ +{ + "userName": "longstringsuser", + "realName": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "uid": 3004, + "gid": 3004, + "homeDirectory": "/home/longstringsuser", + "shell": "/bin/bash", + "disposition": "regular", + "location": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "environment": [ + "LONGVAR=CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" + ], + "memberOf": [ + "group1", "group2", "group3", "group4", "group5", + "group6", "group7", "group8", "group9", "group10", + "group11", "group12", "group13", "group14", "group15", + "group16", "group17", "group18", "group19", "group20" + ], + "privileged": { + "sshAuthorizedKeys": [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDVeryLongKeyDataHereThatGoesOnAndOnAndOnForAWhileToTestLongSSHKeysAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA user@host" + ] + } +} diff --git a/test/fuzz/fuzz-user-record/edge-max-values.json b/test/fuzz/fuzz-user-record/edge-max-values.json new file mode 100644 index 0000000000000..52a34109f774b --- /dev/null +++ b/test/fuzz/fuzz-user-record/edge-max-values.json @@ -0,0 +1,41 @@ +{ + "userName": "maxuser", + "realName": "Maximum Values Test User", + "uid": 4294967294, + "gid": 4294967294, + "homeDirectory": "/home/maxuser", + "shell": "/bin/bash", + "disposition": "regular", + "umask": 511, + "niceLevel": 19, + "cpuWeight": 10000, + "ioWeight": 10000, + "diskSize": 18446744073709551615, + "diskSizeRelative": 4294967295, + "tasksMax": 18446744073709551614, + "memoryHigh": 18446744073709551614, + "memoryMax": 18446744073709551614, + "accessMode": 511, + "luksPbkdfForceIterations": 18446744073709551615, + "luksPbkdfTimeCostUSec": 18446744073709551615, + "luksPbkdfMemoryCost": 18446744073709551615, + "luksPbkdfParallelThreads": 18446744073709551615, + "luksSectorSize": 4096, + "luksVolumeKeySize": 512, + "rebalanceWeight": 10000, + "rateLimitIntervalUSec": 18446744073709551615, + "rateLimitBurst": 18446744073709551615, + "stopDelayUSec": 18446744073709551615, + "lastChangeUSec": 18446744073709551615, + "lastPasswordChangeUSec": 18446744073709551615, + "notBeforeUSec": 0, + "notAfterUSec": 18446744073709551615, + "passwordChangeMinUSec": 0, + "passwordChangeMaxUSec": 18446744073709551615, + "passwordChangeWarnUSec": 18446744073709551615, + "passwordChangeInactiveUSec": 18446744073709551615, + "tmpLimit": 18446744073709551615, + "tmpLimitScale": 4294967295, + "devShmLimit": 18446744073709551615, + "devShmLimitScale": 4294967295 +} diff --git a/test/fuzz/fuzz-user-record/edge-min-values.json b/test/fuzz/fuzz-user-record/edge-min-values.json new file mode 100644 index 0000000000000..b0b569a34e8c4 --- /dev/null +++ b/test/fuzz/fuzz-user-record/edge-min-values.json @@ -0,0 +1,41 @@ +{ + "userName": "minuser", + "realName": "Minimum Values Test User", + "uid": 0, + "gid": 0, + "homeDirectory": "/", + "shell": "/", + "disposition": "intrinsic", + "umask": 0, + "niceLevel": -20, + "cpuWeight": 1, + "ioWeight": 1, + "diskSize": 1, + "diskSizeRelative": 1, + "tasksMax": 1, + "memoryHigh": 1, + "memoryMax": 1, + "accessMode": 0, + "luksPbkdfForceIterations": 1, + "luksPbkdfTimeCostUSec": 1, + "luksPbkdfMemoryCost": 1, + "luksPbkdfParallelThreads": 1, + "luksSectorSize": 512, + "luksVolumeKeySize": 1, + "rebalanceWeight": 1, + "rateLimitIntervalUSec": 1, + "rateLimitBurst": 1, + "stopDelayUSec": 0, + "lastChangeUSec": 0, + "lastPasswordChangeUSec": 0, + "notBeforeUSec": 0, + "notAfterUSec": 1, + "passwordChangeMinUSec": 0, + "passwordChangeMaxUSec": 1, + "passwordChangeWarnUSec": 0, + "passwordChangeInactiveUSec": 0, + "tmpLimit": 1, + "tmpLimitScale": 1, + "devShmLimit": 1, + "devShmLimitScale": 1 +} diff --git a/test/fuzz/fuzz-user-record/edge-missing-optionals.json b/test/fuzz/fuzz-user-record/edge-missing-optionals.json new file mode 100644 index 0000000000000..e06884f9768d4 --- /dev/null +++ b/test/fuzz/fuzz-user-record/edge-missing-optionals.json @@ -0,0 +1,9 @@ +{ + "userName": "missingoptionals", + "realName": "Missing Optional Fields User", + "uid": 3002, + "gid": 3002, + "homeDirectory": "/home/missingoptionals", + "shell": "/bin/bash", + "disposition": "regular" +} diff --git a/test/fuzz/fuzz-user-record/edge-null-values.json b/test/fuzz/fuzz-user-record/edge-null-values.json new file mode 100644 index 0000000000000..cb995f38bdd7f --- /dev/null +++ b/test/fuzz/fuzz-user-record/edge-null-values.json @@ -0,0 +1,18 @@ +{ + "userName": "nullvalues", + "realName": "Null Values Test User", + "uid": 3019, + "gid": 3019, + "homeDirectory": "/home/nullvalues", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "autoResizeMode": null, + "rebalanceWeight": null, + "luksDiscard": null, + "luksOfflineDiscard": null, + "tmpLimit": null, + "tmpLimitScale": null, + "devShmLimit": null, + "devShmLimitScale": null +} diff --git a/test/fuzz/fuzz-user-record/edge-unicode-strings.json b/test/fuzz/fuzz-user-record/edge-unicode-strings.json new file mode 100644 index 0000000000000..ce1204d08060c --- /dev/null +++ b/test/fuzz/fuzz-user-record/edge-unicode-strings.json @@ -0,0 +1,21 @@ +{ + "userName": "unicodeuser", + "realName": "Ünïcödé Üsér 日本語 中文 العربية", + "uid": 3003, + "gid": 3003, + "homeDirectory": "/home/unicodeuser", + "shell": "/bin/bash", + "disposition": "regular", + "emailAddress": "unicode@例え.jp", + "location": "東京, 日本 🌸", + "environment": [ + "GREETING=Привет мир", + "HELLO=你好世界" + ], + "preferredLanguage": "ja_JP.UTF-8", + "additionalLanguages": ["zh_CN.UTF-8", "ar_SA.UTF-8", "ru_RU.UTF-8"], + "timeZone": "Asia/Tokyo", + "privileged": { + "passwordHint": "お気に入りの食べ物は何ですか?" + } +} diff --git a/test/fuzz/fuzz-user-record/full-featured.json b/test/fuzz/fuzz-user-record/full-featured.json new file mode 100644 index 0000000000000..4ed15a92998b9 --- /dev/null +++ b/test/fuzz/fuzz-user-record/full-featured.json @@ -0,0 +1,245 @@ +{ + "userName": "fuzzuser", + "realm": "example.com", + "aliases": ["fuzz", "fuzzer", "testfuzz"], + "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "blobDirectory": "/var/cache/systemd/homed/fuzzuser/", + "blobManifest": { + "avatar": "c0636851d25a62d817ff7da4e081d1e646e42c74d0ecb53425f75fcf1ba43b52", + "login-background": "da7ad0222a6edbc6cd095149c72d38d92fd3114f606e4b57469857ef47fade18", + "custom-file": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + }, + "realName": "Fuzz Test User", + "emailAddress": "fuzzuser@example.com", + "iconName": "user-available", + "location": "Fuzzing Lab, Room 42", + "disposition": "regular", + "lastChangeUSec": 1700000000000000, + "lastPasswordChangeUSec": 1699000000000000, + "shell": "/bin/bash", + "umask": 22, + "environment": [ + "EDITOR=vim", + "PAGER=less", + "LANG=en_US.UTF-8", + "FUZZ_VAR=test_value" + ], + "timeZone": "Europe/Berlin", + "preferredLanguage": "en_US.UTF-8", + "additionalLanguages": ["de_DE.UTF-8", "fr_FR.UTF-8", "es_ES.UTF-8"], + "niceLevel": 5, + "resourceLimits": { + "RLIMIT_NOFILE": { "cur": 1024, "max": 65536 }, + "RLIMIT_NPROC": { "cur": 4096, "max": 8192 }, + "RLIMIT_MEMLOCK": { "cur": 65536, "max": 131072 }, + "RLIMIT_AS": { "cur": 4294967296, "max": 8589934592 }, + "RLIMIT_FSIZE": { "cur": 1073741824, "max": 2147483648 }, + "RLIMIT_STACK": { "cur": 8388608, "max": 16777216 }, + "RLIMIT_CORE": { "cur": 0, "max": 0 }, + "RLIMIT_RSS": { "cur": 4294967296, "max": 8589934592 }, + "RLIMIT_CPU": { "cur": 3600, "max": 7200 }, + "RLIMIT_DATA": { "cur": 4294967296, "max": 8589934592 }, + "RLIMIT_NICE": { "cur": 0, "max": 0 }, + "RLIMIT_RTPRIO": { "cur": 0, "max": 0 }, + "RLIMIT_RTTIME": { "cur": 1000000, "max": 2000000 }, + "RLIMIT_SIGPENDING": { "cur": 128, "max": 256 }, + "RLIMIT_MSGQUEUE": { "cur": 819200, "max": 1638400 }, + "RLIMIT_LOCKS": { "cur": 1024, "max": 2048 } + }, + "locked": false, + "notBeforeUSec": 1600000000000000, + "notAfterUSec": 1900000000000000, + "storage": "luks", + "diskSize": 107374182400, + "diskSizeRelative": 2147483648, + "skeletonDirectory": "/etc/skel", + "accessMode": 448, + "tasksMax": 4096, + "memoryHigh": 4294967296, + "memoryMax": 8589934592, + "cpuWeight": 100, + "ioWeight": 100, + "mountNoDevices": true, + "mountNoSuid": true, + "mountNoExecute": false, + "cifsDomain": "WORKGROUP", + "cifsUserName": "fuzzuser", + "cifsService": "//server.example.com/homes/fuzzuser", + "cifsExtraMountOptions": "vers=3.0,seal", + "imagePath": "/home/fuzzuser.home", + "homeDirectory": "/home/fuzzuser", + "uid": 60001, + "gid": 60001, + "memberOf": ["wheel", "users", "audio", "video", "docker", "libvirt"], + "capabilityBoundingSet": ["CAP_NET_ADMIN", "CAP_SYS_PTRACE", "CAP_SETUID", "CAP_SETGID"], + "capabilityAmbientSet": ["CAP_NET_BIND_SERVICE"], + "fileSystemType": "ext4", + "partitionUuid": "41f9ce04-c827-4b74-a981-c669f93eb4dc", + "luksUuid": "e63581ba-79fb-4226-b9de-1888393f7573", + "fileSystemUuid": "758e88c8-5851-4a2a-b88f-e7474279c111", + "luksDiscard": true, + "luksOfflineDiscard": false, + "luksCipher": "aes", + "luksCipherMode": "xts-plain64", + "luksVolumeKeySize": 64, + "luksPbkdfHashAlgorithm": "sha512", + "luksPbkdfType": "argon2id", + "luksPbkdfForceIterations": 1000, + "luksPbkdfTimeCostUSec": 1000000, + "luksPbkdfMemoryCost": 1073741824, + "luksPbkdfParallelThreads": 4, + "luksSectorSize": 4096, + "luksExtraMountOptions": "discard,noatime", + "dropCaches": true, + "autoResizeMode": "shrink-and-grow", + "rebalanceWeight": 100, + "service": "io.systemd.Home", + "rateLimitIntervalUSec": 60000000, + "rateLimitBurst": 5, + "enforcePasswordPolicy": true, + "autoLogin": false, + "preferredSessionType": "wayland", + "preferredSessionLauncher": "gnome", + "stopDelayUSec": 180000000, + "killProcesses": true, + "passwordChangeMinUSec": 86400000000, + "passwordChangeMaxUSec": 7776000000000, + "passwordChangeWarnUSec": 1209600000000, + "passwordChangeInactiveUSec": 2592000000000, + "passwordChangeNow": false, + "pkcs11TokenUri": [ + "pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=00000000;token=fuzzuser" + ], + "fido2HmacCredential": [ + "AQIDBA==" + ], + "recoveryKeyType": ["modhex64"], + "selfModifiableFields": [ + "realName", + "emailAddress", + "iconName", + "location", + "preferredLanguage", + "additionalLanguages", + "timeZone", + "preferredSessionType", + "preferredSessionLauncher" + ], + "selfModifiableBlobs": ["avatar", "login-background"], + "selfModifiablePrivileged": ["passwordHint"], + "tmpLimit": 1073741824, + "tmpLimitScale": 858993459, + "devShmLimit": 536870912, + "devShmLimitScale": 429496729, + "defaultArea": "work", + "privileged": { + "passwordHint": "Your favorite fuzzing target", + "hashedPassword": [ + "$6$rounds=656000$somesalt$hashedpassworddata1234567890abcdefghijklmnopqrstuvwxyz", + "$y$j9T$somesalt$yescrypthash1234567890" + ], + "sshAuthorizedKeys": [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG7FVK5YkKL3IYQr7Z6UDYqvB8S8bM7b8vNjVp4S6Y9Y fuzzuser@example.com", + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC0123456789abcdef fuzzuser@backup" + ], + "pkcs11EncryptedKey": [ + { + "uri": "pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=00000000;token=fuzzuser", + "data": "SGVsbG8gV29ybGQhIFRoaXMgaXMgYSB0ZXN0IGVuY3J5cHRlZCBrZXku", + "hashedPassword": "$6$rounds=656000$somesalt$encryptedkeyhash" + } + ], + "fido2HmacSalt": [ + { + "credential": "AQIDBA==", + "salt": "c29tZXNhbHRmb3JmaWRvMg==", + "hashedPassword": "$6$rounds=656000$fidosalt$fidohashedpassword", + "up": true, + "uv": true, + "clientPin": false + } + ], + "recoveryKey": [ + { + "type": "modhex64", + "hashedPassword": "$6$rounds=656000$recoverysalt$recoverykeyhash" + } + ] + }, + "perMachine": [ + { + "matchMachineId": "15e19cf24e004b949ddaac60c74aa165", + "diskSize": 214748364800, + "memoryMax": 17179869184, + "cpuWeight": 200 + }, + { + "matchHostname": "workstation", + "shell": "/bin/zsh", + "niceLevel": 0, + "autoLogin": true + }, + { + "matchNotMachineId": "00000000000000000000000000000000", + "matchNotHostname": "server", + "locked": false + } + ], + "binding": { + "15e19cf24e004b949ddaac60c74aa165": { + "blobDirectory": "/var/cache/systemd/homed/fuzzuser/", + "imagePath": "/home/fuzzuser.home", + "homeDirectory": "/home/fuzzuser", + "partitionUuid": "41f9ce04-c827-4b74-a981-c669f93eb4dc", + "luksUuid": "e63581ba-79fb-4226-b9de-1888393f7573", + "fileSystemUuid": "758e88c8-5851-4a2a-b88f-e7474279c111", + "uid": 60001, + "gid": 60001, + "storage": "luks", + "fileSystemType": "ext4", + "luksCipher": "aes", + "luksCipherMode": "xts-plain64", + "luksVolumeKeySize": 64 + } + }, + "status": { + "15e19cf24e004b949ddaac60c74aa165": { + "aliases": ["fuzz-status-alias"], + "diskUsage": 53687091200, + "diskFree": 53687091200, + "diskSize": 107374182400, + "diskCeiling": 214748364800, + "diskFloor": 1073741824, + "state": "active", + "service": "io.systemd.Home", + "signedLocally": true, + "goodAuthenticationCounter": 42, + "badAuthenticationCounter": 3, + "lastGoodAuthenticationUSec": 1700000000000000, + "lastBadAuthenticationUSec": 1699999000000000, + "rateLimitBeginUSec": 1699999500000000, + "rateLimitCount": 1, + "removable": false, + "accessMode": 448, + "fileSystemType": "ext4", + "fallbackShell": "/bin/sh", + "fallbackHomeDirectory": "/", + "useFallback": false, + "defaultArea": "work" + } + }, + "signature": [ + { + "data": "TFUvSGVWclBaU3ppM01KMFBWSHdENW0veGY1MVhEWUNyU3BiRFJOQmR0RjRmRFZock4wdDJJMk9xSC8xeVhpQmlkWGxWMHB0TXVRVnE4S1ZJQ2RFRHE9PQ==", + "key": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA/QT6kQWOAMhDJf56jBmszEQQpJHqDsGDMZOdiptBgRk=\n-----END PUBLIC KEY-----\n" + } + ], + "secret": { + "password": ["testpassword123", "alternatepassword456"], + "tokenPin": ["1234", "5678"], + "pkcs11Pin": ["0000"], + "pkcs11ProtectedAuthenticationPathPermitted": true, + "fido2UserPresencePermitted": true, + "fido2UserVerificationPermitted": true + } +} diff --git a/test/fuzz/fuzz-user-record/luks-complete.json b/test/fuzz/fuzz-user-record/luks-complete.json new file mode 100644 index 0000000000000..d74b3ae94a5e7 --- /dev/null +++ b/test/fuzz/fuzz-user-record/luks-complete.json @@ -0,0 +1,36 @@ +{ + "userName": "luksuser", + "realName": "Complete LUKS User", + "uid": 3015, + "gid": 3015, + "homeDirectory": "/home/luksuser", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "imagePath": "/home/luksuser.home", + "diskSize": 107374182400, + "fileSystemType": "btrfs", + "partitionUuid": "12345678-1234-1234-1234-123456789abc", + "luksUuid": "abcdef12-3456-7890-abcd-ef1234567890", + "fileSystemUuid": "fedcba98-7654-3210-fedc-ba9876543210", + "luksDiscard": true, + "luksOfflineDiscard": true, + "luksCipher": "aes", + "luksCipherMode": "xts-plain64", + "luksVolumeKeySize": 64, + "luksPbkdfHashAlgorithm": "sha256", + "luksPbkdfType": "pbkdf2", + "luksPbkdfForceIterations": 100000, + "luksPbkdfTimeCostUSec": 500000, + "luksPbkdfMemoryCost": 67108864, + "luksPbkdfParallelThreads": 2, + "luksSectorSize": 4096, + "luksExtraMountOptions": "compress=zstd:3,noatime", + "dropCaches": true, + "mountNoDevices": true, + "mountNoSuid": true, + "mountNoExecute": false, + "accessMode": 448, + "autoResizeMode": "shrink-and-grow", + "rebalanceWeight": 100 +} diff --git a/test/fuzz/fuzz-user-record/minimal.json b/test/fuzz/fuzz-user-record/minimal.json new file mode 100644 index 0000000000000..a22cc44fb3d34 --- /dev/null +++ b/test/fuzz/fuzz-user-record/minimal.json @@ -0,0 +1,3 @@ +{ + "userName": "u" +} diff --git a/test/fuzz/fuzz-user-record/password-policy.json b/test/fuzz/fuzz-user-record/password-policy.json new file mode 100644 index 0000000000000..c4b336a4f5e99 --- /dev/null +++ b/test/fuzz/fuzz-user-record/password-policy.json @@ -0,0 +1,24 @@ +{ + "userName": "policuser", + "realName": "Password Policy User", + "uid": 3017, + "gid": 3017, + "homeDirectory": "/home/policyuser", + "shell": "/bin/bash", + "disposition": "regular", + "enforcePasswordPolicy": true, + "passwordChangeNow": true, + "passwordChangeMinUSec": 86400000000, + "passwordChangeMaxUSec": 7776000000000, + "passwordChangeWarnUSec": 1209600000000, + "passwordChangeInactiveUSec": 2592000000000, + "lastPasswordChangeUSec": 1700000000000000, + "rateLimitIntervalUSec": 60000000, + "rateLimitBurst": 3, + "privileged": { + "hashedPassword": [ + "$6$rounds=656000$salt$hashedpassword" + ], + "passwordHint": "Your first pet's name" + } +} diff --git a/test/fuzz/fuzz-user-record/per-machine-complex.json b/test/fuzz/fuzz-user-record/per-machine-complex.json new file mode 100644 index 0000000000000..327f3e5a92665 --- /dev/null +++ b/test/fuzz/fuzz-user-record/per-machine-complex.json @@ -0,0 +1,58 @@ +{ + "userName": "permachineuser", + "realName": "Per Machine Complex User", + "uid": 3005, + "gid": 3005, + "homeDirectory": "/home/permachineuser", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "diskSize": 53687091200, + "memoryMax": 4294967296, + "cpuWeight": 100, + "perMachine": [ + { + "matchMachineId": "11111111111111111111111111111111", + "diskSize": 107374182400, + "memoryMax": 8589934592, + "cpuWeight": 200, + "storage": "luks", + "shell": "/bin/zsh" + }, + { + "matchMachineId": ["22222222222222222222222222222222", "33333333333333333333333333333333"], + "diskSize": 214748364800, + "memoryMax": 17179869184, + "ioWeight": 500 + }, + { + "matchHostname": "workstation", + "autoLogin": true, + "niceLevel": -5, + "environment": ["WORKSTATION=true"] + }, + { + "matchHostname": ["server1", "server2"], + "locked": false, + "killProcesses": false, + "stopDelayUSec": 300000000 + }, + { + "matchNotMachineId": "44444444444444444444444444444444", + "matchNotHostname": "restricted-host", + "tasksMax": 8192 + }, + { + "matchMachineId": "55555555555555555555555555555555", + "matchHostname": "special-host", + "blobDirectory": "/var/cache/special/blobs/", + "blobManifest": { + "special-file": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + }, + "selfModifiableFields": ["realName", "location"], + "selfModifiableBlobs": ["avatar"], + "preferredSessionType": "x11", + "preferredSessionLauncher": "plasma" + } + ] +} diff --git a/test/fuzz/fuzz-user-record/rlimits-all.json b/test/fuzz/fuzz-user-record/rlimits-all.json new file mode 100644 index 0000000000000..ea9299a69eeb5 --- /dev/null +++ b/test/fuzz/fuzz-user-record/rlimits-all.json @@ -0,0 +1,27 @@ +{ + "userName": "rlimitsuser", + "realName": "Resource Limits Test User", + "uid": 3009, + "gid": 3009, + "homeDirectory": "/home/rlimitsuser", + "shell": "/bin/bash", + "disposition": "regular", + "resourceLimits": { + "RLIMIT_AS": { "cur": 4294967296, "max": 8589934592 }, + "RLIMIT_CORE": { "cur": 0, "max": 9223372036854775807 }, + "RLIMIT_CPU": { "cur": 3600, "max": 9223372036854775807 }, + "RLIMIT_DATA": { "cur": 4294967296, "max": 9223372036854775807 }, + "RLIMIT_FSIZE": { "cur": 1073741824, "max": 9223372036854775807 }, + "RLIMIT_LOCKS": { "cur": 1024, "max": 9223372036854775807 }, + "RLIMIT_MEMLOCK": { "cur": 65536, "max": 9223372036854775807 }, + "RLIMIT_MSGQUEUE": { "cur": 819200, "max": 9223372036854775807 }, + "RLIMIT_NICE": { "cur": 0, "max": 40 }, + "RLIMIT_NOFILE": { "cur": 1024, "max": 1048576 }, + "RLIMIT_NPROC": { "cur": 4096, "max": 9223372036854775807 }, + "RLIMIT_RSS": { "cur": 4294967296, "max": 9223372036854775807 }, + "RLIMIT_RTPRIO": { "cur": 0, "max": 99 }, + "RLIMIT_RTTIME": { "cur": 1000000, "max": 9223372036854775807 }, + "RLIMIT_SIGPENDING": { "cur": 128, "max": 9223372036854775807 }, + "RLIMIT_STACK": { "cur": 8388608, "max": 9223372036854775807 } + } +} diff --git a/test/fuzz/fuzz-user-record/session-prefs.json b/test/fuzz/fuzz-user-record/session-prefs.json new file mode 100644 index 0000000000000..56344241f3532 --- /dev/null +++ b/test/fuzz/fuzz-user-record/session-prefs.json @@ -0,0 +1,14 @@ +{ + "userName": "sessionuser", + "realName": "Session Preferences User", + "uid": 3016, + "gid": 3016, + "homeDirectory": "/home/sessionuser", + "shell": "/bin/bash", + "disposition": "regular", + "autoLogin": true, + "preferredSessionType": "wayland", + "preferredSessionLauncher": "gnome", + "stopDelayUSec": 180000000, + "killProcesses": false +} diff --git a/test/fuzz/fuzz-user-record/ssh-keys.json b/test/fuzz/fuzz-user-record/ssh-keys.json new file mode 100644 index 0000000000000..00d947d6ba8e0 --- /dev/null +++ b/test/fuzz/fuzz-user-record/ssh-keys.json @@ -0,0 +1,18 @@ +{ + "userName": "sshuser", + "realName": "SSH Keys User", + "uid": 3013, + "gid": 3013, + "homeDirectory": "/home/sshuser", + "shell": "/bin/bash", + "disposition": "regular", + "privileged": { + "sshAuthorizedKeys": [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKlH9A7KdFhMmfBrV2fzONpPeaQaJAXyY3bMpZ1sT5Xy ed25519-key", + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC7s... rsa-key", + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBA... ecdsa-key", + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIA... security-key", + "command=\"/usr/bin/restricted\",no-port-forwarding,no-agent-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJx... restricted-key" + ] + } +} diff --git a/test/fuzz/fuzz-user-record/storage-cifs.json b/test/fuzz/fuzz-user-record/storage-cifs.json new file mode 100644 index 0000000000000..f30d5d676d0f7 --- /dev/null +++ b/test/fuzz/fuzz-user-record/storage-cifs.json @@ -0,0 +1,15 @@ +{ + "userName": "cifsuser", + "realName": "CIFS Home User", + "uid": 1001, + "gid": 1001, + "homeDirectory": "/home/cifsuser", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "cifs", + "cifsDomain": "EXAMPLE", + "cifsUserName": "cifsuser", + "cifsService": "//fileserver.example.com/homes/cifsuser", + "cifsExtraMountOptions": "vers=3.0,seal,sec=krb5", + "memberOf": ["users", "domain-users"] +} diff --git a/test/fuzz/fuzz-user-record/storage-classic.json b/test/fuzz/fuzz-user-record/storage-classic.json new file mode 100644 index 0000000000000..b77e07d09b252 --- /dev/null +++ b/test/fuzz/fuzz-user-record/storage-classic.json @@ -0,0 +1,10 @@ +{ + "userName": "classicuser", + "realName": "Classic Unix User", + "uid": 1005, + "gid": 1005, + "homeDirectory": "/home/classicuser", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "classic" +} diff --git a/test/fuzz/fuzz-user-record/storage-directory.json b/test/fuzz/fuzz-user-record/storage-directory.json new file mode 100644 index 0000000000000..12dbd6eb5e9a0 --- /dev/null +++ b/test/fuzz/fuzz-user-record/storage-directory.json @@ -0,0 +1,13 @@ +{ + "userName": "diruser", + "realName": "Directory Storage User", + "uid": 1002, + "gid": 1002, + "homeDirectory": "/home/diruser", + "imagePath": "/home/diruser.homedir", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "directory", + "accessMode": 448, + "diskSize": 10737418240 +} diff --git a/test/fuzz/fuzz-user-record/storage-fscrypt.json b/test/fuzz/fuzz-user-record/storage-fscrypt.json new file mode 100644 index 0000000000000..63c2887fd4e3b --- /dev/null +++ b/test/fuzz/fuzz-user-record/storage-fscrypt.json @@ -0,0 +1,14 @@ +{ + "userName": "fscryptuser", + "realName": "Fscrypt User", + "uid": 1004, + "gid": 1004, + "homeDirectory": "/home/fscryptuser", + "imagePath": "/home/fscryptuser.homedir", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "fscrypt", + "fileSystemType": "ext4", + "accessMode": 448, + "diskSize": 21474836480 +} diff --git a/test/fuzz/fuzz-user-record/storage-subvolume.json b/test/fuzz/fuzz-user-record/storage-subvolume.json new file mode 100644 index 0000000000000..8e80145d5c293 --- /dev/null +++ b/test/fuzz/fuzz-user-record/storage-subvolume.json @@ -0,0 +1,14 @@ +{ + "userName": "btrfsuser", + "realName": "Btrfs Subvolume User", + "uid": 1003, + "gid": 1003, + "homeDirectory": "/home/btrfsuser", + "imagePath": "/home/btrfsuser.homedir", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "subvolume", + "fileSystemType": "btrfs", + "accessMode": 448, + "diskSize": 53687091200 +} diff --git a/test/fuzz/fuzz-user-record/time-constraints.json b/test/fuzz/fuzz-user-record/time-constraints.json new file mode 100644 index 0000000000000..238efa3e6902a --- /dev/null +++ b/test/fuzz/fuzz-user-record/time-constraints.json @@ -0,0 +1,13 @@ +{ + "userName": "timeuser", + "realName": "Time Constraints User", + "uid": 3018, + "gid": 3018, + "homeDirectory": "/home/timeuser", + "shell": "/bin/bash", + "disposition": "regular", + "notBeforeUSec": 1609459200000000, + "notAfterUSec": 1924905600000000, + "lastChangeUSec": 1700000000000000, + "locked": false +} diff --git a/test/fuzz/fuzz-user-record/tmpfs-limits.json b/test/fuzz/fuzz-user-record/tmpfs-limits.json new file mode 100644 index 0000000000000..725e6df8a2ef2 --- /dev/null +++ b/test/fuzz/fuzz-user-record/tmpfs-limits.json @@ -0,0 +1,13 @@ +{ + "userName": "tmpfsuser", + "realName": "Tmpfs Limits Test User", + "uid": 3014, + "gid": 3014, + "homeDirectory": "/home/tmpfsuser", + "shell": "/bin/bash", + "disposition": "regular", + "tmpLimit": 1073741824, + "tmpLimitScale": 214748364, + "devShmLimit": 536870912, + "devShmLimitScale": 107374182 +} diff --git a/test/fuzz/fuzz-user-record/with-realm.json b/test/fuzz/fuzz-user-record/with-realm.json new file mode 100644 index 0000000000000..ab77c824afcbe --- /dev/null +++ b/test/fuzz/fuzz-user-record/with-realm.json @@ -0,0 +1,10 @@ +{ + "userName": "realmuser", + "realm": "corp.example.com", + "realName": "Corporate Realm User", + "uid": 3011, + "gid": 3011, + "homeDirectory": "/home/realmuser@corp.example.com", + "shell": "/bin/bash", + "disposition": "regular" +} diff --git a/test/fuzz/meson.build b/test/fuzz/meson.build index 6f9f43a4105f9..d19fda3a02eaa 100644 --- a/test/fuzz/meson.build +++ b/test/fuzz/meson.build @@ -51,7 +51,7 @@ foreach p : out.stdout().split() # # Also, backslashes get mangled, so skip test. See # https://github.com/mesonbuild/meson/issues/1564. - if p.contains('\\') + if p.contains('\\') or not fs.exists(p) continue endif fuzzer = fs.name(fs.parent(p)) diff --git a/test/integration-tests/README.md b/test/integration-tests/README.md index 4fea50660fe21..e0a345613caab 100644 --- a/test/integration-tests/README.md +++ b/test/integration-tests/README.md @@ -339,71 +339,6 @@ where `--test-name=` is the name of the test you want to run/debug. The `--shell-fail` option will pause the execution in case the test fails and shows you the information how to connect to the testbed for further debugging. -## Manually running CodeQL analysis - -This is mostly useful for debugging various CodeQL quirks. - -Download the CodeQL Bundle from https://github.com/github/codeql-action/releases -and unpack it somewhere. From now the 'tutorial' assumes you have the `codeql` -binary from the unpacked archive in $PATH for brevity. - -Switch to the systemd repository if not already: - -```shell -$ cd -``` - -Create an initial CodeQL database: - -```shell -$ CCACHE_DISABLE=1 codeql database create codeqldb --language=cpp -vvv -``` - -Disabling ccache is important, otherwise you might see CodeQL complaining: - -No source code was seen and extracted to -/home/mrc0mmand/repos/@ci-incubator/systemd/codeqldb. This can occur if the -specified build commands failed to compile or process any code. - - Confirm that there is some source code for the specified language in the - project. - - For codebases written in Go, JavaScript, TypeScript, and Python, do not - specify an explicit --command. - - For other languages, the --command must specify a "clean" build which - compiles all the source code files without reusing existing build artefacts. - -If you want to run all queries systemd uses in CodeQL, run: - -```shell -$ codeql database analyze codeqldb/ --format csv --output results.csv .github/codeql-custom.qls .github/codeql-queries/*.ql -vvv -``` - -Note: this will take a while. - -If you're interested in a specific check, the easiest way (without hunting down -the specific CodeQL query file) is to create a custom query suite. For example: - -```shell -$ cat >test.qls < bool: + notify_socket = os.environ.get("NOTIFY_SOCKET") + if not notify_socket: + return False + if notify_socket.startswith("@"): + notify_socket = "\0" + notify_socket[1:] + try: + with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sock: + sock.sendto(state.encode(), notify_socket) + except OSError: + return False + + return True + +class Handler(BaseHTTPRequestHandler): + def do_GET(self): + if self.path == "/userdata": + body = b"{\"systemd.credentials\":[{\"name\":\"acredtest\",\"text\":\"avalue\"}]}" + self.send_response(200) + self.send_header("Content-Type", "text/plain") + self.send_header("Content-Length", len(body)) + self.end_headers() + self.wfile.write(body) + elif self.path == "/hostname": + body = b"piff" + self.send_response(200) + self.send_header("Content-Type", "text/plain") + self.send_header("Content-Length", len(body)) + self.end_headers() + self.wfile.write(body) + else: + self.send_error(404) + + def log_message(self, fmt, *args): + print(f"{self.address_string()} - {fmt % args}") + +PORT=8088 + +server = HTTPServer(("", PORT), Handler) +print(f"Serving on http://localhost:{PORT}/") +try: + sd_notify("READY=1") + server.serve_forever() +except KeyboardInterrupt: + print("\nStopped.") diff --git a/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py new file mode 100755 index 0000000000000..4875a00bada6a --- /dev/null +++ b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py @@ -0,0 +1,70 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: LGPL-2.1-or-later + +import argparse, json, os, socket, ssl +from http.server import BaseHTTPRequestHandler, HTTPServer + +def sd_notify(state: str) -> bool: + notify_socket = os.environ.get("NOTIFY_SOCKET") + if not notify_socket: + return False + if notify_socket.startswith("@"): + notify_socket = "\0" + notify_socket[1:] + try: + with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sock: + sock.sendto(state.encode(), notify_socket) + except OSError: + return False + return True + +class Handler(BaseHTTPRequestHandler): + def do_POST(self): + length = int(self.headers.get("Content-Length", 0)) + body = self.rfile.read(length) + + # Check optional attribute + if auth := self.headers.get("Authorization"): + print(f"Authorization: {auth}") + + # Validate JSON structure + try: + data = json.loads(body) + except json.JSONDecodeError: + self.send_error(400, "Invalid JSON") + return + + print(f"JSON: {s if len(s := str(data)) < 80 else s[:40] + '…' + s[-40:]}") + + if "metrics" not in data and "facts" not in data: + self.send_error(400, "Missing 'metrics' or 'facts' field") + return + + response = json.dumps({"status": "ok"}).encode() + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", len(response)) + self.end_headers() + self.wfile.write(response) + + def log_message(self, fmt, *args): + print(f"{self.address_string()} - {fmt % args}") + +parser = argparse.ArgumentParser() +parser.add_argument("--port", type=int, default=8089) +parser.add_argument("--cert", help="TLS certificate file") +parser.add_argument("--key", help="TLS private key file") +args = parser.parse_args() + +server = HTTPServer(("", args.port), Handler) +if args.cert and args.key: + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ctx.load_cert_chain(args.cert, args.key) + server.socket = ctx.wrap_socket(server.socket, server_side=True) + print(f"Serving on https://localhost:{args.port}/") +else: + print(f"Serving on http://localhost:{args.port}/") +try: + sd_notify("READY=1") + server.serve_forever() +except KeyboardInterrupt: + print("\nStopped.") diff --git a/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/proxy-echo.py b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/proxy-echo.py new file mode 100755 index 0000000000000..827ce6af0670f --- /dev/null +++ b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/proxy-echo.py @@ -0,0 +1,18 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: LGPL-2.1-or-later + +import socket +import sys + +data = sys.stdin.buffer.read() +s = socket.create_connection(("localhost", 12345), timeout=15) +s.settimeout(15) +s.sendall(data) +received = b"" +while len(received) < len(data): + chunk = s.recv(65536) + if not chunk: + break + received += chunk +sys.stdout.buffer.write(received) +s.close() diff --git a/test/integration-tests/TEST-75-RESOLVED/meson.build b/test/integration-tests/TEST-75-RESOLVED/meson.build index 8dec5f37e73a8..988aeb8dc9837 100644 --- a/test/integration-tests/TEST-75-RESOLVED/meson.build +++ b/test/integration-tests/TEST-75-RESOLVED/meson.build @@ -3,5 +3,8 @@ integration_tests += [ integration_test_template + { 'name' : fs.name(meson.current_source_dir()), + # knot uses lmdb which uses O_SYNC which will fail with EINVAL + # when running under --suppress-sync. + 'suppress-sync' : false, }, ] diff --git a/test/integration-tests/TEST-79-MEMPRESS/meson.build b/test/integration-tests/TEST-79-PRESSURE/meson.build similarity index 100% rename from test/integration-tests/TEST-79-MEMPRESS/meson.build rename to test/integration-tests/TEST-79-PRESSURE/meson.build diff --git a/test/integration-tests/TEST-85-NETWORK/meson.build b/test/integration-tests/TEST-85-NETWORK/meson.build index 9e8534cff02e4..f708f05067b33 100644 --- a/test/integration-tests/TEST-85-NETWORK/meson.build +++ b/test/integration-tests/TEST-85-NETWORK/meson.build @@ -23,6 +23,7 @@ foreach testcase : [ 'NetworkdIPv6PrefixTests', 'NetworkdMTUTests', 'NetworkdSysctlTest', + 'NetworkdWWANTests', ] integration_tests += [ integration_test_template + { diff --git a/test/integration-tests/TEST-90-CLONESETUP/meson.build b/test/integration-tests/TEST-90-CLONESETUP/meson.build new file mode 100644 index 0000000000000..77370ce4588c4 --- /dev/null +++ b/test/integration-tests/TEST-90-CLONESETUP/meson.build @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +integration_tests += [ + integration_test_template + { + 'name' : fs.name(meson.current_source_dir()), + 'vm' : true, + }, +] diff --git a/test/integration-tests/integration-test-wrapper.py b/test/integration-tests/integration-test-wrapper.py index 0bbfb6044d434..93e1e0d91b54f 100755 --- a/test/integration-tests/integration-test-wrapper.py +++ b/test/integration-tests/integration-test-wrapper.py @@ -395,6 +395,7 @@ def main() -> None: parser.add_argument('--rtc', action=argparse.BooleanOptionalAction) parser.add_argument('--tpm', action=argparse.BooleanOptionalAction) parser.add_argument('--skip', action=argparse.BooleanOptionalAction) + parser.add_argument('--suppress-sync', action=argparse.BooleanOptionalAction, default=False) parser.add_argument('mkosi_args', nargs='*') args = parser.parse_args() @@ -612,7 +613,11 @@ def main() -> None: '--credential', f"journal.storage={'persistent' if sys.stdin.isatty() else args.storage}", *(['--runtime-build-sources=no', '--register=no'] if not sys.stdin.isatty() else []), 'vm' if vm else 'boot', - *(['--', '--capability=CAP_BPF'] if not vm else []), + *( + ['--', '--capability=CAP_BPF', f'--suppress-sync={"yes" if args.suppress_sync else "no"}'] + if not vm + else [] + ), ] # fmt: skip try: diff --git a/test/integration-tests/meson.build b/test/integration-tests/meson.build index 5d71e87c79cbc..1c283a5ce4fde 100644 --- a/test/integration-tests/meson.build +++ b/test/integration-tests/meson.build @@ -31,6 +31,7 @@ integration_test_template = { 'sanitizer-exclude-regex' : '', 'rtc' : false, 'tpm' : false, + 'suppress-sync' : true, } foreach dirname : [ @@ -91,7 +92,7 @@ foreach dirname : [ 'TEST-74-AUX-UTILS', 'TEST-75-RESOLVED', 'TEST-78-SIGQUEUE', - 'TEST-79-MEMPRESS', + 'TEST-79-PRESSURE', 'TEST-80-NOTIFYACCESS', 'TEST-81-GENERATORS', 'TEST-82-SOFTREBOOT', @@ -102,6 +103,7 @@ foreach dirname : [ 'TEST-87-AUX-UTILS-VM', 'TEST-88-UPGRADE', 'TEST-89-RESOLVED-MDNS', + 'TEST-90-CLONESETUP', ] subdir(dirname) endforeach @@ -139,6 +141,10 @@ foreach integration_test : integration_tests integration_test_args += ['--mkosi', mkosi.full_path()] endif + if integration_test['suppress-sync'] + integration_test_args += ['--suppress-sync'] + endif + integration_test_args += ['--'] if integration_test['cmdline'].length() > 0 diff --git a/test/meson.build b/test/meson.build index 7bf557cc19335..b64f971126df6 100644 --- a/test/meson.build +++ b/test/meson.build @@ -357,6 +357,7 @@ if install_tests 'integration-tests/TEST-63-PATH/TEST-63-PATH.units', 'integration-tests/TEST-65-ANALYZE/TEST-65-ANALYZE.units', 'integration-tests/TEST-66-DEVICE-ISOLATION/TEST-66-DEVICE-ISOLATION.units', + 'integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units', 'integration-tests/TEST-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units', 'units', ] diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.container.sysroot/local-fs.target.wants/systemd-remount-fs.service b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.container.sysroot/local-fs.target.wants/systemd-remount-fs.service new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.container/initrd-usr-fs.target.requires/sysroot.mount b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.container/initrd-usr-fs.target.requires/sysroot.mount new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/dev-sdx1.device.d/50-netdev-dependencies.conf b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/dev-sdx1.device.d/50-netdev-dependencies.conf new file mode 100644 index 0000000000000..33d814c275505 --- /dev/null +++ b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/dev-sdx1.device.d/50-netdev-dependencies.conf @@ -0,0 +1,5 @@ +# Automatically generated by systemd-fstab-generator + +[Unit] +After=network-online.target network.target +Wants=network-online.target diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/dev-sdx1.swap b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/dev-sdx1.swap new file mode 100644 index 0000000000000..32f276c9e1c90 --- /dev/null +++ b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/dev-sdx1.swap @@ -0,0 +1,10 @@ +# Automatically generated by systemd-fstab-generator + +[Unit] +Documentation=man:fstab(5) man:systemd-fstab-generator(8) +SourcePath=/etc/fstab +After=blockdev@dev-sdx1.target + +[Swap] +What=/dev/sdx1 +Options=_netdev diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/local-fs.target.wants/systemd-remount-fs.service b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/local-fs.target.wants/systemd-remount-fs.service new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/remote-fs.target.requires/dev-sdx1.swap b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/remote-fs.target.requires/dev-sdx1.swap new file mode 120000 index 0000000000000..00f0c5ce6621d --- /dev/null +++ b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/remote-fs.target.requires/dev-sdx1.swap @@ -0,0 +1 @@ +../dev-sdx1.swap \ No newline at end of file diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/dev-sdx1.device.d/50-netdev-dependencies.conf b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/dev-sdx1.device.d/50-netdev-dependencies.conf new file mode 100644 index 0000000000000..33d814c275505 --- /dev/null +++ b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/dev-sdx1.device.d/50-netdev-dependencies.conf @@ -0,0 +1,5 @@ +# Automatically generated by systemd-fstab-generator + +[Unit] +After=network-online.target network.target +Wants=network-online.target diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/dev-sdx1.swap b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/dev-sdx1.swap new file mode 100644 index 0000000000000..32f276c9e1c90 --- /dev/null +++ b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/dev-sdx1.swap @@ -0,0 +1,10 @@ +# Automatically generated by systemd-fstab-generator + +[Unit] +Documentation=man:fstab(5) man:systemd-fstab-generator(8) +SourcePath=/etc/fstab +After=blockdev@dev-sdx1.target + +[Swap] +What=/dev/sdx1 +Options=_netdev diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/initrd-usr-fs.target.requires/sysroot.mount b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/initrd-usr-fs.target.requires/sysroot.mount new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/remote-fs.target.requires/dev-sdx1.swap b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/remote-fs.target.requires/dev-sdx1.swap new file mode 120000 index 0000000000000..00f0c5ce6621d --- /dev/null +++ b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/remote-fs.target.requires/dev-sdx1.swap @@ -0,0 +1 @@ +../dev-sdx1.swap \ No newline at end of file diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.input b/test/test-fstab-generator/test-21-swap-netdev.fstab.input new file mode 100644 index 0000000000000..5f719a4202813 --- /dev/null +++ b/test/test-fstab-generator/test-21-swap-netdev.fstab.input @@ -0,0 +1 @@ +/dev/sdx1 none swap _netdev 0 0 diff --git a/test/test-journals/corrupted/zstd-truncated-frame.zst b/test/test-journals/corrupted/zstd-truncated-frame.zst new file mode 100644 index 0000000000000..66db974981fd8 Binary files /dev/null and b/test/test-journals/corrupted/zstd-truncated-frame.zst differ diff --git a/test/test-network/conf/25-dhcp-client-simple.network b/test/test-network/conf/25-dhcp-client-simple.network new file mode 100644 index 0000000000000..e98bd2620060e --- /dev/null +++ b/test/test-network/conf/25-dhcp-client-simple.network @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=client + +[Network] +DHCP=ipv4 +IPv6AcceptRA=no diff --git a/test/test-network/conf/25-route-static-issue-40106-dummy.network b/test/test-network/conf/25-route-static-issue-40106-dummy.network new file mode 100644 index 0000000000000..08553b97f5f19 --- /dev/null +++ b/test/test-network/conf/25-route-static-issue-40106-dummy.network @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=dummy99 + +[Network] +ConfigureWithoutCarrier=true +VLAN=vlan99 diff --git a/test/test-network/conf/25-route-static-issue-40106-vlan.netdev b/test/test-network/conf/25-route-static-issue-40106-vlan.netdev new file mode 100644 index 0000000000000..c40e7b755aefa --- /dev/null +++ b/test/test-network/conf/25-route-static-issue-40106-vlan.netdev @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[NetDev] +Kind=vlan +Name=vlan99 + +[VLAN] +Id=99 diff --git a/test/test-network/conf/25-route-static-issue-40106-vlan.network b/test/test-network/conf/25-route-static-issue-40106-vlan.network new file mode 100644 index 0000000000000..442cf480fbc51 --- /dev/null +++ b/test/test-network/conf/25-route-static-issue-40106-vlan.network @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=vlan99 + +[Network] +ConfigureWithoutCarrier=true +BindCarrier=dummy99 + +[Address] +Address=192.0.2.1/24 + +[Route] +Destination=198.51.100.1 +Type=multicast +Scope=link diff --git a/test/test-network/conf/25-routing-policy-rule-test1.network b/test/test-network/conf/25-routing-policy-rule-test1.network index 66ea59d3a99cc..4c83fd5fb78ff 100644 --- a/test/test-network/conf/25-routing-policy-rule-test1.network +++ b/test/test-network/conf/25-routing-policy-rule-test1.network @@ -67,7 +67,7 @@ Priority=202 Table=22 # The four routing policy rules below intentionally have the same config -# excepts for their To= addresses. See issue #35874. +# except for their To= addresses. See issue #35874. [RoutingPolicyRule] To=192.0.2.0/26 Table=1001 diff --git a/test/test-network/conf/25-sysctl.network b/test/test-network/conf/25-sysctl.network index dcc4f0d293a3c..c0c709c32ce79 100644 --- a/test/test-network/conf/25-sysctl.network +++ b/test/test-network/conf/25-sysctl.network @@ -12,5 +12,6 @@ IPv4ProxyARPPrivateVLAN=yes IPv6ProxyNDP=yes IPv6AcceptRA=no IPv4AcceptLocal=yes +IPv4SrcValidMark=yes IPv4ReversePathFilter=no MulticastIGMPVersion=v1 diff --git a/test/test-network/conf/25-wwan-ipv4v6.network b/test/test-network/conf/25-wwan-ipv4v6.network new file mode 100644 index 0000000000000..bf0a857716dc3 --- /dev/null +++ b/test/test-network/conf/25-wwan-ipv4v6.network @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=dummy98 + +[Network] +LLDP=no +LinkLocalAddressing=no +IPv6AcceptRA=no + +[MobileNetwork] +APN=internet.test +IPFamily=both diff --git a/test/test-network/conf/mock-modem-manager.conf b/test/test-network/conf/mock-modem-manager.conf new file mode 100644 index 0000000000000..0a762d7a72728 --- /dev/null +++ b/test/test-network/conf/mock-modem-manager.conf @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index bbbab8e3fe636..0db4830848535 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -73,6 +73,7 @@ timedatectl_bin = shutil.which('timedatectl', path=which_paths) udevadm_bin = shutil.which('udevadm', path=which_paths) test_ndisc_send = None +test_modem_manager_mock = None build_dir = None source_dir = None @@ -869,13 +870,18 @@ class SvcParam(enum.Enum): return data -def start_dnsmasq(*additional_options, interface='veth-peer', ra_mode=None, ipv4_range='192.168.5.10,192.168.5.200', ipv4_router='192.168.5.1', ipv6_range='2600::10,2600::20'): +def start_dnsmasq(*additional_options, namespace=None, interface='veth-peer', ra_mode=None, ipv4_range='192.168.5.10,192.168.5.200', ipv4_router='192.168.5.1', ipv6_range='2600::10,2600::20'): if ra_mode: ra_mode = f',{ra_mode}' else: ra_mode = '' - command = ( + if namespace: + ns_command = ('ip', 'netns', 'exec', namespace) + else: + ns_command = () + + command = ns_command + ( 'dnsmasq', f'--log-facility={dnsmasq_log_file}', '--log-queries=extra', @@ -925,6 +931,13 @@ def get_dbus_link_path(link): out = out.decode() return out[:-1].split('"')[1] +def get_dhcp_server_property(link, prop): + link_path = get_dbus_link_path(link) + + out = subprocess.check_output(['busctl', 'get-property', 'org.freedesktop.network1', + link_path, 'org.freedesktop.network1.DHCPServer', prop]) + return out.strip().decode() + def get_dhcp_client_state(link, family): link_path = get_dbus_link_path(link) @@ -968,6 +981,28 @@ def start_radvd(*additional_options, config_file): def stop_radvd(): stop_by_pid_file(radvd_pid_file) +def start_modem_manager_mock(*additional_options): + dbus_policy_src = os.path.join(networkd_ci_temp_dir, 'mock-modem-manager.conf') + cp(dbus_policy_src, '/etc/dbus-1/system.d/mock-modem-manager.conf') + check_output('systemctl reload dbus.service') + + command = ' '.join([test_modem_manager_mock] + list(additional_options)) + with open('/run/systemd/system/test-modem-manager-mock.service', mode='w', encoding='utf-8') as f: + f.write('[Unit]\n' + 'Description=Mock ModemManager for networkd testing\n' + '[Service]\n' + 'Type=notify\n' + 'BusName=org.freedesktop.ModemManager1\n' + f'ExecStart={command}\n') + check_output('systemctl daemon-reload') + check_output('systemctl start test-modem-manager-mock.service') + +def stop_modem_manager_mock(): + call('systemctl stop test-modem-manager-mock.service') + rm_f('/run/systemd/system/test-modem-manager-mock.service') + call('systemctl daemon-reload') + rm_f('/etc/dbus-1/system.d/mock-modem-manager.conf') + def radvd_check_config(config_file): if not shutil.which('radvd'): print('radvd is not installed, assuming the config check failed') @@ -982,7 +1017,7 @@ def networkd_invocation_id(): return check_output('systemctl show --value -p InvocationID systemd-networkd.service') def networkd_pid(): - return check_output('systemctl show --value -p MainPID systemd-networkd.service') + return int(check_output('systemctl show --value -p MainPID systemd-networkd.service')) def read_networkd_log(invocation_id=None, since=None): if not invocation_id: @@ -1046,9 +1081,6 @@ def restart_networkd(show_logs=True): pid = networkd_pid() print(f'Restarted systemd-networkd.service: PID={pid}, Invocation ID={invocation_id}') -def networkd_pid(): - return int(check_output('systemctl show --value -p MainPID systemd-networkd.service')) - def networkctl(*args): # Do not call networkctl if networkd is in failed state. # Otherwise, networkd may be restarted and we may get wrong results. @@ -1097,6 +1129,7 @@ def tear_down_common(): stop_dnsmasq() stop_isc_dhcpd() stop_radvd() + stop_modem_manager_mock() # 2. remove modules call_quiet('rmmod netdevsim') @@ -1104,6 +1137,8 @@ def tear_down_common(): # 3. remove network namespace call_quiet('ip netns del ns99') + call_quiet('ip netns del ns-bridge') + call_quiet('ip netns del ns-server') # 4. remove links flush_l2tp_tunnels() @@ -4603,6 +4638,47 @@ def test_route_via_ipv6(self): self.assertRegex(output, '149.10.124.48/28 proto kernel scope link src 149.10.124.58') self.assertRegex(output, '149.10.124.66 via inet6 2001:1234:5:8fff:ff:ff:ff:ff proto static') + def test_route_static_issue_40106(self): + check_output('ip link add dummy99 type dummy') + check_output('ip link set dummy99 up carrier off') + copy_network_unit( + '25-route-static-issue-40106-dummy.network', + '25-route-static-issue-40106-vlan.netdev', + '25-route-static-issue-40106-vlan.network', + ) + start_networkd() + self.wait_online('dummy99:no-carrier') + self.wait_operstate('vlan99', operstate='off', setup_state='configuring') + + # address can be configured even when the interface is down. + self.wait_address('vlan99', '192.0.2.1/24', ipv='-4', timeout_sec=10) + print('### ip -4 address show dev vlan99') + output = check_output('ip -4 address show dev vlan99') + print(output) + self.assertIn('inet 192.0.2.1/24 brd 192.0.2.255 scope global vlan99', output) + + # route cannot be configured when the interface is down. + print('### ip -4 route show dev vlan99') + output = check_output('ip -4 route show dev vlan99') + print(output) + self.assertEqual(output, '') + + # When cable is connected, the vlan becomes up by BindCarrier=, then + # the pending route is also configured. + check_output('ip link set dummy99 carrier on') + self.wait_online('dummy99:degraded', 'vlan99:routable') + + print('### ip -4 address show dev vlan99') + output = check_output('ip -4 address show dev vlan99') + print(output) + self.assertIn('inet 192.0.2.1/24 brd 192.0.2.255 scope global vlan99', output) + + print('### ip -4 route show dev vlan99') + output = check_output('ip -4 route show dev vlan99') + print(output) + self.assertIn('192.0.2.0/24 proto kernel scope link src 192.0.2.1', output) + self.assertIn('multicast 198.51.100.1 proto static scope link', output) + @expectedFailureIfModuleIsNotAvailable('tcp_dctcp') def test_route_congctl(self): copy_network_unit('25-route-congctl.network', '12-dummy.netdev') @@ -4927,6 +5003,7 @@ def test_sysctl(self): self.check_ipv4_sysctl_attr('dummy98', 'proxy_arp', '1') self.check_ipv4_sysctl_attr('dummy98', 'proxy_arp_pvlan', '1') self.check_ipv4_sysctl_attr('dummy98', 'accept_local', '1') + self.check_ipv4_sysctl_attr('dummy98', 'src_valid_mark', '1') self.check_ipv4_sysctl_attr('dummy98', 'rp_filter', '0') self.check_ipv4_sysctl_attr('dummy98', 'force_igmp_version', '1') @@ -7224,6 +7301,9 @@ def check_dhcp_server(self, persist_leases='yes'): print(output) self.assertRegex(output, "Offered DHCP leases: 192.168.5.[0-9]*") + self.assertEqual(get_dhcp_server_property('veth-peer', 'PoolSize'), 'u 50') + self.assertEqual(get_dhcp_server_property('veth-peer', 'PoolOffset'), 'u 10') + if persist_leases == 'yes': path = '/var/lib/systemd/network/dhcp-server-lease/veth-peer' elif persist_leases == 'runtime': @@ -7947,6 +8027,105 @@ def test_dhcp_client_ipv4_only(self): self.teardown_nftset('addr4', 'network4', 'ifindex') + def _test_dhcp_client_send_release_one(self, stop=True) -> bool: + start_dnsmasq( + namespace='ns-server', + interface='server', + ipv4_range='192.0.2.100,192.0.2.109', + ipv4_router='192.0.2.1', + ) + + start_networkd() + self.wait_online('client:routable') + + print('## ip -4 address show dev client scope global') + output = check_output('ip -4 address show dev client scope global') + print(output) + self.assertRegex(output, r'192.0.2.10[0-9]/24') + + print('## ip -4 route show dev client') + output = check_output('ip -4 route show dev client') + print(output) + self.assertRegex(output, r'default via 192.0.2.1 proto dhcp src 192.0.2.10[0-9]') + self.assertRegex(output, r'192.0.2.0/24 proto kernel scope link src 192.0.2.10[0-9]') + self.assertRegex(output, r'192.0.2.1 proto dhcp scope link src 192.0.2.10[0-9]') + + if stop: + stop_networkd() + else: + networkctl('down', 'client') + + success = False + for _ in range(20): + time.sleep(0.5) + try: + output = read_dnsmasq_log_file() + except FileNotFoundError: + output = "" + if 'DHCPRELEASE' in output: + success = True + break + + print('## dnsmasq log') + print(output) + + return success + + def test_dhcp_client_send_release(self): + check_output('ip netns add ns-bridge') + check_output('ip netns exec ns-bridge ip link add bridge99 type bridge') + check_output('ip netns exec ns-bridge ip link set bridge99 address 12:34:56:78:90:ab') + check_output('ip netns exec ns-bridge ip link set bridge99 up') + + check_output('ip link add client type veth peer clientp') + check_output('ip link set clientp netns ns-bridge') + check_output('ip netns exec ns-bridge ip link set clientp master bridge99') + check_output('ip netns exec ns-bridge ip link set clientp up') + + check_output('ip link add server type veth peer serverp') + check_output('ip link set serverp netns ns-bridge') + check_output('ip netns exec ns-bridge ip link set serverp master bridge99') + check_output('ip netns exec ns-bridge ip link set serverp up') + + check_output('ip netns add ns-server') + check_output('ip link set server netns ns-server') + check_output('ip netns exec ns-server ip link set server up') + check_output('ip netns exec ns-server ip address add 192.0.2.1/24 dev server') + + copy_network_unit('25-dhcp-client-simple.network') + + ''' + Sending DHCPRELEASE is best-effort. Even if send() succeeds, the packet may be dropped later in the + networking stack (e.g. due to unresolved neighbor state or interface teardown). Userspace cannot + reliably determine whether the packet was eventually transmitted or dropped. + + Hence, the test below may be flaky. In most cases, neighbor resolution completes quickly enough and + the packet is transmitted before the interface is brought down. Running the test multiple times + should make it sufficiently reliable. + ''' + + first = True + for _ in range(5): + if not first: + stop_dnsmasq() + stop_networkd(show_logs=False) + + first = False + + if self._test_dhcp_client_send_release_one(): + break + else: + self.fail('Timed out waiting for DHCPRELEASE in dnsmasq log (on stopping networkd)') + + for _ in range(5): + stop_dnsmasq() + stop_networkd(show_logs=False) + + if self._test_dhcp_client_send_release_one(stop=False): + break + else: + self.fail('Timed out waiting for DHCPRELEASE in dnsmasq log (on bringing down interface)') + def test_dhcp_client_ipv4_dbus_status(self): copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv4-only.network') start_networkd() @@ -9423,6 +9602,54 @@ def test_sysctl_monitor(self): self.assertNotIn("changed sysctl '/proc/sys/net/ipv6/conf/dummy98/max_addresses'", log) self.assertNotIn("Sysctl monitor BPF returned error", log) +class NetworkdWWANTests(unittest.TestCase, Utilities): + + def setUp(self): + setup_common() + + def tearDown(self): + tear_down_common() + + def test_wwan_ipv4v6_static(self): + """Test WWAN bearer with both IPv4 and IPv6 static configuration. + + Regression test for https://github.com/systemd/systemd/issues/41389 + """ + if not os.path.exists(test_modem_manager_mock): + self.skipTest(f'{test_modem_manager_mock} does not exist.') + + copy_network_unit('12-dummy.netdev', '25-wwan-ipv4v6.network') + try: + start_modem_manager_mock( + '--ifname', 'dummy98', + '--ipv4-address', '100.120.244.160', + '--ipv4-gateway', '100.120.244.161', + '--ipv4-prefix', '26', + '--ipv6-address', '2001:db8::1', + '--ipv6-gateway', '2001:db8::2', + '--ipv6-prefix', '64', + ) + except (subprocess.CalledProcessError, PermissionError, OSError) as e: + self.skipTest(f'Failed to start mock ModemManager: {e}') + start_networkd() + self.wait_online('dummy98:routable') + + output = check_output('ip -4 address show dev dummy98') + print(output) + self.assertIn('100.120.244.160/26', output) + + output = check_output('ip -6 address show dev dummy98') + print(output) + self.assertIn('2001:db8::1/64', output) + + output = check_output('ip -4 route show dev dummy98') + print(output) + self.assertIn('default via 100.120.244.161', output) + + output = check_output('ip -6 route show dev dummy98') + print(output) + self.assertIn('default via 2001:db8::2', output) + if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--build-dir', help='Path to build dir', dest='build_dir') @@ -9482,6 +9709,11 @@ def test_sysctl_monitor(self): else: test_ndisc_send = '/usr/lib/tests/test-ndisc-send' + if build_dir: + test_modem_manager_mock = os.path.normpath(os.path.join(build_dir, 'test-modem-manager-mock')) + else: + test_modem_manager_mock = '/usr/lib/systemd/tests/unit-tests/manual/test-modem-manager-mock' + if asan_options: env.update({'ASAN_OPTIONS': asan_options}) if lsan_options: diff --git a/test/units/TEST-02-UNITTESTS.sh b/test/units/TEST-02-UNITTESTS.sh index ee5eab08d566d..2a38062a41046 100755 --- a/test/units/TEST-02-UNITTESTS.sh +++ b/test/units/TEST-02-UNITTESTS.sh @@ -24,7 +24,7 @@ if [[ -z "${TEST_MATCH_SUBTEST:-}" ]]; then # in QEMU to only those that can't run in a container to avoid running # the same tests again in a, most likely, very slow environment if ! systemd-detect-virt -qc && [[ "${TEST_PREFER_NSPAWN:-0}" -ne 0 ]]; then - TEST_MATCH_SUBTEST="test-loop-block" + TEST_MATCH_SUBTEST="test-loop-util" else TEST_MATCH_SUBTEST="test-*" fi diff --git a/test/units/TEST-04-JOURNAL.SYSTEMD_JOURNAL_COMPRESS.sh b/test/units/TEST-04-JOURNAL.SYSTEMD_JOURNAL_COMPRESS.sh index 13ca3751cb35d..97782f9634806 100755 --- a/test/units/TEST-04-JOURNAL.SYSTEMD_JOURNAL_COMPRESS.sh +++ b/test/units/TEST-04-JOURNAL.SYSTEMD_JOURNAL_COMPRESS.sh @@ -17,6 +17,13 @@ EOF systemctl reset-failed systemd-journald.service for c in NONE XZ LZ4 ZSTD; do + # compression_to_string() returns "uncompressed" for COMPRESSION_NONE + if [[ "${c}" == NONE ]]; then + log_name="uncompressed" + else + log_name="${c,,}" + fi + cat >/run/systemd/system/systemd-journald.service.d/compress.conf <&1 | grep -F 'compress=${c}' >/dev/null; do sleep .5; done" + timeout 10 bash -c "until SYSTEMD_LOG_LEVEL=debug journalctl --verify --quiet --file /var/log/journal/$MACHINE_ID/system.journal 2>&1 | grep -F 'compress=${log_name}' >/dev/null; do sleep .5; done" # $SYSTEMD_JOURNAL_COMPRESS= also works for journal-remote if [[ -x /usr/lib/systemd/systemd-journal-remote ]]; then for cc in NONE XZ LZ4 ZSTD; do + if [[ "${cc}" == NONE ]]; then + cc_log_name="uncompressed" + else + cc_log_name="${cc,,}" + fi + rm -f /tmp/foo.journal SYSTEMD_JOURNAL_COMPRESS="${cc}" /usr/lib/systemd/systemd-journal-remote --split-mode=none -o /tmp/foo.journal --getter="journalctl -b -o export -t $ID" - SYSTEMD_LOG_LEVEL=debug journalctl --verify --quiet --file /tmp/foo.journal 2>&1 | grep -F "compress=${cc}" >/dev/null + SYSTEMD_LOG_LEVEL=debug journalctl --verify --quiet --file /tmp/foo.journal 2>&1 | grep -F "compress=${cc_log_name}" >/dev/null journalctl -t "$ID" -o cat --file /tmp/foo.journal | grep -F "hoge with ${c}" >/dev/null done fi diff --git a/test/units/TEST-04-JOURNAL.corrupted-journals.sh b/test/units/TEST-04-JOURNAL.corrupted-journals.sh index 479011ea78f43..0a8d7e030e86c 100755 --- a/test/units/TEST-04-JOURNAL.corrupted-journals.sh +++ b/test/units/TEST-04-JOURNAL.corrupted-journals.sh @@ -9,7 +9,7 @@ REMOTE_OUT="$(mktemp -d)" unzstd --stdout "/usr/lib/systemd/tests/testdata/test-journals/afl-corrupted-journals.tar.zst" | tar -xC "$JOURNAL_DIR/" while read -r file; do filename="${file##*/}" - unzstd "$file" -o "$JOURNAL_DIR/${filename%*.zst}" + unzstd "$file" -o "$JOURNAL_DIR/${filename%.zst}.journal" done < <(find /usr/lib/systemd/tests/testdata/test-journals/corrupted/ -name "*.zst") # First, try each of them sequentially. Skip this part when running with plain # QEMU, as it is excruciatingly slow @@ -20,6 +20,7 @@ if [[ "$(systemd-detect-virt -v)" != "qemu" ]]; then timeout 10 journalctl --file="$file" --boot >/dev/null || [[ $? -lt 124 ]] timeout 10 journalctl --file="$file" --verify >/dev/null || [[ $? -lt 124 ]] timeout 10 journalctl --file="$file" --output=export >/dev/null || [[ $? -lt 124 ]] + timeout 10 journalctl --file="$file" --grep=. >/dev/null || [[ $? -lt 124 ]] timeout 10 journalctl --file="$file" --fields >/dev/null || [[ $? -lt 124 ]] timeout 10 journalctl --file="$file" --list-boots >/dev/null || [[ $? -lt 124 ]] if [[ -x /usr/lib/systemd/systemd-journal-remote ]]; then @@ -36,6 +37,7 @@ fi timeout 30 journalctl --directory="$JOURNAL_DIR" --boot >/dev/null || [[ $? -lt 124 ]] timeout 30 journalctl --directory="$JOURNAL_DIR" --verify >/dev/null || [[ $? -lt 124 ]] timeout 30 journalctl --directory="$JOURNAL_DIR" --output=export >/dev/null || [[ $? -lt 124 ]] +timeout 30 journalctl --directory="$JOURNAL_DIR" --grep=. >/dev/null || [[ $? -lt 124 ]] timeout 30 journalctl --directory="$JOURNAL_DIR" --fields >/dev/null || [[ $? -lt 124 ]] timeout 30 journalctl --directory="$JOURNAL_DIR" --list-boots >/dev/null || [[ $? -lt 124 ]] if [[ -x /usr/lib/systemd/systemd-journal-remote ]]; then diff --git a/test/units/TEST-07-PID1.alias-corruption.sh b/test/units/TEST-07-PID1.alias-corruption.sh new file mode 100755 index 0000000000000..e2fc00d410f85 --- /dev/null +++ b/test/units/TEST-07-PID1.alias-corruption.sh @@ -0,0 +1,229 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Verify that stale alias state doesn't overwrite canonical unit state. +# 1. Legit unit is running (PID A). +# 2. Sus units are running (PID B, C, D...). +# 3. We alias sus -> legit. +# 4. If the bug triggers, legit unit's state is overwritten by a sus unit's state. +# 5. Legit unit thinks it is now PID B (or C, or D...). +# 6. We detect this PID change as proof of corruption. + +declare -a abandoned_pids=() + +reap_abandoned_pids() { + local pid attempt + + if (( ${#abandoned_pids[@]} == 0 )); then + return 0 + fi + + echo "Reaping ${#abandoned_pids[@]} abandoned processes..." + + for pid in "${abandoned_pids[@]}"; do + kill "$pid" 2>/dev/null || true + done + + for pid in "${abandoned_pids[@]}"; do + for attempt in $(seq 1 50); do + if ! kill -0 "$pid" 2>/dev/null; then + break + fi + + sleep 0.1 + done + + if kill -0 "$pid" 2>/dev/null; then + kill -KILL "$pid" 2>/dev/null || true + fi + + for attempt in $(seq 1 50); do + if ! kill -0 "$pid" 2>/dev/null; then + break + fi + + sleep 0.1 + done + + if kill -0 "$pid" 2>/dev/null; then + echo "ERROR: Failed to reap abandoned process PID $pid" + return 1 + fi + done + + abandoned_pids=() +} + +run_test() { + local reload_cmd="${1:?}" + local current_pid journal_warnings new_pid orig_pid pid reload_start unit warning_count + + echo "" + echo "=========================================" + echo "Testing with: systemctl $reload_cmd" + echo "=========================================" + + cat >/run/systemd/system/legit.service <<'EOF' +[Service] +Type=simple +ExecStart=/bin/sleep infinity +EOF + + # Create 20 sus units. They must be Type=simple/running so systemd + # CANNOT garbage collect them. If they are dead/stopped, systemd can remove + # them from memory before serialization + echo "Creating 20 sus units..." + for i in $(seq -f "%02g" 1 20); do + cat >/run/systemd/system/sus-"${i}".service <<'EOF' +[Service] +Type=simple +ExecStart=/bin/sleep infinity +EOF + done + + systemctl daemon-reload + + echo "Starting legit unit..." + systemctl start legit.service + + echo "Starting sus units..." + for i in $(seq -f "%02g" 1 20); do + systemctl start sus-"${i}".service + done + + echo "Setup complete: 1 running legit unit, 20 running sus units" + + orig_pid=$(systemctl show -P MainPID legit.service) + echo "Original legit PID: $orig_pid" + + if (( orig_pid == 0 )); then + echo "Error: Legit PID is 0, setup failed." + return 1 + fi + + # Since ordering is not deterministic we should loop 3 times to reduce + # false negative rate (ordering luck). With this it's roughly 0.01% chance + # of falsely passing. Falsely failing does not happen, though. + for attempt in 1 2 3; do + echo "" + echo "--- Attempt $attempt/3 ---" + + unset sus_pids + declare -A sus_pids + for i in $(seq -f "%02g" 1 20); do + pid=$(systemctl show -P MainPID sus-"${i}".service) + if (( pid != 0 )); then + sus_pids["sus-${i}"]=$pid + abandoned_pids+=("$pid") + echo "sus-${i}.service PID: $pid" + fi + done + + echo "Converting sus units to symlinks -> legit.service..." + for i in $(seq -f "%02g" 1 20); do + rm -f /run/systemd/system/sus-"${i}".service + ln -sf /run/systemd/system/legit.service /run/systemd/system/sus-"${i}".service + done + + reload_start=$(date '+%Y-%m-%d %H:%M:%S') + + echo "Running $reload_cmd..." + systemctl "$reload_cmd" + + # If the bug triggered, legit.service deserialized a sus unit's state + # and overwrote its own MainPID with the sus unit's PID. + new_pid=$(systemctl show -P MainPID legit.service) + + if [[ "$new_pid" != "$orig_pid" ]]; then + echo "legit.service PID changed from $orig_pid to $new_pid!" + echo "The stale alias state corrupted the canonical unit." + return 1 + fi + + echo "legit.service PID remains $new_pid. Attempt $attempt passed." + + # Verify that all sus unit processes were abandoned (still running but no longer tracked) + echo "Verifying sus unit processes were abandoned..." + for unit in "${!sus_pids[@]}"; do + pid=${sus_pids[$unit]} + # Process should still be running + if ! kill -0 "$pid" 2>/dev/null; then + echo "ERROR: $unit process (PID $pid) was killed instead of abandoned!" + return 1 + fi + # But the alias should now either be inactive (MainPID=0) or resolve to legit's PID. + current_pid=$(systemctl show -P MainPID "${unit}.service") + if ! (( current_pid == 0 || current_pid == new_pid )); then + echo "ERROR: $unit unexpectedly reports MainPID=$current_pid after aliasing!" + return 1 + fi + echo "$unit process (PID $pid) was correctly abandoned (still running, no longer tracked)" + done + + # Check consistency between journal warnings and abandoned processes + echo "Checking journal for stale state warnings..." + journal_warnings=$(journalctl --since "$reload_start" --no-pager | grep "Skipping stale state" || true) + warning_count=$(echo "$journal_warnings" | grep -c "Skipping stale state" || true) + + echo "Found $warning_count 'Skipping stale state' warnings" + + # Extract unit names from warnings and verify they match our sus units + if (( warning_count > 0 )); then + echo "Verifying warning consistency..." + for unit in "${!sus_pids[@]}"; do + if [[ "$journal_warnings" != *"${unit}.service"* ]]; then + echo "WARNING: Expected journal warning for ${unit}.service but didn't find it" + fi + done + fi + + reap_abandoned_pids + + if (( attempt < 3 )); then + echo "Resetting sus units..." + + # We must fully reset to get independent running units again + for i in $(seq -f "%02g" 1 20); do + rm -f /run/systemd/system/sus-"${i}".service + cat >/run/systemd/system/sus-"${i}".service <<'EOF' +[Service] +Type=simple +ExecStart=/bin/sleep infinity +EOF + done + + systemctl "$reload_cmd" + + # Ensure they are running again (they might have been + # abandoned/killed during the transition) + for i in $(seq -f "%02g" 1 20); do + systemctl start sus-"${i}".service + done + + echo "Reset complete." + fi + done + + echo "legit.service did not become sus through all 3 $reload_cmd cycles" + + echo "$reload_cmd test passed" +} + +cleanup_test_units() { + reap_abandoned_pids || true + systemctl stop legit.service 2>/dev/null || true + for i in $(seq -f "%02g" 1 20); do + systemctl stop sus-"${i}".service 2>/dev/null || true + rm -f /run/systemd/system/sus-"${i}".service + done + rm -f /run/systemd/system/legit.service + systemctl daemon-reload +} + +trap cleanup_test_units EXIT + +run_test daemon-reload +cleanup_test_units +run_test daemon-reexec diff --git a/test/units/TEST-07-PID1.alias-rename.sh b/test/units/TEST-07-PID1.alias-rename.sh new file mode 100755 index 0000000000000..c33408914ce82 --- /dev/null +++ b/test/units/TEST-07-PID1.alias-rename.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +run_test() { + local reload_cmd="${1:?}" + local orig_pid new_pid + + echo "" + echo "=========================================" + echo "Testing rename preservation with: systemctl $reload_cmd" + echo "=========================================" + + cat >/run/systemd/system/rename.service <<'EOF' +[Service] +Type=simple +ExecStart=/bin/sleep infinity +EOF + + systemctl daemon-reload + systemctl start rename.service + + orig_pid=$(systemctl show -P MainPID rename.service) + (( orig_pid != 0 )) + + # The old name becomes an alias to the new canonical unit... + rm -f /run/systemd/system/rename.service + cat >/run/systemd/system/the-unit-formerly-known-as-rename.service <<'EOF' +[Service] +Type=simple +ExecStart=/bin/sleep infinity +EOF + ln -sf /run/systemd/system/the-unit-formerly-known-as-rename.service /run/systemd/system/rename.service + + systemctl "$reload_cmd" + + # ...and the running service must stay tracked across the rename. + new_pid=$(systemctl show -P MainPID the-unit-formerly-known-as-rename.service) + (( new_pid == orig_pid )) + (( $(systemctl show -P MainPID rename.service) == orig_pid )) + [[ "$(systemctl show -P ActiveState the-unit-formerly-known-as-rename.service)" == active ]] + [[ "$(systemctl show -P ActiveState rename.service)" == active ]] +} + +cleanup_test_units() { + systemctl stop the-unit-formerly-known-as-rename.service 2>/dev/null || true + systemctl stop rename.service 2>/dev/null || true + rm -f /run/systemd/system/rename.service + rm -f /run/systemd/system/the-unit-formerly-known-as-rename.service + systemctl daemon-reload +} + +trap cleanup_test_units EXIT + +run_test daemon-reload +cleanup_test_units +run_test daemon-reexec diff --git a/test/units/TEST-07-PID1.exec-context.sh b/test/units/TEST-07-PID1.exec-context.sh index 877095609123f..14cc49f29237f 100755 --- a/test/units/TEST-07-PID1.exec-context.sh +++ b/test/units/TEST-07-PID1.exec-context.sh @@ -33,12 +33,14 @@ proc_supports_option() { # in that case instead of complicating the test setup even more */ if [[ -z "${COVERAGE_BUILD_DIR:-}" ]]; then if ! systemd-detect-virt -cq && command -v bootctl >/dev/null; then - boot_path="$(bootctl --print-boot-path)" - esp_path="$(bootctl --print-esp-path)" + boot_path="$(bootctl --print-boot-path)" || : + esp_path="$(bootctl --print-esp-path)" || : # If the mount points are handled by automount units, make sure we trigger # them before proceeding further - ls -l "$boot_path" "$esp_path" + if [[ -n "${boot_path:-}" && -n "${esp_path:-}" ]]; then + ls -l "$boot_path" "$esp_path" + fi fi systemd-run --wait --pipe -p ProtectSystem=yes \ diff --git a/test/units/TEST-07-PID1.issue-40916.sh b/test/units/TEST-07-PID1.issue-40916.sh new file mode 100755 index 0000000000000..c3f6dd6f59ad8 --- /dev/null +++ b/test/units/TEST-07-PID1.issue-40916.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later + +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# Units with excessive numbers of fields in LogExtraFields=. +# Issue: https://github.com/systemd/systemd/issues/40916 + +UNIT=test-07-pid1-issue-40916.service + +cleanup() { + rm -f /run/systemd/system/"$UNIT" + systemctl daemon-reload +} + +trap cleanup EXIT + +cat >/run/systemd/system/"$UNIT" <>/run/systemd/system/"$UNIT" + +systemctl start --wait "$UNIT" + +systemctl show -p LogExtraFields "$UNIT" | grep FIELD_1000 +(! systemctl show -p LogExtraFields "$UNIT" | grep FIELD_1500) + +# Now try setting the properties dynamically +(! systemd-run --wait -u test-07-pid1-issue-40916-1.service -pLogExtraFields=FD{1..2000}=1 true) +systemd-run --wait -u test-07-pid1-issue-40916-1.service -pLogExtraFields=FD{1..1000}=1 true diff --git a/test/units/TEST-07-PID1.subgroup-kill.sh b/test/units/TEST-07-PID1.subgroup-kill.sh index c8eb6539abbd8..274e495af45f4 100755 --- a/test/units/TEST-07-PID1.subgroup-kill.sh +++ b/test/units/TEST-07-PID1.subgroup-kill.sh @@ -36,4 +36,4 @@ systemctl kill user@"$(id -u testuser)".service --kill-subgroup=app.slice/subgro run0 -u testuser systemctl --user is-active subgroup-test.service systemctl kill user@"$(id -u testuser)".service --kill-subgroup=app.slice/subgroup-test.service --kill-whom=cgroup-fail -timeout 60 bash -c 'while run0 -u testuser systemctl --user is-active subgroup-test.service; do sleep 1; done' +timeout --foreground 60 bash -c 'while run0 -u testuser systemctl --user is-active subgroup-test.service; do sleep 1; done' diff --git a/test/units/TEST-13-NSPAWN.machined.sh b/test/units/TEST-13-NSPAWN.machined.sh index 34307f3c8b162..de51daa24c73d 100755 --- a/test/units/TEST-13-NSPAWN.machined.sh +++ b/test/units/TEST-13-NSPAWN.machined.sh @@ -44,21 +44,27 @@ set -x PID=0 -trap 'touch /terminate; kill 0' RTMIN+3 -trap 'touch /poweroff' RTMIN+4 -trap 'touch /reboot' INT -trap 'touch /trap' TRAP +# Use only builtins in trap handlers to avoid forking. External commands +# (like touch) cause bash to enter wait_for() for the child, and a nested +# signal arriving during that wait triggers a bash bug where +# run_interrupt_trap() clears catch_flag while other traps are still +# pending, creating an orphaned pending_traps[] entry that makes 'wait' +# busy-loop indefinitely. +trap ': >/terminate; kill 0' RTMIN+3 +trap ': >/poweroff' RTMIN+4 +trap ': >/reboot' INT +trap ': >/trap' TRAP trap 'exit 0' TERM trap 'kill $PID' EXIT # We need to wait for the sleep process asynchronously in order to allow # bash to process signals sleep infinity & +PID=$! # notify that the process is ready -touch /ready +: >/ready -PID=$! while :; do wait || : done @@ -332,11 +338,11 @@ trap 'kill $PID' EXIT # We need to wait for the sleep process asynchronously in order to allow # bash to process signals sleep infinity & +PID=$! # notify that the process is ready -touch /ready +: >/ready -PID=$! while :; do wait || : done diff --git a/test/units/TEST-13-NSPAWN.nspawn-oci.sh b/test/units/TEST-13-NSPAWN.nspawn-oci.sh index 1fd2a5ad76774..0d85518a1d392 100755 --- a/test/units/TEST-13-NSPAWN.nspawn-oci.sh +++ b/test/units/TEST-13-NSPAWN.nspawn-oci.sh @@ -395,7 +395,7 @@ touch /opt/readonly/foo && exit 1 exit 0 EOF -timeout 30 systemd-nspawn --oci-bundle="$OCI" +timeout --foreground 30 systemd-nspawn --oci-bundle="$OCI" # Test a couple of invalid configs INVALID_SNIPPETS=( diff --git a/test/units/TEST-13-NSPAWN.nspawn.sh b/test/units/TEST-13-NSPAWN.nspawn.sh index c2fa9eaaf8940..47c19f08c01f2 100755 --- a/test/units/TEST-13-NSPAWN.nspawn.sh +++ b/test/units/TEST-13-NSPAWN.nspawn.sh @@ -202,7 +202,7 @@ testcase_sanity() { systemd-nspawn --register=no --directory="$root" bash -xec '[[ $$ -eq 1 ]]' systemd-nspawn --register=no --directory="$root" --as-pid2 bash -xec '[[ $$ -eq 2 ]]' - # --user= + # --uid= # "Fake" getent passwd's bare minimum, so we don't have to pull it in # with all the DSO shenanigans cat >"$root/bin/getent" <<\EOF @@ -222,6 +222,9 @@ EOF # as bash isn't invoked with the necessary environment variables for that. useradd --root="$root" --uid 1000 --user-group --create-home testuser systemd-nspawn --register=no --directory="$root" bash -xec '[[ $USER == root ]]' + systemd-nspawn --register=no --directory="$root" --uid=testuser bash -xec '[[ $USER == testuser ]]' + # Backward compat: --user NAME (space-separated) and --user=testuser should still work + systemd-nspawn --register=no --directory="$root" --user testuser bash -xec '[[ $USER == testuser ]]' systemd-nspawn --register=no --directory="$root" --user=testuser bash -xec '[[ $USER == testuser ]]' # --settings= + .nspawn files @@ -335,10 +338,10 @@ EOF --load-credential=cred.path:/tmp/cred.path \ --set-credential="cred.set:hello world" \ bash -xec '[[ "$("$root/bin/getent" <<\EOF @@ -893,7 +936,7 @@ EOF systemd-nspawn --register=no \ --directory="$root" \ -U \ - --user=testuser \ + --uid=testuser \ --bind=/tmp/owneridmap/bind:/home/testuser:owneridmap \ ${COVERAGE_BUILD_DIR:+--bind="$COVERAGE_BUILD_DIR"} \ bash -c "$cmd" |& tee nspawn.out; then diff --git a/test/units/TEST-13-NSPAWN.unpriv.sh b/test/units/TEST-13-NSPAWN.unpriv.sh index cbf332aa22093..e0516449c70b0 100755 --- a/test/units/TEST-13-NSPAWN.unpriv.sh +++ b/test/units/TEST-13-NSPAWN.unpriv.sh @@ -23,6 +23,8 @@ at_exit() { rm -rf /home/testuser/.local/state/machines/inodetest2 ||: rm -rf /home/testuser/.local/state/machines/mangletest ||: machinectl terminate zurps ||: + machinectl terminate exfiltrate ||: + systemctl --user --machine testuser@ stop exfiltrate.service ||: rm -f /etc/polkit-1/rules.d/registermachinetest.rules machinectl terminate nurps ||: machinectl terminate kurps ||: @@ -31,6 +33,7 @@ at_exit() { rm -f /usr/share/polkit-1/rules.d/registermachinetest.rules rm -rf /var/tmp/mangletest rm -f /var/tmp/mangletest.tar.gz + rm -f /shouldnotwork } trap at_exit EXIT @@ -104,6 +107,86 @@ run0 -u testuser \ (! run0 -u testuser machinectl shell 0@shouldnotwork2 /usr/bin/id -u) (! run0 -u testuser machinectl shell testuser@shouldnotwork2 /usr/bin/id -u) +run0 -u testuser \ + systemd-run --unit sleep.service --user sleep infinity +sleep_pid="$(run0 -u testuser systemctl show --user -P MainPID sleep.service)" +run0 -u testuser \ + varlinkctl \ + call \ + /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.Register \ + "{\"name\":\"shouldnotwork3\", \"class\":\"container\", \"leader\": $sleep_pid}" +(! run0 -u testuser \ + varlinkctl \ + call \ + /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.Open \ + '{"name":"shouldnotwork3", "mode": "shell", "user":"root","path":"/usr/bin/bash","args":["bash","-c","''touch /shouldnotwork; sleep 20''"]}') +(! varlinkctl \ + call \ + /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.Register \ + "{\"name\":\"shouldnotwork4\", \"class\":\"host\", \"leader\": $sleep_pid}") +(! machinectl list | grep shouldnotwork4) +(! run0 -u testuser \ + varlinkctl \ + call \ + /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.Register \ + "{\"name\":\"shouldnotwork5\", \"class\":\"host\", \"leader\": $sleep_pid}") +(! machinectl list | grep shouldnotwork5) +(! busctl call \ + org.freedesktop.machine1 \ + /org/freedesktop/machine1 \ + org.freedesktop.machine1.Manager \ + RegisterMachine \ + 'sayssus' \ + shouldnotwork6 \ + 0 \ + "" \ + host \ + 0 \ + "") +(! machinectl list | grep shouldnotwork6) +(! run0 -u testuser \ + busctl call \ + org.freedesktop.machine1 \ + /org/freedesktop/machine1 \ + org.freedesktop.machine1.Manager \ + RegisterMachine \ + 'sayssus' \ + shouldnotwork7 \ + 0 \ + "" \ + host \ + 0 \ + "") +(! machinectl list | grep shouldnotwork7) +systemctl --user --machine testuser@ stop sleep.service +test ! -f /shouldnotwork + +echo FOO=bar >/tmp/foo +chmod 600 /tmp/foo +run0 -u testuser \ + systemd-run --unit exfiltrate.service --service-type notify --property NotifyAccess=all --user \ + unshare --map-root-user --user --mount \ + bash -c 'mount --bind /tmp/foo /usr/lib/os-release; systemd-notify --ready; exec sleep infinity' +exfiltrate_pid="$(systemctl --machine testuser@.host show --user -P MainPID exfiltrate.service)" +run0 -u testuser \ + varlinkctl \ + call \ + /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.Register \ + "{\"name\":\"exfiltrate\", \"class\":\"container\", \"leader\": $exfiltrate_pid}" +exfiltrate_output="$(run0 -u testuser \ + varlinkctl \ + call \ + /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List \ + "{\"name\":\"exfiltrate\",\"acquireMetadata\":\"graceful\"}" 2>&1)" || true +(! echo "$exfiltrate_output" | grep '"name".*"exfiltrate"' >/dev/null) +(! echo "$exfiltrate_output" | grep "FOO=bar" >/dev/null) +systemctl --user --machine testuser@ stop exfiltrate.service + run0 -u testuser mkdir /var/tmp/image-tar run0 -u testuser importctl --user export-tar zurps /var/tmp/image-tar/kurps.tar.gz -m run0 -u testuser importctl --user import-tar /var/tmp/image-tar/kurps.tar.gz -m diff --git a/test/units/TEST-19-CGROUP.tasks-max-scale.sh b/test/units/TEST-19-CGROUP.tasks-max-scale.sh new file mode 100755 index 0000000000000..d9b2793cc046b --- /dev/null +++ b/test/units/TEST-19-CGROUP.tasks-max-scale.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +testcase_tasks_max_scale_serialize() { + # Regression test for https://github.com/systemd/systemd/issues/41009 + # TasksMaxScale was serialized as 4.0% instead of 40.00%, causing 10x reduction on daemon-reload + + local unit="test-tasks-max.slice" + local unit_file="/run/systemd/system/${unit}" + local dropin_dir="/run/systemd/system.control/${unit}.d" + + # shellcheck disable=SC2329 + cleanup() ( + set +e + rm -rf "${dropin_dir}" + rm -f "${unit_file}" + systemctl daemon-reload + ) + trap cleanup RETURN + + printf '[Slice]\n' >"${unit_file}" + + systemctl daemon-reload + + # Set TasksMax=40% via D-Bus — exercises bus_cgroup_set_tasks_max_scale() + systemctl set-property --runtime "${unit}" TasksMax=40% + + # Verify drop-in file contains correct percentage (40.00%, not 4.0%) + grep -q '^TasksMax=40\.00%$' "${dropin_dir}/50-TasksMaxScale.conf" + + # Capture value before daemon-reload + local tasks_max_before + tasks_max_before=$(systemctl show -P TasksMax "${unit}") + + # Reload and verify value is preserved (the actual bug: value dropped 10x here) + systemctl daemon-reload + + local tasks_max_after + tasks_max_after=$(systemctl show -P TasksMax "${unit}") + assert_eq "${tasks_max_before}" "${tasks_max_after}" +} + +run_testcases diff --git a/test/units/TEST-22-TMPFILES.01.sh b/test/units/TEST-22-TMPFILES.01.sh index 4159796dae3ce..4062c838dc6d0 100755 --- a/test/units/TEST-22-TMPFILES.01.sh +++ b/test/units/TEST-22-TMPFILES.01.sh @@ -9,5 +9,24 @@ set -o pipefail rm -fr /tmp/test echo "e /tmp/test - root root 1d" | systemd-tmpfiles --create - +test ! -e /tmp/test + +touch /tmp/test +echo "r /tmp/test - - - -" | systemd-tmpfiles --remove - +test ! -e /tmp/test +touch /tmp/test +systemd-tmpfiles --remove --inline 'p /tmp/fifo' 'r /tmp/test' +test ! -e /tmp/fifo test ! -e /tmp/test + +# Test invalid config +systemd-tmpfiles --inline --remove 'garbage' || ret=$? +test "$ret" -eq 65 # EX_DATAERR + +echo 'garbage' >/tmp/config.conf +systemd-tmpfiles --remove /tmp/config.conf || ret=$? +test "$ret" -eq 65 # EX_DATAERR + +systemd-tmpfiles --remove /tmp/config-missing.conf || ret=$? +test "$ret" -eq 1 diff --git a/test/units/TEST-29-PORTABLE.image.sh b/test/units/TEST-29-PORTABLE.image.sh index 36d187a75288d..3018e86ecdee1 100755 --- a/test/units/TEST-29-PORTABLE.image.sh +++ b/test/units/TEST-29-PORTABLE.image.sh @@ -212,6 +212,19 @@ portablectl inspect --force --cat --extension /tmp/app0.raw --extension /tmp/con portablectl detach --now --runtime --extension /tmp/app0.raw --extension /tmp/conf0.raw /usr/share/minimal_0.raw app0 +# Ensure that ExtensionImages= is added to the drop-in even when the extension has no unit files +# (all units come from the base image). +portablectl "${ARGS[@]}" attach --force --now --runtime --extension /tmp/app-data-only.raw /usr/share/minimal_0.raw minimal-app0 + +systemctl is-active minimal-app0.service +status="$(portablectl is-attached --extension app-data-only minimal_0)" +[[ "${status}" == "running-runtime" ]] + +grep -q -F "ExtensionImages=/tmp/app-data-only.raw" /run/systemd/system.attached/minimal-app0.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app-data-only.raw" /run/systemd/system.attached/minimal-app0.service.d/20-portable.conf + +portablectl detach --now --runtime --extension /tmp/app-data-only.raw /usr/share/minimal_0.raw minimal-app0 + # Ensure that mixed mode copies the images and units (client-owned) but symlinks the profile (OS owned) portablectl "${ARGS[@]}" attach --copy=mixed --runtime --extension /tmp/app0.raw /usr/share/minimal_0.raw app0 test -f /run/portables/app0.raw diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh index 46abca9bace72..4b81799ef3dea 100755 --- a/test/units/TEST-46-HOMED.sh +++ b/test/units/TEST-46-HOMED.sh @@ -87,6 +87,29 @@ testcase_basic() { PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Inline test" inspect test-user + # --member-of= + systemd-sysusers --inline "g test-group1" "g test-group2" + # Single group + PASSWORD=xEhErW0ndafV4s homectl update test-user --member-of="test-group1" + [[ "$(homectl inspect -j test-user | jq -c .memberOf)" == '["test-group1"]' ]] + # Multiple groups + PASSWORD=xEhErW0ndafV4s homectl update test-user --member-of="test-group1,test-group2" + [[ "$(homectl inspect -j test-user | jq -c .memberOf)" == '["test-group1","test-group2"]' ]] + # Empty argument + PASSWORD=xEhErW0ndafV4s homectl update test-user --member-of= + [[ "$(homectl inspect -j test-user | jq -c .memberOf)" == 'null' ]] + # Argument shenanigans + # - only separators + (! PASSWORD=xEhErW0ndafV4s homectl update test-user --member-of=",,,,,,,,,,,,,,,,,,") + # - invalid group + (! PASSWORD=xEhErW0ndafV4s homectl update test-user --member-of="test-group1,inv@lid.group?") + # - separators & valid groups + PASSWORD=xEhErW0ndafV4s homectl update test-user --member-of=",,,,,test-group1,,,,,,,,,,,,,,test-group2," + [[ "$(homectl inspect -j test-user | jq -c .memberOf)" == '["test-group1","test-group2"]' ]] + # - duplicate groups + PASSWORD=xEhErW0ndafV4s homectl update test-user --member-of="test-group2,test-group1,test-group1,test-group2" + [[ "$(homectl inspect -j test-user | jq -c .memberOf)" == '["test-group1","test-group2"]' ]] + homectl deactivate test-user inspect test-user diff --git a/test/units/TEST-50-DISSECT.dissect.sh b/test/units/TEST-50-DISSECT.dissect.sh index 07e0d1a8a0488..65e684fa07ad9 100755 --- a/test/units/TEST-50-DISSECT.dissect.sh +++ b/test/units/TEST-50-DISSECT.dissect.sh @@ -25,9 +25,9 @@ systemd-dissect "$MINIMAL_IMAGE.raw" | grep -F -f <(sed 's/"//g' "$OS_RELEASE") systemd-dissect --list "$MINIMAL_IMAGE.raw" | grep '^etc/os-release$' >/dev/null systemd-dissect --mtree "$MINIMAL_IMAGE.raw" --mtree-hash yes | \ - grep -E "^.(/usr|)/bin/cat type=file mode=0755 uid=0 gid=0 size=[0-9]* sha256sum=[a-z0-9]*$" >/dev/null + grep -E "^.(/usr|)/bin/bash type=file mode=0755 uid=0 gid=0 size=[0-9]* sha256sum=[a-z0-9]*$" >/dev/null systemd-dissect --mtree "$MINIMAL_IMAGE.raw" --mtree-hash no | \ - grep -E "^.(/usr|)/bin/cat type=file mode=0755 uid=0 gid=0 size=[0-9]*$" >/dev/null + grep -E "^.(/usr|)/bin/bash type=file mode=0755 uid=0 gid=0 size=[0-9]*$" >/dev/null read -r SHA256SUM1 _ < <(systemd-dissect --copy-from "$MINIMAL_IMAGE.raw" etc/os-release | sha256sum) test "$SHA256SUM1" != "" @@ -160,6 +160,8 @@ mv "$MINIMAL_IMAGE.foohash" "$MINIMAL_IMAGE.roothash" # Derive partition UUIDs from root hash, in UUID syntax ROOT_UUID="$(systemd-id128 -u show "$(head -c 32 "$MINIMAL_IMAGE.roothash")" -u | tail -n 1 | cut -b 6-)" VERITY_UUID="$(systemd-id128 -u show "$(tail -c 32 "$MINIMAL_IMAGE.roothash")" -u | tail -n 1 | cut -b 6-)" +USR_UUID="$ROOT_UUID" +USR_VERITY_UUID="$VERITY_UUID" systemd-dissect --json=short \ --root-hash "$MINIMAL_IMAGE_ROOTHASH" \ @@ -177,6 +179,20 @@ if [[ -n "${OPENSSL_CONFIG:-}" ]]; then fi systemd-dissect --root-hash "$MINIMAL_IMAGE_ROOTHASH" "$MINIMAL_IMAGE.gpt" | grep -F "MARKER=1" >/dev/null systemd-dissect --root-hash "$MINIMAL_IMAGE_ROOTHASH" "$MINIMAL_IMAGE.gpt" | grep -F -f <(sed 's/"//g' "$OS_RELEASE") >/dev/null +systemd-dissect --json=short \ + --usr-hash "$MINIMAL_IMAGE_ROOTHASH" \ + "$MINIMAL_IMAGE.usr.gpt" | \ + grep '{"rw":"ro","designator":"usr","partition_uuid":"'"$USR_UUID"'","partition_label":"Usr Partition","fstype":"squashfs","architecture":"'"$ARCHITECTURE"'","verity":"signed",' >/dev/null +systemd-dissect --json=short \ + --usr-hash "$MINIMAL_IMAGE_ROOTHASH" \ + "$MINIMAL_IMAGE.usr.gpt" | \ + grep '{"rw":"ro","designator":"usr-verity","partition_uuid":"'"$USR_VERITY_UUID"'","partition_label":"Usr Verity Partition","fstype":"DM_verity_hash","architecture":"'"$ARCHITECTURE"'","verity":null,' >/dev/null +if [[ -n "${OPENSSL_CONFIG:-}" ]]; then + systemd-dissect --json=short \ + --usr-hash "$MINIMAL_IMAGE_ROOTHASH" \ + "$MINIMAL_IMAGE.usr.gpt" | \ + grep -E '{"rw":"ro","designator":"usr-verity-sig","partition_uuid":"'".*"'","partition_label":"Usr Signature Partition","fstype":"verity_hash_signature","architecture":"'"$ARCHITECTURE"'","verity":null,' >/dev/null +fi # Test image policies systemd-dissect --validate "$MINIMAL_IMAGE.gpt" @@ -194,6 +210,12 @@ systemd-dissect --validate "$MINIMAL_IMAGE.gpt" --image-policy=root=verity:swap= systemd-dissect --validate "$MINIMAL_IMAGE.gpt" --image-policy=root=signed (! systemd-dissect --validate "$MINIMAL_IMAGE.gpt" --image-policy=root=signed:root-verity-sig=unused+absent) (! systemd-dissect --validate "$MINIMAL_IMAGE.gpt" --image-policy=root=signed:root-verity=unused+absent) +systemd-dissect --validate "$MINIMAL_IMAGE.usr.gpt" --image-policy=usr=verity +systemd-dissect --validate "$MINIMAL_IMAGE.usr.gpt" --image-policy=usr=verity:usr-verity-sig=unused+absent +(! systemd-dissect --validate "$MINIMAL_IMAGE.usr.gpt" --image-policy=usr=verity:usr-verity=unused+absent) +systemd-dissect --validate "$MINIMAL_IMAGE.usr.gpt" --image-policy=usr=signed +(! systemd-dissect --validate "$MINIMAL_IMAGE.usr.gpt" --image-policy=usr=signed:usr-verity-sig=unused+absent) +(! systemd-dissect --validate "$MINIMAL_IMAGE.usr.gpt" --image-policy=usr=signed:usr-verity=unused+absent) # Test RootImagePolicy= unit file setting systemd-run --wait -P \ @@ -243,6 +265,12 @@ systemd-run --wait -P \ -p RootImagePolicy='root=signed+lol:wut=wat+signed' \ -p MountAPIVFS=yes \ cat /usr/lib/os-release | grep -F "MARKER=1" >/dev/null +# A policy pinning a single fstype on a GPT image should still use verity. +systemd-run --wait -P \ + -p RootImage="$MINIMAL_IMAGE.gpt" \ + -p RootImagePolicy='root=verity+squashfs' \ + -p MountAPIVFS=yes \ + cat /usr/lib/os-release | grep -F "MARKER=1" >/dev/null (! systemd-run --wait -P \ -p RootImage="$MINIMAL_IMAGE.gpt" \ -p RootHash="$MINIMAL_IMAGE_ROOTHASH" \ @@ -643,13 +671,20 @@ VDIR="/tmp/${VBASE}.v" mkdir "$VDIR" rm -rf /tmp/markers/ mkdir /tmp/markers/ +CDIR1="/tmp/${VBASE}_confext_a" +CDIR2="/tmp/${VBASE}_confext_b" +mkdir -p "$CDIR1/etc/extension-release.d/" "$CDIR2/etc/extension-release.d/" +echo "ID=_any" >"$CDIR1/etc/extension-release.d/extension-release.${VBASE}_confext_a" +touch "$CDIR1/etc/${VBASE}_confext_a.marker" +echo "ID=_any" >"$CDIR2/etc/extension-release.d/extension-release.${VBASE}_confext_b" +touch "$CDIR2/etc/${VBASE}_confext_b.marker" cat >/run/systemd/system/testservice-50g.service <"$VDIR/${VBASE}_2/etc/extension-release.d/extension-release.${VBASE}_2" touch "$VDIR/${VBASE}_2/etc/${VBASE}_2.marker" systemctl reload testservice-50g.service grep -q -F "${VBASE}_2.marker" /tmp/markers/50g +grep -q -F "${VBASE}_confext_a.marker" /tmp/markers/50g +grep -q -F "${VBASE}_confext_b.marker" /tmp/markers/50g # Do it for a couple more times (to make sure we're tearing down old overlays) for _ in {1..5}; do systemctl reload testservice-50g.service; done systemctl stop testservice-50g.service @@ -684,13 +723,17 @@ rm -f /run/systemd/system/testservice-50g.service # this time) VDIR2="/tmp/${VBASE}.raw.v" mkdir "$VDIR2" +CIMG1="/tmp/${VBASE}_confext_a.raw" +CIMG2="/tmp/${VBASE}_confext_b.raw" +mksquashfs "$CDIR1" "$CIMG1" -noappend +mksquashfs "$CDIR2" "$CIMG2" -noappend cat >/run/systemd/system/testservice-50h.service </run/systemd/system/testservice-50m.service <&1 | grep -v -F "Warning" >/dev/null rm /var/lib/extensions/app-reload.raw +# Test that GPT images with an ISO9660 El Torito boot catalog are dissected correctly. The blkid +# superblock filter must prevent the iso9660 superblock from being detected, so dissection proceeds via +# the GPT partition table rather than treating the whole image as a single iso9660 filesystem. +defs="$(mktemp --directory "$IMAGE_DIR/test-50-dissect-eltorito.defs.XXXXXXXXXX")" +imgs="$(mktemp --directory "$IMAGE_DIR/test-50-dissect-eltorito.imgs.XXXXXXXXXX")" + +tee "$defs/00-esp.conf" </dev/null + volume="test-repart-$RANDOM" touch "$imgs/empty-password" @@ -396,6 +398,50 @@ $imgs/zzz7 : start= 6291416, size= 131072, type=3B8F8425-20E0-4F3B-907F PASSWORD="" systemd-dissect "$imgs/zzz" -M "$imgs/mount" udevadm info /dev/disk/by-label/schrupfel | grep ID_FS_TYPE=crypto_LUKS >/dev/null systemd-dissect -U "$imgs/mount" + + echo "*** 7. Testing Discard=no ***" + + tee "$defs/extra4.conf" </dev/null + losetup -d "$loop" } testcase_dropin() { @@ -895,6 +941,7 @@ EOF --dry-run=no \ --empty=create \ --size=auto \ + --split=yes \ --json=pretty \ --private-key="$defs/verity.key" \ --certificate="$defs/verity.crt" \ @@ -907,6 +954,13 @@ EOF assert_eq "$drh" "$hrh" assert_eq "$hrh" "$srh" + # The split-out verity signature file should be a valid JSON document (i.e. trailing NUL padding + # from the on-disk partition must be trimmed when writing the split file). + sig_split=$(jq -r ".[] | select(.type == \"root-${architecture}-verity-sig\") | .split_path" <<<"$output") + assert_neq "$sig_split" "" + assert_neq "$sig_split" "null" + jq . "$sig_split" >/dev/null + # Check that offline signing works and the resulting image is valid output=$(systemd-repart --offline="$OFFLINE" \ @@ -1208,6 +1262,18 @@ Minimize=guess EOF done + if command -v mkfs.btrfs >/dev/null; then + for minimize in guess best; do + tee "$defs/root-btrfs-${minimize}.conf" </dev/null; then tee "$defs/root-squashfs.conf" <= 3 with KDF-SS if openssl_supports_kdf SSKDF; then - PASSWORD=passphrase systemd-cryptenroll --tpm2-device-key=/tmp/srk.pub --tpm2-pcrs="12:sha256=$CURRENT_PCR_VALUE" "$IMAGE" + PASSWORD=passphrase systemd-cryptenroll --tpm2-device-key=/tmp/srk.pub --tpm2-public-key= --tpm2-pcrs="12:sha256=$CURRENT_PCR_VALUE" "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume fi @@ -153,23 +153,23 @@ fi # Use default (0) seal key handle systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-seal-key-handle=0 "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x0 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-seal-key-handle=0x0 "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume # Use SRK seal key handle systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=81000001 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-seal-key-handle=81000001 "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x81000001 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-seal-key-handle=0x81000001 "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume @@ -189,12 +189,12 @@ PERSISTENT_HANDLE="0x${PERSISTENT_LINE##*0x}" tpm2_flushcontext -t systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle="${PERSISTENT_HANDLE#0x}" "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-seal-key-handle="${PERSISTENT_HANDLE#0x}" "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle="$PERSISTENT_HANDLE" "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-seal-key-handle="$PERSISTENT_HANDLE" "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume diff --git a/test/units/TEST-70-TPM2.measure.sh b/test/units/TEST-70-TPM2.measure.sh index bf30bd57b3340..30fa51e52137c 100755 --- a/test/units/TEST-70-TPM2.measure.sh +++ b/test/units/TEST-70-TPM2.measure.sh @@ -45,6 +45,12 @@ cat >/tmp/result.json </tmp/result </run/log/systemd/tpm2-measure.log </etc/machine-info </etc/machine-info < "$CONFIGDIR/optional.feature.d/enable.conf" "$SYSUPDATE" --offline list v5 | grep "incomplete" >/dev/null - update_now "monolithic" + update_now "$update_type" "$SYSUPDATE" --offline list v5 | grep -v "incomplete" >/dev/null verify_version "$blockdev" "$sector_size" v3 1 verify_version_current "$blockdev" "$sector_size" v5 2 @@ -373,7 +373,9 @@ EOF if $have_updatectl; then systemctl start systemd-sysupdated "$SYSUPDATE" --verify=no check-new - updatectl update + updatectl update |& tee "$WORKDIR"/updatectl-update-6 + grep "Done" "$WORKDIR"/updatectl-update-6 + (! grep "Already up-to-date" "$WORKDIR"/updatectl-update-6) else # If no updatectl, gracefully fall back to systemd-sysupdate update_now "$update_type" diff --git a/test/units/TEST-74-AUX-UTILS.busctl.sh b/test/units/TEST-74-AUX-UTILS.busctl.sh index b8ae8da568204..875f353b00ea4 100755 --- a/test/units/TEST-74-AUX-UTILS.busctl.sh +++ b/test/units/TEST-74-AUX-UTILS.busctl.sh @@ -146,10 +146,10 @@ busctl get-property -j \ busctl --quiet --timeout=1 --limit-messages=1 --match "interface=org.freedesktop.systemd1.Manager" monitor -START_USEC=$(date +%s%6N) +START_NSEC=$(date +%s%N) busctl --quiet --timeout=500ms --match "interface=io.dontexist.NeverGonnaHappen" monitor -END_USEC=$(date +%s%6N) -USEC=$((END_USEC-START_USEC)) +END_NSEC=$(date +%s%N) +NSEC=$((END_NSEC-START_NSEC)) # Validate that the above was delayed for at least 500ms, but at most 30s (some leeway for slow CIs) -test "$USEC" -gt 500000 -test "$USEC" -lt 30000000 +test "$NSEC" -gt 500000000 +test "$NSEC" -lt 30000000000 diff --git a/test/units/TEST-74-AUX-UTILS.capsule.sh b/test/units/TEST-74-AUX-UTILS.capsule.sh index c2c3073ea80a0..c32f7b9b5716a 100755 --- a/test/units/TEST-74-AUX-UTILS.capsule.sh +++ b/test/units/TEST-74-AUX-UTILS.capsule.sh @@ -58,3 +58,8 @@ systemctl clean capsule@foobar.service --what=all (! test -f /run/capsules/foobar ) (! test -f /var/lib/capsules/foobar ) (! id -u c-foobar ) + +systemctl status capsule@foobar.service || : + +systemctl --state=failed --no-legend --no-pager | tee /failed +test ! -s /failed diff --git a/test/units/TEST-74-AUX-UTILS.imds.sh b/test/units/TEST-74-AUX-UTILS.imds.sh new file mode 100755 index 0000000000000..ccd3d04c04265 --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.imds.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + + +if ! test -x /usr/lib/systemd/systemd-imdsd ; then + echo "No imdsd installed, skipping test." + exit 0 +fi + +at_exit() { + set +e + systemctl stop fake-imds systemd-imdsd.socket + ip link del dummy0 + rm -f /run/credstore/firstboot.hostname /run/credstore/acredtest /run/systemd/system/systemd-imdsd@.service.d/50-env.conf + rmdir /run/systemd/system/systemd-imdsd@.service.d +} + +trap at_exit EXIT + +systemd-run -p Type=notify --unit=fake-imds /usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-imds.py +systemctl status fake-imds + +# Add a fake network interface so that IMDS gets going +ip link add dummy0 type dummy +ip link set dummy0 up +ip addr add 192.168.47.11/24 dev dummy0 + +USERDATA='{"systemd.credentials":[{"name":"acredtest","text":"avalue"}]}' + +# First try imdsd directly +IMDSD="/usr/lib/systemd/systemd-imdsd --vendor=test --data-url=http://192.168.47.11:8088 --well-known-key=userdata:/userdata --well-known-key=hostname:/hostname" +assert_eq "$($IMDSD --well-known=hostname)" "piff" +assert_eq "$($IMDSD --well-known=userdata)" "$USERDATA" +assert_eq "$($IMDSD /hostname)" "piff" +assert_eq "$($IMDSD /userdata)" "$USERDATA" + +# Then, try it as Varlink service +mkdir -p /run/systemd/system/systemd-imdsd@.service.d/ +cat >/run/systemd/system/systemd-imdsd@.service.d/50-env.conf </dev/null + +systemd-run -p Type=notify --unit=fake-report-server-tls \ + "$FAKE_SERVER" --cert="$CERTDIR/server.crt" --key="$CERTDIR/server.key" --port=8090 +systemctl status fake-report-server-tls + +"$REPORT" metrics --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt" +"$REPORT" facts --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt" \ + --extra-header='Authorization: Bearer magic string' diff --git a/test/units/TEST-74-AUX-UTILS.socket-proxyd.sh b/test/units/TEST-74-AUX-UTILS.socket-proxyd.sh new file mode 100755 index 0000000000000..c028747ec02cb --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.socket-proxyd.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# Test systemd-socket-proxyd by setting up a backend server, a proxy in front of it, +# and verifying that data passes through correctly. + +BACKEND_SOCK="/tmp/test-proxyd-backend.sock" + +at_exit() { + set +e + systemctl stop test-proxyd-backend.service 2>/dev/null + systemctl stop test-proxyd.socket 2>/dev/null + systemctl stop test-proxyd.service 2>/dev/null + rm -f "$BACKEND_SOCK" + rm -f /run/systemd/system/test-proxyd.socket /run/systemd/system/test-proxyd.service + systemctl daemon-reload 2>/dev/null +} +trap at_exit EXIT + +# Start a backend echo server via systemd-run +systemd-run --unit=test-proxyd-backend --service-type=simple \ + socat UNIX-LISTEN:"$BACKEND_SOCK",fork EXEC:cat + +# Ensure socket is ready +timeout 5 bash -c "until [[ -S $BACKEND_SOCK ]]; do sleep 0.1; done" + +# Create a socket unit for the proxy +cat >/run/systemd/system/test-proxyd.socket </run/systemd/system/test-proxyd.service </dev/null (! busctl call org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager LookupDynamicUserByName "s" disk) (! busctl call org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager LookupDynamicUserByUID "u" "$DISK_GID") systemctl stop "$UNIT" + +# Probe specific user records +echo '{"userName":"weightmin","cpuWeight":1,"ioWeight":1}' | userdbctl -F - +echo '{"userName":"weightmax","cpuWeight":10000,"ioWeight":10000}' | userdbctl -F - diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index dc5f952e59277..9a22757067f24 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -230,6 +230,18 @@ set -o pipefail varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"cgroup": "/init.scope"}' invocation_id="$(systemctl show -P InvocationID systemd-journald.service)" varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"invocationID\": \"$invocation_id\"}" +# test for KillContext +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": 0}}' | jq -e '.context.Kill' +# test for AutomountContext/Runtime +automount_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "automount" and .runtime.LoadState == "loaded") .context.ID' | grep -v null | tail -n 1) +test -n "$automount_id" +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"name\": \"$automount_id\"}" | jq -e '.context.Automount' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"name\": \"$automount_id\"}" | jq -e '.runtime.Automount' +# test for MountContext/Runtime +mount_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "mount" and .runtime.LoadState == "loaded") .context.ID' | grep -v null | tail -n 1) +test -n "$mount_id" +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"name\": \"$mount_id\"}" | jq -e '.context.Mount' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"name\": \"$mount_id\"}" | jq -e '.runtime.Mount' # test io.systemd.Metrics varlinkctl info /run/systemd/report/io.systemd.Manager @@ -255,3 +267,148 @@ systemd-run --wait --pipe --user --machine testuser@ \ # test io.systemd.Unit in user manager systemd-run --wait --pipe --user --machine testuser@ \ varlinkctl --more call "/run/user/$testuser_uid/systemd/io.systemd.Manager" io.systemd.Unit.List '{}' + +# test --upgrade (protocol upgrade) +# The basic --upgrade proxy test is covered by the "varlinkctl serve" tests below (which use +# serve+rev/gunzip as the server). The tests here exercise features that need the Python +# server: file-input (defer fallback), ssh-exec transport (pipe pairs) and --exec mode. +UPGRADE_SOCKET="$(mktemp -d)/upgrade.sock" +UPGRADE_SERVER="$(mktemp)" +cat >"$UPGRADE_SERVER" <<'PYEOF' +#!/usr/bin/env python3 +"""Varlink upgrade test server. With a socket path argument, listens on a unix socket. +Without arguments, speaks over stdin/stdout (for ssh-exec: transport testing).""" +import json, os, socket, sys + +def sd_notify_ready(): + addr = os.environ.get("NOTIFY_SOCKET") + if not addr: + return + if addr[0] == "@": + addr = "\0" + addr[1:] + s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) + s.connect(addr) + s.sendall(b"READY=1") + s.close() + +if len(sys.argv) > 1: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.bind(sys.argv[1]) + sock.listen(1) + sd_notify_ready() + conn, _ = sock.accept() + inp = conn.makefile("rb") + out = conn.makefile("wb") +else: + inp = sys.stdin.buffer + out = sys.stdout.buffer + conn = sock = None + +# Read the varlink request (NUL-terminated JSON) +data = b"" +while True: + chunk = inp.read(1) + assert chunk, "Connection closed before receiving full varlink request" + data += chunk + if b"\0" in data: + break + +msg = json.loads(data.split(b"\0")[0]) +received_parameters = msg.get("parameters", {}) +out.write(b'{"parameters": {}}\0') +out.flush() + +# Upgraded protocol: send a non-JSON banner first to prove we're truly in raw mode, +# then echo the received parameters, then reverse lines from the client +out.write(b"<<< UPGRADED >>>\n") +out.write((json.dumps(received_parameters) + "\n").encode()) +out.flush() +for line in inp: + text = line.decode().rstrip("\n") + out.write((text[::-1] + "\n").encode()) + out.flush() + +if conn: + conn.close() +if sock: + sock.close() +PYEOF +chmod +x "$UPGRADE_SERVER" + +# Test --upgrade with stdin redirected from a regular file (epoll can't poll regular files, +# so this exercises the sd_event_add_defer fallback path) +UPGRADE_SOCKET2="$(mktemp -d)/upgrade.sock" +systemd-notify --fork -q -- python3 "$UPGRADE_SERVER" "$UPGRADE_SOCKET2" + +echo "file input test" > /tmp/test-upgrade-input +result="$(varlinkctl call --upgrade "unix:$UPGRADE_SOCKET2" io.systemd.test.Reverse '{"foo":"file"}' < /tmp/test-upgrade-input)" +echo "$result" | grep "<<< UPGRADED >>>" >/dev/null +echo "$result" | grep '"foo": "file"' >/dev/null +echo "$result" | grep "tset tupni elif" >/dev/null + +# Test --upgrade over ssh-exec: transport (pipe pair, not a bidirectional socket). +# This exercises the input_fd != output_fd path in sd_varlink_call_and_upgrade(). +# Reuse the same server script without a socket argument - it speaks over stdin/stdout. +cat > "$SSHBINDIR"/ssh <>>" >/dev/null +echo "$result" | grep '"foo": "ssh"' >/dev/null +echo "$result" | grep "tset epip hss" >/dev/null + +# Start another server for --exec test +rm -f "$UPGRADE_SOCKET" +systemd-notify --fork -q -- python3 "$UPGRADE_SERVER" "$UPGRADE_SOCKET" + +# Test --exec mode: the upgraded socket becomes stdin/stdout of the child. +# Since stdout goes to the socket (not the terminal), write results to a file for verification. +EXEC_RESULT="$(mktemp)" +varlinkctl call --upgrade --exec "unix:$UPGRADE_SOCKET" io.systemd.test.Reverse '{"foo":"bar"}' -- \ + bash -c "head -2 > '$EXEC_RESULT'; echo 'hello world'; head -1 >> '$EXEC_RESULT'" +grep "<<< UPGRADED >>>" "$EXEC_RESULT" >/dev/null +grep '"foo": "bar"' "$EXEC_RESULT" >/dev/null +grep "dlrow olleh" "$EXEC_RESULT" >/dev/null +rm -f "$EXEC_RESULT" + +rm -f "$UPGRADE_SOCKET" "$UPGRADE_SOCKET2" "$UPGRADE_SERVER" /tmp/test-upgrade-input +rm -rf "$(dirname "$UPGRADE_SOCKET")" "$(dirname "$UPGRADE_SOCKET2")" + +# Test varlinkctl serve: expose a stdio command via varlink protocol upgrade with socket activation. +# This is the "inetd for varlink" pattern: any stdio tool becomes a varlink service. +SERVE_SOCKET="$(mktemp -d)/serve.sock" + +# Test 1: serve rev: proves bidirectional data flow through the upgrade +SERVE_PID=$(systemd-notify --fork -- \ + systemd-socket-activate -l "$SERVE_SOCKET" -- \ + varlinkctl serve io.systemd.test.Reverse rev) + +# Verify introspection works on the serve endpoint and shows the upgrade annotation +varlinkctl introspect "unix:$SERVE_SOCKET" io.systemd.test | grep "method Reverse" >/dev/null +varlinkctl introspect "unix:$SERVE_SOCKET" io.systemd.test | grep "Requires 'upgrade' flag" >/dev/null + +result="$(echo "hello world" | varlinkctl call --upgrade "unix:$SERVE_SOCKET" io.systemd.test.Reverse '{}')" +echo "$result" | grep "dlrow olleh" >/dev/null +kill "$SERVE_PID" 2>/dev/null || true +wait "$SERVE_PID" 2>/dev/null || true +rm -f "$SERVE_SOCKET" + +# Test 2: decompress via serve: the "sandboxed decompressor" use-case (the real thing would be a proper +# unit with real sandboxing). +# Pipe gzip-compressed data through a varlinkctl serve + gunzip endpoint and verify round-trip. +SERVE_PID=$(systemd-notify --fork -- \ + systemd-socket-activate -l "$SERVE_SOCKET" -- \ + varlinkctl serve io.systemd.Compress.Decompress gunzip) + +SERVE_TMPDIR="$(mktemp -d)" +echo "untrusted data decompressed safely via varlink serve" | gzip > "$SERVE_TMPDIR/compressed.gz" +result="$(varlinkctl call --upgrade "unix:$SERVE_SOCKET" io.systemd.Compress.Decompress '{}' < "$SERVE_TMPDIR/compressed.gz")" +echo "$result" | grep "untrusted data decompressed safely" >/dev/null +kill "$SERVE_PID" 2>/dev/null || true +wait "$SERVE_PID" 2>/dev/null || true + +rm -f "$SERVE_SOCKET" +rm -rf "$(dirname "$SERVE_SOCKET")" "$SERVE_TMPDIR" diff --git a/test/units/TEST-75-RESOLVED.sh b/test/units/TEST-75-RESOLVED.sh index b3656da94043a..bb1cf9576c292 100755 --- a/test/units/TEST-75-RESOLVED.sh +++ b/test/units/TEST-75-RESOLVED.sh @@ -1487,6 +1487,55 @@ EOF grep -qF "1.2.3.4" "$RUN_OUT" } +testcase_static_record() { + mkdir -p /run/systemd/resolve/static.d/ + cat >/run/systemd/resolve/static.d/statictest.rr </run/systemd/resolve/static.d/statictest2.rr </run/systemd/resolve/static.d/garbage.rr </run/systemd/resolve/static.d/garbage2.rr </dev/null ; then - echo "kernel too old, has no PSI." >&2 - echo OK >/testok - exit 0 -fi - -CGROUP=/sys/fs/cgroup/"$(systemctl show TEST-79-MEMPRESS.service -P ControlGroup)" -test -d "$CGROUP" - -if ! test -f "$CGROUP"/memory.pressure ; then - echo "No memory accounting/PSI delegated via cgroup, can't test." >&2 - echo OK >/testok - exit 0 -fi - -UNIT="test-mempress-$RANDOM.service" -SCRIPT="/tmp/mempress-$RANDOM.sh" - -cat >"$SCRIPT" <<'EOF' -#!/usr/bin/env bash - -set -ex - -export -id - -test -n "$MEMORY_PRESSURE_WATCH" -test "$MEMORY_PRESSURE_WATCH" != /dev/null -test -w "$MEMORY_PRESSURE_WATCH" - -ls -al "$MEMORY_PRESSURE_WATCH" - -EXPECTED="$(echo -n -e "some 123000 2000000\x00" | base64)" - -test "$EXPECTED" = "$MEMORY_PRESSURE_WRITE" - -EOF - -chmod +x "$SCRIPT" - -systemd-run \ - -u "$UNIT" \ - -p Type=exec \ - -p ProtectControlGroups=1 \ - -p DynamicUser=1 \ - -p MemoryPressureWatch=on \ - -p MemoryPressureThresholdSec=123ms \ - -p BindPaths=$SCRIPT \ - `# Make sanitizers happy when DynamicUser=1 pulls in instrumented systemd NSS modules` \ - -p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \ - --wait "$SCRIPT" - -rm "$SCRIPT" - -touch /testok diff --git a/test/units/TEST-79-PRESSURE.sh b/test/units/TEST-79-PRESSURE.sh new file mode 100755 index 0000000000000..72de8a1d9d189 --- /dev/null +++ b/test/units/TEST-79-PRESSURE.sh @@ -0,0 +1,170 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +# We not just test if the file exists, but try to read from it, since if +# CONFIG_PSI_DEFAULT_DISABLED is set in the kernel the file will exist and can +# be opened, but any read()s will fail with EOPNOTSUPP, which we want to +# detect. +if ! cat /proc/pressure/memory >/dev/null ; then + echo "kernel too old, has no PSI." >&2 + echo OK >/testok + exit 0 +fi + +CGROUP=/sys/fs/cgroup/"$(systemctl show TEST-79-PRESSURE.service -P ControlGroup)" +test -d "$CGROUP" + +if ! test -f "$CGROUP"/memory.pressure ; then + echo "No memory accounting/PSI delegated via cgroup, can't test." >&2 + echo OK >/testok + exit 0 +fi + +UNIT="test-mempress-$RANDOM.service" +SCRIPT="/tmp/mempress-$RANDOM.sh" + +cat >"$SCRIPT" <<'EOF' +#!/usr/bin/env bash + +set -ex + +export +id + +test -n "$MEMORY_PRESSURE_WATCH" +test "$MEMORY_PRESSURE_WATCH" != /dev/null +test -w "$MEMORY_PRESSURE_WATCH" + +ls -al "$MEMORY_PRESSURE_WATCH" + +EXPECTED="$(echo -n -e "some 123000 2000000\x00" | base64)" + +test "$EXPECTED" = "$MEMORY_PRESSURE_WRITE" + +EOF + +chmod +x "$SCRIPT" + +systemd-run \ + -u "$UNIT" \ + -p Type=exec \ + -p ProtectControlGroups=1 \ + -p DynamicUser=1 \ + -p MemoryPressureWatch=on \ + -p MemoryPressureThresholdSec=123ms \ + -p BindPaths=$SCRIPT \ + `# Make sanitizers happy when DynamicUser=1 pulls in instrumented systemd NSS modules` \ + -p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \ + --wait "$SCRIPT" + +rm "$SCRIPT" + +# Now test CPU pressure + +if ! cat /proc/pressure/cpu >/dev/null ; then + echo "kernel has no CPU PSI support." >&2 + echo OK >/testok + exit 0 +fi + +if ! test -f "$CGROUP"/cpu.pressure ; then + echo "No CPU accounting/PSI delegated via cgroup, can't test." >&2 + echo OK >/testok + exit 0 +fi + +UNIT="test-cpupress-$RANDOM.service" +SCRIPT="/tmp/cpupress-$RANDOM.sh" + +cat >"$SCRIPT" <<'EOF' +#!/usr/bin/env bash + +set -ex + +export +id + +test -n "$CPU_PRESSURE_WATCH" +test "$CPU_PRESSURE_WATCH" != /dev/null +test -w "$CPU_PRESSURE_WATCH" + +ls -al "$CPU_PRESSURE_WATCH" + +EXPECTED="$(echo -n -e "some 123000 2000000\x00" | base64)" + +test "$EXPECTED" = "$CPU_PRESSURE_WRITE" + +EOF + +chmod +x "$SCRIPT" + +systemd-run \ + -u "$UNIT" \ + -p Type=exec \ + -p ProtectControlGroups=1 \ + -p DynamicUser=1 \ + -p CPUPressureWatch=on \ + -p CPUPressureThresholdSec=123ms \ + -p BindPaths=$SCRIPT \ + `# Make sanitizers happy when DynamicUser=1 pulls in instrumented systemd NSS modules` \ + -p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \ + --wait "$SCRIPT" + +rm "$SCRIPT" + +# Now test IO pressure + +if ! cat /proc/pressure/io >/dev/null ; then + echo "kernel has no IO PSI support." >&2 + echo OK >/testok + exit 0 +fi + +if ! test -f "$CGROUP"/io.pressure ; then + echo "No IO accounting/PSI delegated via cgroup, can't test." >&2 + echo OK >/testok + exit 0 +fi + +UNIT="test-iopress-$RANDOM.service" +SCRIPT="/tmp/iopress-$RANDOM.sh" + +cat >"$SCRIPT" <<'EOF' +#!/usr/bin/env bash + +set -ex + +export +id + +test -n "$IO_PRESSURE_WATCH" +test "$IO_PRESSURE_WATCH" != /dev/null +test -w "$IO_PRESSURE_WATCH" + +ls -al "$IO_PRESSURE_WATCH" + +EXPECTED="$(echo -n -e "some 123000 2000000\x00" | base64)" + +test "$EXPECTED" = "$IO_PRESSURE_WRITE" + +EOF + +chmod +x "$SCRIPT" + +systemd-run \ + -u "$UNIT" \ + -p Type=exec \ + -p ProtectControlGroups=1 \ + -p DynamicUser=1 \ + -p IOPressureWatch=on \ + -p IOPressureThresholdSec=123ms \ + -p BindPaths=$SCRIPT \ + `# Make sanitizers happy when DynamicUser=1 pulls in instrumented systemd NSS modules` \ + -p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \ + --wait "$SCRIPT" + +rm "$SCRIPT" + +touch /testok diff --git a/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh b/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh index 7d26541ffa6f4..440c3e5edfbcc 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh @@ -101,7 +101,14 @@ basic_tests() { testcase_bootctl_basic() { assert_in "$(bootctl --print-esp-path)" "^(/boot/|/efi)$" assert_in "$(bootctl --print-boot-path)" "^(/boot/|/efi)$" + bootctl --print-root-device + bootctl --print-root-device --print-root-device + bootctl --print-esp-path + bootctl --print-boot-path + bootctl --print-loader-path + bootctl --print-stub-path + bootctl --print-efi-architecture basic_tests } diff --git a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh index b3f702a2b0d7f..b3a3de76960a3 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh @@ -150,6 +150,11 @@ coredumpctl info foo bar baz "${CORE_TEST_BIN##*/}" coredumpctl info COREDUMP_EXE="$CORE_TEST_BIN" coredumpctl info COREDUMP_EXE=aaaaa COREDUMP_EXE= COREDUMP_EXE="$CORE_TEST_BIN" +# Check that COREDUMP_TID= is present and displayed by coredumpctl info +coredumpctl info "$CORE_TEST_BIN" | grep "TID:" >/dev/null +# Check the field is queryable in the journal +coredumpctl -F COREDUMP_TID + coredumpctl debug --debugger=/bin/true "$CORE_TEST_BIN" SYSTEMD_DEBUGGER=/bin/true coredumpctl debug "$CORE_TEST_BIN" coredumpctl debug --debugger=/bin/true --debugger-arguments="-this --does --not 'do anything' -a -t --all" "${CORE_TEST_BIN##*/}" diff --git a/test/units/TEST-87-AUX-UTILS-VM.vmspawn-drives.sh b/test/units/TEST-87-AUX-UTILS-VM.vmspawn-drives.sh new file mode 100755 index 0000000000000..5ec2fd2f7bc4c --- /dev/null +++ b/test/units/TEST-87-AUX-UTILS-VM.vmspawn-drives.sh @@ -0,0 +1,188 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# Test vmspawn QMP-based multi-drive setup and ephemeral overlay. +# +# Exercises the async QMP command pipeline with multiple drives: +# - Multiple fdset allocations (counter correctness) +# - Pipelined blockdev-add commands (FIFO ordering) +# - io_uring retry callbacks (if QEMU lacks io_uring support) +# - Multiple device_add commands +# - blockdev-create job watching with deferred continuation (ephemeral) +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +if [[ -v ASAN_OPTIONS ]]; then + echo "vmspawn launches QEMU which doesn't work under ASan, skipping" + exit 0 +fi + +if ! command -v systemd-vmspawn >/dev/null 2>&1; then + echo "systemd-vmspawn not found, skipping" + exit 0 +fi + +if ! find_qemu_binary; then + echo "QEMU not found, skipping" + exit 0 +fi + +if ! command -v mke2fs >/dev/null 2>&1; then + echo "mke2fs not found, skipping" + exit 0 +fi + +# Find a kernel for direct boot +KERNEL="" +for k in /usr/lib/modules/"$(uname -r)"/vmlinuz /boot/vmlinuz-"$(uname -r)" /boot/vmlinuz; do + if [[ -f "$k" ]]; then + KERNEL="$k" + break + fi +done + +if [[ -z "$KERNEL" ]]; then + echo "No kernel found for direct VM boot, skipping" + exit 0 +fi +echo "Using kernel: $KERNEL" + +WORKDIR="$(mktemp -d)" + +at_exit() { + set +e + for m in "${MACHINE_MULTI:-}" "${MACHINE_EPHEMERAL:-}"; do + [[ -n "$m" ]] || continue + if machinectl status "$m" &>/dev/null; then + machinectl terminate "$m" 2>/dev/null + timeout 10 bash -c "while machinectl status '$m' &>/dev/null; do sleep .5; done" 2>/dev/null + fi + done + [[ -n "${VMSPAWN_MULTI_PID:-}" ]] && kill "$VMSPAWN_MULTI_PID" 2>/dev/null && wait "$VMSPAWN_MULTI_PID" 2>/dev/null + [[ -n "${VMSPAWN_EPHEMERAL_PID:-}" ]] && kill "$VMSPAWN_EPHEMERAL_PID" 2>/dev/null && wait "$VMSPAWN_EPHEMERAL_PID" 2>/dev/null + rm -rf "$WORKDIR" +} +trap at_exit EXIT + +# Create a minimal root filesystem directory, then bake it into a raw ext4 image. +# The guest doesn't need to fully boot — 'sleep infinity' keeps QEMU alive for QMP testing. +mkdir -p "$WORKDIR/rootfs/sbin" +cat >"$WORKDIR/rootfs/sbin/init" <<'INITEOF' +#!/bin/sh +exec sleep infinity +INITEOF +chmod +x "$WORKDIR/rootfs/sbin/init" + +truncate -s 256M "$WORKDIR/root.raw" +mke2fs -t ext4 -q -d "$WORKDIR/rootfs" "$WORKDIR/root.raw" + +# Create extra raw drive images (different sizes to be distinguishable) +truncate -s 64M "$WORKDIR/extra1.raw" +truncate -s 32M "$WORKDIR/extra2.raw" + +wait_for_machine() { + local machine="$1" pid="$2" log="$3" + timeout 30 bash -c " + while ! machinectl list --no-legend 2>/dev/null | grep >/dev/null '$machine'; do + if ! kill -0 $pid 2>/dev/null; then + echo 'vmspawn exited before machine registration' + cat '$log' + exit 1 + fi + sleep .5 + done + " +} + +# --- Test 1: Multi-drive setup (root + 2 extra drives) --- +# Verifies that --image with multiple --extra-drive flags works with the async +# QMP pipeline. Three drives means three fdset allocations, three blockdev-add +# file nodes (each with io_uring retry), three blockdev-add format nodes, and +# three device_add commands — all pipelined without waiting for responses. + +MACHINE_MULTI="test-vmspawn-drives-$$" +systemd-vmspawn \ + --machine="$MACHINE_MULTI" \ + --ram=256M \ + --image="$WORKDIR/root.raw" \ + --extra-drive="$WORKDIR/extra1.raw" \ + --extra-drive="$WORKDIR/extra2.raw" \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + root=/dev/vda rw \ + &>"$WORKDIR/vmspawn-multi.log" & +VMSPAWN_MULTI_PID=$! + +wait_for_machine "$MACHINE_MULTI" "$VMSPAWN_MULTI_PID" "$WORKDIR/vmspawn-multi.log" +echo "Multi-drive machine '$MACHINE_MULTI' registered with machined" + +# Verify varlink control address is present and the VM is running +VARLINK_ADDR=$(varlinkctl call /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.List "{\"name\":\"$MACHINE_MULTI\"}" | jq -r '.controlAddress') +assert_neq "$VARLINK_ADDR" "null" + +STATUS=$(varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Describe '{}') +echo "$STATUS" | jq -e '.running == true' +echo "Multi-drive VM running — async QMP drive pipeline succeeded" + +# Verify no on_setup_complete failures in the vmspawn log +if grep -E '(add-fd|blockdev-add|blockdev-create|device_add|getfd|netdev_add|chardev-add) failed:' "$WORKDIR/vmspawn-multi.log"; then + echo "Full vmspawn log:" + cat "$WORKDIR/vmspawn-multi.log" + exit 1 +fi +echo "No QMP device setup errors in log" + +machinectl terminate "$MACHINE_MULTI" +timeout 10 bash -c "while machinectl status '$MACHINE_MULTI' &>/dev/null; do sleep .5; done" +timeout 10 bash -c "while kill -0 '$VMSPAWN_MULTI_PID' 2>/dev/null; do sleep .5; done" +echo "Multi-drive VM terminated cleanly" + +# --- Test 2: Ephemeral overlay (blockdev-create job continuation) --- +# Verifies that --image with --ephemeral works. This is the most complex async +# path: blockdev-create returns immediately, the qcow2 overlay is formatted in a +# background job, JOB_STATUS_CHANGE events are watched, and when the job +# concludes the deferred continuation fires blockdev-add (overlay format) + +# device_add. If any step fails, the root drive is never attached and the kernel +# panics — vmspawn exits without registering. + +MACHINE_EPHEMERAL="test-vmspawn-ephemeral-$$" +systemd-vmspawn \ + --machine="$MACHINE_EPHEMERAL" \ + --ram=256M \ + --image="$WORKDIR/root.raw" \ + --ephemeral \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + root=/dev/vda rw \ + &>"$WORKDIR/vmspawn-ephemeral.log" & +VMSPAWN_EPHEMERAL_PID=$! + +wait_for_machine "$MACHINE_EPHEMERAL" "$VMSPAWN_EPHEMERAL_PID" "$WORKDIR/vmspawn-ephemeral.log" +echo "Ephemeral machine '$MACHINE_EPHEMERAL' registered with machined" + +VARLINK_ADDR_E=$(varlinkctl call /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.List "{\"name\":\"$MACHINE_EPHEMERAL\"}" | jq -r '.controlAddress') +assert_neq "$VARLINK_ADDR_E" "null" + +STATUS_E=$(varlinkctl call "$VARLINK_ADDR_E" io.systemd.MachineInstance.Describe '{}') +echo "$STATUS_E" | jq -e '.running == true' +echo "Ephemeral VM running — blockdev-create job continuation succeeded" + +if grep -E '(add-fd|blockdev-add|blockdev-create|device_add|getfd|netdev_add|chardev-add) failed:' "$WORKDIR/vmspawn-ephemeral.log"; then + echo "Full vmspawn log:" + cat "$WORKDIR/vmspawn-ephemeral.log" + exit 1 +fi +echo "No QMP device setup errors in ephemeral log" + +machinectl terminate "$MACHINE_EPHEMERAL" +timeout 10 bash -c "while machinectl status '$MACHINE_EPHEMERAL' &>/dev/null; do sleep .5; done" +timeout 10 bash -c "while kill -0 '$VMSPAWN_EPHEMERAL_PID' 2>/dev/null; do sleep .5; done" +echo "Ephemeral VM terminated cleanly" + +echo "All vmspawn drive setup tests passed" diff --git a/test/units/TEST-87-AUX-UTILS-VM.vmspawn.sh b/test/units/TEST-87-AUX-UTILS-VM.vmspawn.sh new file mode 100755 index 0000000000000..52de5b2f208f9 --- /dev/null +++ b/test/units/TEST-87-AUX-UTILS-VM.vmspawn.sh @@ -0,0 +1,319 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# Test vmspawn QMP-varlink bridge and machinectl VM control verbs. +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +if [[ -v ASAN_OPTIONS ]]; then + echo "vmspawn launches QEMU which doesn't work under ASan, skipping" + exit 0 +fi + +if ! command -v systemd-vmspawn >/dev/null 2>&1; then + echo "systemd-vmspawn not found, skipping" + exit 0 +fi + +if ! find_qemu_binary; then + echo "QEMU not found, skipping" + exit 0 +fi + +# --directory= needs virtiofsd (on Fedora it lives in /usr/libexec, not in PATH) +if ! command -v virtiofsd >/dev/null 2>&1 && + ! test -x /usr/libexec/virtiofsd && + ! test -x /usr/lib/virtiofsd; then + echo "virtiofsd not found, skipping" + exit 0 +fi + +# Find a kernel for direct boot +KERNEL="" +for k in /usr/lib/modules/"$(uname -r)"/vmlinuz /boot/vmlinuz-"$(uname -r)" /boot/vmlinuz; do + if [[ -f "$k" ]]; then + KERNEL="$k" + break + fi +done + +if [[ -z "$KERNEL" ]]; then + echo "No kernel found for direct VM boot, skipping" + exit 0 +fi +echo "Using kernel: $KERNEL" + +MACHINE="test-vmspawn-qmp-$$" +WORKDIR="$(mktemp -d)" + +at_exit() { + set +e + + for m in "$MACHINE" "${MACHINE2:-}" "${STRESS_MACHINE:-}"; do + [[ -n "$m" ]] || continue + if machinectl status "$m" &>/dev/null; then + machinectl terminate "$m" 2>/dev/null + timeout 10 bash -c "while machinectl status '$m' &>/dev/null; do sleep .5; done" 2>/dev/null + fi + done + + [[ -n "${SUBSCRIBE_ALL_PID:-}" ]] && kill "$SUBSCRIBE_ALL_PID" 2>/dev/null && wait "$SUBSCRIBE_ALL_PID" 2>/dev/null + [[ -n "${SUBSCRIBE_FILTER_PID:-}" ]] && kill "$SUBSCRIBE_FILTER_PID" 2>/dev/null && wait "$SUBSCRIBE_FILTER_PID" 2>/dev/null + [[ -n "${STRESS_PID:-}" ]] && kill "$STRESS_PID" 2>/dev/null && wait "$STRESS_PID" 2>/dev/null + [[ -n "${VMSPAWN_PID:-}" ]] && kill "$VMSPAWN_PID" 2>/dev/null && wait "$VMSPAWN_PID" 2>/dev/null + [[ -n "${VMSPAWN2_PID:-}" ]] && kill "$VMSPAWN2_PID" 2>/dev/null && wait "$VMSPAWN2_PID" 2>/dev/null + rm -rf "$WORKDIR" +} +trap at_exit EXIT + +# Create a minimal root filesystem. The guest does not need to fully boot -- we only need QEMU running +# with QMP. A trivial init that sleeps is sufficient. +mkdir -p "$WORKDIR/root/sbin" +cat >"$WORKDIR/root/sbin/init" <<'EOF' +#!/bin/sh +exec sleep infinity +EOF +chmod +x "$WORKDIR/root/sbin/init" + +# Wait for a vmspawn machine to register with machined. +# Skips the test gracefully if vmspawn fails due to missing vhost-user-fs support (nested VM). +wait_for_machine() { + local machine="$1" pid="$2" log="$3" + timeout 30 bash -c " + while ! machinectl list --no-legend 2>/dev/null | grep >/dev/null '$machine'; do + if ! kill -0 $pid 2>/dev/null; then + if grep >/dev/null 'virtiofs.*QMP\|vhost-user-fs-pci' '$log'; then + echo 'vhost-user-fs not supported (nested VM?), skipping' + exit 77 + fi + echo 'vmspawn exited before registering' + cat '$log' + exit 1 + fi + sleep .5 + done + " || { + local rc=$? + if [[ $rc -eq 77 ]]; then exit 0; fi + exit "$rc" + } +} + +# Launch vmspawn in the background with direct kernel boot and headless console. +systemd-vmspawn \ + --machine="$MACHINE" \ + --ram=256M \ + --directory="$WORKDIR/root" \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + &>"$WORKDIR/vmspawn.log" & +VMSPAWN_PID=$! + +wait_for_machine "$MACHINE" "$VMSPAWN_PID" "$WORKDIR/vmspawn.log" +echo "Machine '$MACHINE' registered with machined" + +# Verify that controlAddress is present in Machine.List output +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List "{\"name\":\"$MACHINE\"}" | grep >/dev/null controlAddress +echo "controlAddress exposed in Machine.List" + +# Exercise the MachineInstance varlink interface directly via varlinkctl. +# Look up the varlink address from machined. Do this BEFORE machinectl poweroff since poweroff +# is destructive (either kills the machine via signal or sends ACPI shutdown). +VARLINK_ADDR=$(varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List "{\"name\":\"$MACHINE\"}" | jq -r '.controlAddress') +assert_neq "$VARLINK_ADDR" "null" + +# Describe should reflect a running VM +STATUS=$(varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Describe '{}') +echo "$STATUS" | jq -e '.running == true' +echo "$STATUS" | jq -e '.status == "running"' +echo "Describe returned running state" + +# Pause, verify, resume via varlinkctl +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Pause '{}' +STATUS=$(varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Describe '{}') +echo "$STATUS" | jq -e '.running == false' +echo "Verified paused state via Describe" + +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Resume '{}' +STATUS=$(varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Describe '{}') +echo "$STATUS" | jq -e '.running == true' +echo "Verified resumed state via Describe" + +# --- SubscribeEvents tests --- +# Subscribe to all events in the background, collect output +varlinkctl call --more --timeout=10 "$VARLINK_ADDR" io.systemd.MachineInstance.SubscribeEvents '{}' \ + >"$WORKDIR/events-all.json" 2>&1 & +SUBSCRIBE_ALL_PID=$! +sleep 0.5 + +# Trigger STOP + RESUME events via pause/resume +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Pause '{}' +sleep 0.2 +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Resume '{}' +sleep 0.5 + +# Kill the subscriber and check output +kill "$SUBSCRIBE_ALL_PID" 2>/dev/null; wait "$SUBSCRIBE_ALL_PID" 2>/dev/null || true +cat "$WORKDIR/events-all.json" + +# Verify initial READY event +grep >/dev/null '"READY"' "$WORKDIR/events-all.json" +echo "SubscribeEvents sent READY event" + +# Verify we got both STOP and RESUME events +grep >/dev/null '"STOP"' "$WORKDIR/events-all.json" +grep >/dev/null '"RESUME"' "$WORKDIR/events-all.json" +echo "SubscribeEvents received STOP and RESUME events" + +# Test filtered subscription: only STOP events +varlinkctl call --more --timeout=10 "$VARLINK_ADDR" io.systemd.MachineInstance.SubscribeEvents '{"filter":["STOP"]}' \ + >"$WORKDIR/events-filtered.json" 2>&1 & +SUBSCRIBE_FILTER_PID=$! +sleep 0.5 + +# Trigger both events again +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Pause '{}' +sleep 0.2 +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Resume '{}' +sleep 0.5 + +kill "$SUBSCRIBE_FILTER_PID" 2>/dev/null; wait "$SUBSCRIBE_FILTER_PID" 2>/dev/null || true +cat "$WORKDIR/events-filtered.json" + +# Should have STOP but not RESUME +grep >/dev/null '"STOP"' "$WORKDIR/events-filtered.json" +(! grep >/dev/null '"RESUME"' "$WORKDIR/events-filtered.json") +echo "Filtered subscription correctly received only STOP events" + +# Test machinectl pause/resume +machinectl pause "$MACHINE" +echo "machinectl pause succeeded" + +machinectl resume "$MACHINE" +echo "machinectl resume succeeded" + +# Test machinectl poweroff -- sends ACPI powerdown via QMP (system_powerdown). +# The guest won't handle it (our init is just 'sleep infinity'), but the QMP command should succeed. +machinectl poweroff "$MACHINE" +echo "machinectl poweroff succeeded" + +# --- Stress test: repeated start/pause/resume/terminate cycles --- +# Exercises the varlink disconnect path, QMP reconnection, and ref counting under repeated use. +# This catches use-after-free and double-close bugs that only manifest after multiple cycles. +machinectl terminate "$MACHINE" 2>/dev/null +timeout 10 bash -c "while machinectl status '$MACHINE' &>/dev/null; do sleep .5; done" 2>/dev/null +timeout 10 bash -c "while kill -0 '$VMSPAWN_PID' 2>/dev/null; do sleep .5; done" 2>/dev/null + +for i in $(seq 1 5); do + echo "Stress cycle $i/5" + + STRESS_MACHINE="test-vmspawn-stress-$i-$$" + systemd-vmspawn \ + --machine="$STRESS_MACHINE" \ + --ram=256M \ + --directory="$WORKDIR/root" \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + &>"$WORKDIR/vmspawn-stress.log" & + STRESS_PID=$! + + wait_for_machine "$STRESS_MACHINE" "$STRESS_PID" "$WORKDIR/vmspawn-stress.log" + + STRESS_ADDR=$(varlinkctl call /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.List "{\"name\":\"$STRESS_MACHINE\"}" | jq -r '.controlAddress') + assert_neq "$STRESS_ADDR" "null" + + # Rapid pause/resume/describe cycles + for _ in $(seq 1 3); do + machinectl pause "$STRESS_MACHINE" + varlinkctl call "$STRESS_ADDR" io.systemd.MachineInstance.Describe '{}' | jq -e '.running == false' >/dev/null + machinectl resume "$STRESS_MACHINE" + varlinkctl call "$STRESS_ADDR" io.systemd.MachineInstance.Describe '{}' | jq -e '.running == true' >/dev/null + done + + machinectl terminate "$STRESS_MACHINE" + timeout 10 bash -c "while machinectl status '$STRESS_MACHINE' &>/dev/null; do sleep .5; done" + timeout 10 bash -c "while kill -0 '$STRESS_PID' 2>/dev/null; do sleep .5; done" + echo "Stress cycle $i/5 passed" +done +echo "All stress cycles passed" + +# Restart a fresh VM for the remaining tests +systemd-vmspawn \ + --machine="$MACHINE" \ + --ram=256M \ + --directory="$WORKDIR/root" \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + &>"$WORKDIR/vmspawn.log" & +VMSPAWN_PID=$! + +wait_for_machine "$MACHINE" "$VMSPAWN_PID" "$WORKDIR/vmspawn.log" +VARLINK_ADDR=$(varlinkctl call /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.List "{\"name\":\"$MACHINE\"}" | jq -r '.controlAddress') +assert_neq "$VARLINK_ADDR" "null" + +# --- Parallel multi-machine dispatch tests --- +# Launch a second VM to test machinectl operating on multiple machines simultaneously. +# Use a separate rootfs so each VM gets independent sidecar state (TPM, EFI NVRAM). +MACHINE2="test-vmspawn-qmp2-$$" + +mkdir -p "$WORKDIR/root2/sbin" +cp "$WORKDIR/root/sbin/init" "$WORKDIR/root2/sbin/init" + +systemd-vmspawn \ + --machine="$MACHINE2" \ + --ram=256M \ + --directory="$WORKDIR/root2" \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + &>"$WORKDIR/vmspawn2.log" & +VMSPAWN2_PID=$! + +wait_for_machine "$MACHINE2" "$VMSPAWN2_PID" "$WORKDIR/vmspawn2.log" +echo "Second machine '$MACHINE2' registered" + +VARLINK_ADDR2=$(varlinkctl call /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.List "{\"name\":\"$MACHINE2\"}" | jq -r '.controlAddress') +assert_neq "$VARLINK_ADDR2" "null" + +# Parallel pause: both machines at once +machinectl pause "$MACHINE" "$MACHINE2" +echo "Parallel pause of two machines succeeded" + +# Verify both are paused +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Describe '{}' | jq -e '.running == false' +varlinkctl call "$VARLINK_ADDR2" io.systemd.MachineInstance.Describe '{}' | jq -e '.running == false' +echo "Both machines verified paused" + +# Parallel resume +machinectl resume "$MACHINE" "$MACHINE2" +echo "Parallel resume of two machines succeeded" + +# Verify both resumed +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Describe '{}' | jq -e '.running == true' +varlinkctl call "$VARLINK_ADDR2" io.systemd.MachineInstance.Describe '{}' | jq -e '.running == true' +echo "Both machines verified running" + +# --- Terminate and verify cleanup --- +# Parallel terminate: both machines at once (QMP quit) +machinectl terminate "$MACHINE" "$MACHINE2" +timeout 10 bash -c " + while machinectl status '$MACHINE' &>/dev/null || machinectl status '$MACHINE2' &>/dev/null; do + sleep .5 + done +" +echo "Parallel terminate succeeded, both VMs gone" + +# Both vmspawn processes should have exited +timeout 10 bash -c "while kill -0 '$VMSPAWN_PID' 2>/dev/null; do sleep .5; done" +timeout 10 bash -c "while kill -0 '$VMSPAWN2_PID' 2>/dev/null; do sleep .5; done" +echo "Both vmspawn processes exited" + +echo "All vmspawn QMP-varlink bridge tests passed" diff --git a/test/units/TEST-88-UPGRADE.sh b/test/units/TEST-88-UPGRADE.sh index faf3e3748ee8f..5cc1df21aae7b 100755 --- a/test/units/TEST-88-UPGRADE.sh +++ b/test/units/TEST-88-UPGRADE.sh @@ -84,7 +84,7 @@ timer2=$(systemctl show -P NextElapseUSecRealtime upgrade_timer_test.timer) # FIXME: See https://github.com/systemd/systemd/pull/39293 systemctl stop systemd-networkd-resolve-hook.socket || true -dnf downgrade --no-gpgchecks -y --allowerasing --disablerepo '*' "$pkgdir"/distro/*.rpm +dnf downgrade --nogpgcheck -y --allowerasing --disablerepo '*' "$pkgdir"/distro/*.rpm # Some distros don't ship networkd, so the test will always fail if command -v networkctl >/dev/null; then @@ -105,7 +105,7 @@ fi check_sd # Finally test the upgrade -dnf -y upgrade --no-gpgchecks --disablerepo '*' "$pkgdir"/devel/*.rpm +dnf -y upgrade --nogpgcheck --disablerepo '*' "$pkgdir"/devel/*.rpm # TODO: sanity checks check_sd diff --git a/test/units/TEST-90-CLONESETUP.sh b/test/units/TEST-90-CLONESETUP.sh new file mode 100755 index 0000000000000..757916c4c2dc4 --- /dev/null +++ b/test/units/TEST-90-CLONESETUP.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Test clonesetup generator and systemd-clonesetup + +at_exit() { + set +e + + rm -f /etc/clonetab + [[ -e /tmp/clonetab.bak ]] && cp -fv /tmp/clonetab.bak /etc/clonetab + [[ -n "${LOOP_SRC:-}" ]] && losetup -d "$LOOP_SRC" + [[ -n "${LOOP_DST:-}" ]] && losetup -d "$LOOP_DST" + [[ -n "${LOOP_META:-}" ]] && losetup -d "$LOOP_META" + [[ -n "${WORKDIR:-}" ]] && rm -rf "$WORKDIR" + dmsetup remove testclonesetup 2>/dev/null || true + + systemctl daemon-reload +} + +trap at_exit EXIT + +clonesetup_start_and_check() { + local volume unit + + volume="${1:?}" + unit="systemd-clonesetup@$volume.service" + + # The unit existence check should always pass + [[ "$(systemctl show -P LoadState "$unit")" == loaded ]] + systemctl list-unit-files "$unit" + + systemctl start "$unit" + systemctl status "$unit" + test -e "/dev/mapper/$volume" + dmsetup status "$volume" + + systemctl stop "$unit" + # wait for udev to finish processing so the device node state is in sync + # before the API returns. + udevadm settle --timeout=10 + test ! -e "/dev/mapper/$volume" +} + +prereq() { + # Skip when kernel lacks dm-clone (CONFIG_DM_CLONE) + modprobe dm_clone 2>/dev/null || true + if [[ ! -d /sys/module/dm_clone ]]; then + echo "no dm-clone" >/skipped + exit 77 + fi + echo "Found required kernel module: dm_clone" +} + +prereq + +# Use a common workdir +WORKDIR="$(mktemp -d)" + +# Create test images for source, destination, and metadata +IMG_SRC="$WORKDIR/source.img" +IMG_DST="$WORKDIR/dest.img" +IMG_META="$WORKDIR/meta.img" + +truncate -s 32M "$IMG_SRC" +truncate -s 32M "$IMG_DST" +truncate -s 8M "$IMG_META" + +# Set up loop devices +LOOP_SRC="$(losetup --show --find "$IMG_SRC")" +LOOP_DST="$(losetup --show --find "$IMG_DST")" +LOOP_META="$(losetup --show --find "$IMG_META")" + +udevadm settle --timeout=60 + +# Backup existing clonetab if any +[[ -e /etc/clonetab ]] && cp -fv /etc/clonetab /tmp/clonetab.bak + +# Create test clonetab +cat >/etc/clonetab <"$initdir/usr/lib/systemd/system/other_file" mksquashfs "$initdir" /tmp/app1.raw -noappend + # Create a data-only extension image (no unit files) to test that + # ExtensionImages= is added to the drop-in even when the extension + # does not carry any units. + initdir="/var/tmp/app-data-only" + mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/opt" + ( + echo "ID=_any" + echo "ARCHITECTURE=_any" + ) >"$initdir/usr/lib/extension-release.d/extension-release.app-data-only" + echo "MARKER_DATA_ONLY=1" >"$initdir/opt/data-file" + mksquashfs "$initdir" /tmp/app-data-only.raw -noappend + initdir="/var/tmp/app-nodistro" mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" ( @@ -513,3 +525,36 @@ check_nss_module() ( return 0 ) + +find_qemu_binary() { + # Mirrors find_qemu_binary() from src/vmspawn/vmspawn-util.c. + # Returns 0 if a usable QEMU binary exists, 1 otherwise. + for binary in qemu qemu-kvm; do + if command -v "$binary" >/dev/null 2>&1; then + return 0 + fi + done + + if test -x /usr/libexec/qemu-kvm; then + return 0 + fi + + local arch + case "$(uname -m)" in + x86_64) arch=x86_64 ;; + i?86) arch=i386 ;; + aarch64) arch=aarch64 ;; + armv*l|arm*) arch=arm ;; + alpha) arch=alpha ;; + loongarch64) arch=loongarch64 ;; + mips*) arch=mips ;; + parisc*) arch=hppa ;; + ppc64*|ppc*) arch=ppc ;; + riscv32) arch=riscv32 ;; + riscv64) arch=riscv64 ;; + s390x) arch=s390x ;; + *) return 1 ;; + esac + + command -v "qemu-system-$arch" >/dev/null 2>&1 +} diff --git a/tmpfiles.d/20-systemd-stub.conf.in b/tmpfiles.d/20-systemd-stub.conf.in index 512f39a3e9f61..916c9e503be3b 100644 --- a/tmpfiles.d/20-systemd-stub.conf.in +++ b/tmpfiles.d/20-systemd-stub.conf.in @@ -12,6 +12,7 @@ C /run/systemd/stub/profile 0444 root root - /.extra/profile C /run/systemd/stub/os-release 0444 root root - /.extra/os-release +C /run/systemd/stub/boot-secret 0400 root root - /.extra/boot-secret {% if ENABLE_TPM %} C /run/systemd/tpm2-pcr-signature.json 0444 root root - /.extra/tpm2-pcr-signature.json diff --git a/tmpfiles.d/meson.build b/tmpfiles.d/meson.build index c8f9015b2ecc8..83839dd627f90 100644 --- a/tmpfiles.d/meson.build +++ b/tmpfiles.d/meson.build @@ -6,6 +6,7 @@ endif files = [['README' ], ['home.conf' ], + ['root.conf' ], ['journal-nocow.conf' ], ['portables.conf', 'ENABLE_PORTABLED'], ['systemd-network.conf', 'ENABLE_NETWORKD' ], diff --git a/tmpfiles.d/root.conf b/tmpfiles.d/root.conf new file mode 100644 index 0000000000000..9db0aaa92eda4 --- /dev/null +++ b/tmpfiles.d/root.conf @@ -0,0 +1,10 @@ +# This file is part of systemd. +# +# systemd 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. + +# See tmpfiles.d(5) for details. + +z / 555 - - - diff --git a/tools/check-coccinelle.sh b/tools/check-coccinelle.sh new file mode 100755 index 0000000000000..8a436624c97e7 --- /dev/null +++ b/tools/check-coccinelle.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eu +set -o pipefail + +SRC_DIR="${1:?}" +COCCI_DIR="${2:?}" + +FOUND=0 + +for cocci in "$COCCI_DIR"/check-*.cocci; do + [[ -f "$cocci" ]] || continue + output=$(spatch --very-quiet --macro-file-builtins "$COCCI_DIR/parsing_hacks.h" --sp-file "$cocci" --dir "$SRC_DIR" 2>&1) + if [[ -n "$output" ]]; then + echo "FAIL: $(basename "$cocci") found issues in $SRC_DIR:" + echo "$output" + FOUND=1 + fi +done + +if [[ "$FOUND" -ne 0 ]]; then + echo "" + echo "Coccinelle check(s) failed. For each flagged dereference, either:" + echo " - Add assert(param)/ASSERT_PTR(param) at the top of the function (if the parameter must not be NULL)" + echo " - Add an if (param) guard before the dereference (if NULL is valid)" + echo " - Add POINTER_MAY_BE_NULL(param) if NULL is okay for param" + exit 1 +fi diff --git a/tools/meson.build b/tools/meson.build index 3132eeddba51f..e8b3133d9c8ca 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later check_api_docs_sh = files('check-api-docs.sh') +check_coccinelle_sh = files('check-coccinelle.sh') check_efi_alignment_py = files('check-efi-alignment.py') check_help_sh = files('check-help.sh') check_version_history_py = files('check-version-history.py') diff --git a/units/clonesetup.target b/units/clonesetup.target new file mode 100644 index 0000000000000..21ca6dddfa4e6 --- /dev/null +++ b/units/clonesetup.target @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. + +[Unit] +Description=Local dm-clone Devices +Documentation=man:systemd.special(7) diff --git a/units/meson.build b/units/meson.build index b2cf9bd8f39ce..53ef1d5576822 100644 --- a/units/meson.build +++ b/units/meson.build @@ -24,6 +24,10 @@ units = [ 'conditions' : ['HAVE_LIBCRYPTSETUP'], 'symlinks' : ['sysinit.target.wants/'], }, + { + 'file' : 'clonesetup.target', + 'symlinks' : ['sysinit.target.wants/'], + }, { 'file' : 'debug-shell.service.in' }, { 'file' : 'dev-hugepages.mount', @@ -311,6 +315,10 @@ units = [ 'conditions' : ['ENABLE_INITRD', 'ENABLE_SYSEXT'], 'symlinks' : ['initrd.target.wants/'], }, + { + 'file' : 'systemd-confext-sysroot.service', + 'conditions' : ['ENABLE_INITRD', 'ENABLE_SYSEXT'], + }, { 'file' : 'systemd-coredump.socket', 'conditions' : ['ENABLE_COREDUMP'], @@ -392,6 +400,22 @@ units = [ 'file' : 'systemd-hybrid-sleep.service.in', 'conditions' : ['ENABLE_HIBERNATE'], }, + { + 'file' : 'systemd-imdsd@.service.in', + 'conditions' : ['ENABLE_IMDS'], + }, + { + 'file' : 'systemd-imdsd.socket', + 'conditions' : ['ENABLE_IMDS'], + }, + { + 'file' : 'systemd-imds-early-network.service.in', + 'conditions' : ['ENABLE_IMDS'], + }, + { + 'file' : 'systemd-imds-import.service.in', + 'conditions' : ['ENABLE_IMDS'], + }, { 'file' : 'systemd-importd.service.in', 'conditions' : ['ENABLE_IMPORTD'], @@ -568,6 +592,11 @@ units = [ 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], 'symlinks' : ['sysinit.target.wants/'], }, + { + 'file' : 'systemd-pcrosseparator.service.in', + 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], + 'symlinks' : ['sysinit.target.wants/'], + }, { 'file' : 'systemd-pcrphase-factory-reset.service.in', 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], @@ -603,6 +632,8 @@ units = [ 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], 'symlinks' : ['sysinit.target.wants/'], }, + { 'file' : 'systemd-report-basic.socket' }, + { 'file' : 'systemd-report-basic@.service.in' }, { 'file' : 'systemd-tpm2-clear.service.in', 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], @@ -617,6 +648,10 @@ units = [ 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], 'symlinks' : ['sysinit.target.wants/'], }, + { + 'file' : 'systemd-tpm2-swtpm.service.in', + 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], + }, { 'file' : 'systemd-pcrlock-make-policy.service.in', 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], @@ -693,6 +728,8 @@ units = [ 'file' : 'systemd-repart@.service', 'conditions' : ['ENABLE_REPART'], }, + { 'file' : 'systemd-report-cgroup.socket' }, + { 'file' : 'systemd-report-cgroup@.service.in' }, { 'file' : 'systemd-resolved.service.in', 'conditions' : ['ENABLE_RESOLVE'], @@ -732,6 +769,10 @@ units = [ 'conditions' : ['ENABLE_INITRD', 'ENABLE_SYSEXT'], 'symlinks' : ['initrd.target.wants/'], }, + { + 'file' : 'systemd-sysext-sysroot.service', + 'conditions' : ['ENABLE_INITRD', 'ENABLE_SYSEXT'], + }, { 'file' : 'systemd-sysext.socket', 'conditions' : ['ENABLE_SYSEXT'], diff --git a/units/system-update-cleanup.service b/units/system-update-cleanup.service index a54e74567e1fe..e9ff88c73c87c 100644 --- a/units/system-update-cleanup.service +++ b/units/system-update-cleanup.service @@ -34,4 +34,4 @@ ConditionPathIsSymbolicLink=|/etc/system-update [Service] Type=oneshot -ExecStart=rm -fv /system-update /etc/system-update +ExecStart=systemd-tmpfiles --inline --remove 'r /system-update' 'r /etc/system-update' diff --git a/units/systemd-confext-initrd.service b/units/systemd-confext-initrd.service index 073307edcce7f..9984bd7065217 100644 --- a/units/systemd-confext-initrd.service +++ b/units/systemd-confext-initrd.service @@ -8,7 +8,7 @@ # (at your option) any later version. [Unit] -Description=Merge System Configuration Images into /etc/ +Description=Merge System Configuration Images into /etc/ of the initrd Documentation=man:systemd-confext-initrd.service(8) ConditionCapability=CAP_SYS_ADMIN @@ -19,6 +19,7 @@ ConditionDirectoryNotEmpty=|/usr/lib/confexts ConditionDirectoryNotEmpty=|/.extra/confext ConditionDirectoryNotEmpty=|/.extra/global_confext ConditionPathExists=/etc/initrd-release +ConditionKernelCommandLine=!rd.systemd.confext=0 DefaultDependencies=no Before=local-fs-pre.target cryptsetup-pre.target systemd-tmpfiles-setup.service diff --git a/units/systemd-confext-sysroot.service b/units/systemd-confext-sysroot.service new file mode 100644 index 0000000000000..e30193e17e4dc --- /dev/null +++ b/units/systemd-confext-sysroot.service @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. + +[Unit] +Description=Merge System Configuration Images into /sysroot/etc/ +Documentation=man:systemd-confext-sysroot.service(8) + +ConditionCapability=CAP_SYS_ADMIN +ConditionDirectoryNotEmpty=|/sysroot/var/lib/confexts +ConditionDirectoryNotEmpty=|/sysroot/usr/local/lib/confexts +ConditionDirectoryNotEmpty=|/sysroot/usr/lib/confexts +ConditionPathExists=/etc/initrd-release +ConditionKernelCommandLine=!systemd.confext=0 + +DefaultDependencies=no +Conflicts=shutdown.target +Before=initrd-root-fs.target shutdown.target +Wants=modprobe@loop.service modprobe@dm_mod.service +After=modprobe@loop.service modprobe@dm_mod.service sysroot.mount sysroot-usr.mount systemd-volatile-root.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=systemd-confext --root=/sysroot refresh + +[Install] +WantedBy=initrd.target diff --git a/units/systemd-confext.service b/units/systemd-confext.service index e509036d03599..ffbf8345b8d6a 100644 --- a/units/systemd-confext.service +++ b/units/systemd-confext.service @@ -17,6 +17,7 @@ ConditionDirectoryNotEmpty=|/var/lib/confexts ConditionDirectoryNotEmpty=|/usr/local/lib/confexts ConditionDirectoryNotEmpty=|/usr/lib/confexts ConditionPathExists=!/etc/initrd-release +ConditionKernelCommandLine=!systemd.confext=0 DefaultDependencies=no After=local-fs.target diff --git a/units/systemd-hostnamed.service.in b/units/systemd-hostnamed.service.in index ab00c24b53b27..9bc58f4c13437 100644 --- a/units/systemd-hostnamed.service.in +++ b/units/systemd-hostnamed.service.in @@ -13,6 +13,9 @@ Documentation=man:systemd-hostnamed.service(8) Documentation=man:hostname(5) Documentation=man:machine-info(5) Documentation=man:org.freedesktop.hostname1(5) +DefaultDependencies=no +Conflicts=shutdown.target +Before=shutdown.target [Service] Type=notify diff --git a/units/systemd-hostnamed.socket b/units/systemd-hostnamed.socket index 288e736b47134..f84853ade8af2 100644 --- a/units/systemd-hostnamed.socket +++ b/units/systemd-hostnamed.socket @@ -12,6 +12,9 @@ Description=Hostname Service Socket Documentation=man:systemd-hostnamed.service(8) Documentation=man:hostname(5) Documentation=man:machine-info(5) +DefaultDependencies=no +Conflicts=shutdown.target +Before=shutdown.target sockets.target [Socket] ListenStream=/run/systemd/io.systemd.Hostname diff --git a/units/systemd-imds-early-network.service.in b/units/systemd-imds-early-network.service.in new file mode 100644 index 0000000000000..b4241237f0983 --- /dev/null +++ b/units/systemd-imds-early-network.service.in @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. + +[Unit] +Description=Enable Pre-IMDS Networking +Documentation=man:systemd-imdsd@.service(8) +DefaultDependencies=no +Before=network-pre.target +Wants=network-pre.target +Conflicts=shutdown.target initrd-switch-root.target +Before=shutdown.target initrd-switch-root.target +After=sys-devices-virtual-dmi-id.device + +[Service] +ExecStart={{LIBEXECDIR}}/systemd-imdsd --setup-network +Type=oneshot +RemainAfterExit=yes diff --git a/units/systemd-imds-import.service.in b/units/systemd-imds-import.service.in new file mode 100644 index 0000000000000..9704557fbb5bf --- /dev/null +++ b/units/systemd-imds-import.service.in @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. + +[Unit] +Description=Import System Credentials from IMDS +Documentation=man:systemd-imds(1) +Documentation=man:systemd.system-credentials(7) +DefaultDependencies=no +Wants=systemd-imdsd.socket network-online.target +After=systemd-imdsd.socket network-online.target +Before=sysinit.target systemd-firstboot.service +Conflicts=shutdown.target +Before=shutdown.target +ConditionPathExists=/etc/initrd-release + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart={{LIBEXECDIR}}/systemd-imds --import diff --git a/units/systemd-imdsd.socket b/units/systemd-imdsd.socket new file mode 100644 index 0000000000000..daeb7840b3ec0 --- /dev/null +++ b/units/systemd-imdsd.socket @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. + +[Unit] +Description=Cloud Instance Metadata Access (IMDS) +Documentation=man:systemd-imdsd@.service(8) +DefaultDependencies=no +Before=sockets.target + +[Socket] +ListenStream=/run/systemd/io.systemd.InstanceMetadata +Symlinks=/run/varlink/registry/io.systemd.InstanceMetadata +FileDescriptorName=varlink +SocketMode=0666 +Accept=yes +MaxConnectionsPerSource=16 +RemoveOnStop=yes + +# Note that this is typically pulled in automatically by +# systemd-imds-generator, but you can also enable it manually if you like. +[Install] +WantedBy=sockets.target diff --git a/units/systemd-imdsd@.service.in b/units/systemd-imdsd@.service.in new file mode 100644 index 0000000000000..49001cb6264a2 --- /dev/null +++ b/units/systemd-imdsd@.service.in @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. + +[Unit] +Description=Cloud Instance Metadata Access (IMDS) +Documentation=man:systemd-imdsd@.service(8) +DefaultDependencies=no +Conflicts=shutdown.target initrd-switch-root.target +Before=shutdown.target initrd-switch-root.target +After=sys-devices-virtual-dmi-id.device + +[Service] +ExecStart=-{{LIBEXECDIR}}/systemd-imdsd +User=systemd-imds +RuntimeDirectory=systemd/imds +RuntimeDirectoryPreserve=yes +# CAP_NET_ADMIN is required to set SO_FWMARK and bypass the routing restrictions, and CAP_NET_BIND_SERVICE to bind to a low port +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE +LockPersonality=yes +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +ImportCredential=imds.* diff --git a/units/systemd-logind-varlink.socket b/units/systemd-logind-varlink.socket index 1d3652e049742..377eac7006fdf 100644 --- a/units/systemd-logind-varlink.socket +++ b/units/systemd-logind-varlink.socket @@ -13,7 +13,7 @@ Documentation=man:systemd-logind.service(8) [Socket] ListenStream=/run/systemd/io.systemd.Login -Symlinks=/run/varlink/registry/io.systemd.Login +Symlinks=/run/varlink/registry/io.systemd.Login /run/varlink/registry/io.systemd.Shutdown /run/systemd/io.systemd.Shutdown FileDescriptorName=varlink SocketMode=0666 Service=systemd-logind.service diff --git a/units/systemd-mountfsd.service.in b/units/systemd-mountfsd.service.in index 73105007f925f..1e996a0def832 100644 --- a/units/systemd-mountfsd.service.in +++ b/units/systemd-mountfsd.service.in @@ -18,7 +18,7 @@ Before=sysinit.target shutdown.target DefaultDependencies=no [Service] -CapabilityBoundingSet=CAP_DAC_READ_SEARCH CAP_SYS_RESOURCE CAP_BPF CAP_PERFMON CAP_SETGID CAP_SETUID CAP_DAC_OVERRIDE CAP_CHOWN CAP_SYS_ADMIN +CapabilityBoundingSet=CAP_DAC_READ_SEARCH CAP_SYS_RESOURCE CAP_BPF CAP_PERFMON CAP_SETGID CAP_SETUID CAP_DAC_OVERRIDE CAP_CHOWN CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_SYS_CHROOT ExecStart={{LIBEXECDIR}}/systemd-mountfsd IPAddressDeny=any LimitNOFILE={{HIGH_RLIMIT_NOFILE}} diff --git a/units/systemd-networkd-resolve-hook.socket b/units/systemd-networkd-resolve-hook.socket index 3c11b8e8de1c2..8a724bbc0c0d4 100644 --- a/units/systemd-networkd-resolve-hook.socket +++ b/units/systemd-networkd-resolve-hook.socket @@ -12,6 +12,7 @@ Description=Network Management Resolve Hook Socket Documentation=man:systemd-networkd.service(8) ConditionCapability=CAP_NET_ADMIN DefaultDependencies=no +After=network-pre.target Before=sockets.target shutdown.target Conflicts=shutdown.target diff --git a/units/systemd-pcrextend.socket b/units/systemd-pcrextend.socket index d429150eda0d7..0f4ab11e2fd3c 100644 --- a/units/systemd-pcrextend.socket +++ b/units/systemd-pcrextend.socket @@ -13,7 +13,7 @@ Documentation=man:systemd-pcrextend(8) DefaultDependencies=no After=tpm2.target Before=sockets.target -ConditionSecurity=measured-uki +ConditionSecurity=measured-os [Socket] ListenStream=/run/systemd/io.systemd.PCRExtend diff --git a/units/systemd-pcrfs-root.service.in b/units/systemd-pcrfs-root.service.in index f774c4c8bf6bf..88551d7ed0893 100644 --- a/units/systemd-pcrfs-root.service.in +++ b/units/systemd-pcrfs-root.service.in @@ -15,7 +15,7 @@ Conflicts=shutdown.target After=tpm2.target systemd-pcrmachine.service Before=shutdown.target ConditionPathExists=!/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrfs@.service.in b/units/systemd-pcrfs@.service.in index 3d18fe4d30e16..38cc41976f66c 100644 --- a/units/systemd-pcrfs@.service.in +++ b/units/systemd-pcrfs@.service.in @@ -16,7 +16,7 @@ Conflicts=shutdown.target After=%i.mount tpm2.target systemd-pcrfs-root.service Before=shutdown.target ConditionPathExists=!/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrmachine.service.in b/units/systemd-pcrmachine.service.in index ea2561ef79e3f..d97afa696554d 100644 --- a/units/systemd-pcrmachine.service.in +++ b/units/systemd-pcrmachine.service.in @@ -15,7 +15,7 @@ Conflicts=shutdown.target After=tpm2.target Before=sysinit.target shutdown.target ConditionPathExists=!/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrnvdone.service.in b/units/systemd-pcrnvdone.service.in index e0dd9a8820988..bbd0e66e605ce 100644 --- a/units/systemd-pcrnvdone.service.in +++ b/units/systemd-pcrnvdone.service.in @@ -13,8 +13,8 @@ Documentation=man:systemd-pcrnvdone.service(8) DefaultDependencies=no Conflicts=shutdown.target After=systemd-tpm2-setup-early.service systemd-tpm2-setup.service -Before=sysinit.target shutdown.target -ConditionSecurity=measured-uki +Before=sysinit.target cryptsetup-pre.target cryptsetup.target shutdown.target +ConditionSecurity=measured-os ConditionPathExists=!/etc/initrd-release FailureAction=reboot-force diff --git a/units/systemd-pcrosseparator.service.in b/units/systemd-pcrosseparator.service.in new file mode 100644 index 0000000000000..c3aec1ab91995 --- /dev/null +++ b/units/systemd-pcrosseparator.service.in @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. + +[Unit] +Description=TPM PCR OS Separator +Documentation=man:systemd-pcrosseparator.service(8) +DefaultDependencies=no +Conflicts=shutdown.target initrd-switch-root.target +After=tpm2.target +Before=sysinit.target shutdown.target initrd-switch-root.target cryptsetup-pre.target cryptsetup.target +ConditionPathExists=/etc/initrd-release +ConditionSecurity=measured-os +FailureAction=reboot-force + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart={{LIBEXECDIR}}/systemd-pcrextend --graceful \ + --pcr=0 --pcr=1 --pcr=2 --pcr=3 --pcr=4 --pcr=5 \ + --pcr=6 --pcr=7 --pcr=9 --pcr=12 --pcr=13 --pcr=14 \ + --event-type=os-separator \ + os-separator diff --git a/units/systemd-pcrphase-factory-reset.service.in b/units/systemd-pcrphase-factory-reset.service.in index 5dbcb0f53f160..2efd8830d3210 100644 --- a/units/systemd-pcrphase-factory-reset.service.in +++ b/units/systemd-pcrphase-factory-reset.service.in @@ -14,7 +14,7 @@ DefaultDependencies=no Conflicts=shutdown.target After=tpm2.target Before=shutdown.target factory-reset.target -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrphase-initrd.service.in b/units/systemd-pcrphase-initrd.service.in index 5aba32128c012..cbb833147018d 100644 --- a/units/systemd-pcrphase-initrd.service.in +++ b/units/systemd-pcrphase-initrd.service.in @@ -15,7 +15,7 @@ Conflicts=shutdown.target initrd-switch-root.target After=tpm2.target Before=sysinit.target cryptsetup-pre.target cryptsetup.target shutdown.target initrd-switch-root.target systemd-sysext.service ConditionPathExists=/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrphase-storage-target-mode.service.in b/units/systemd-pcrphase-storage-target-mode.service.in index 52b53e5b819a8..b4330c560f5bb 100644 --- a/units/systemd-pcrphase-storage-target-mode.service.in +++ b/units/systemd-pcrphase-storage-target-mode.service.in @@ -15,7 +15,7 @@ Conflicts=shutdown.target After=tpm2.target Before=shutdown.target ConditionPathExists=/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrphase-sysinit.service.in b/units/systemd-pcrphase-sysinit.service.in index 4a01279159d93..aa4d36409813a 100644 --- a/units/systemd-pcrphase-sysinit.service.in +++ b/units/systemd-pcrphase-sysinit.service.in @@ -15,7 +15,7 @@ Conflicts=shutdown.target After=sysinit.target tpm2.target Before=basic.target shutdown.target ConditionPathExists=!/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrphase.service.in b/units/systemd-pcrphase.service.in index 43459a2fccba0..b2f925d40f46b 100644 --- a/units/systemd-pcrphase.service.in +++ b/units/systemd-pcrphase.service.in @@ -13,7 +13,7 @@ Documentation=man:systemd-pcrphase.service(8) After=remote-fs.target remote-cryptsetup.target tpm2.target Before=systemd-user-sessions.service ConditionPathExists=!/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrproduct.service.in b/units/systemd-pcrproduct.service.in index 09e446c2a01b0..1b121416a9423 100644 --- a/units/systemd-pcrproduct.service.in +++ b/units/systemd-pcrproduct.service.in @@ -12,11 +12,11 @@ Description=TPM NvPCR Product ID Measurement Documentation=man:systemd-pcrproduct.service(8) DefaultDependencies=no Conflicts=shutdown.target -After=tpm2.target +After=tpm2.target systemd-pcrnvdone.service Before=sysinit.target shutdown.target RequiresMountsFor=/var/lib/systemd/nvpcr ConditionPathExists=!/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os [Service] Type=oneshot diff --git a/units/systemd-report-basic.socket b/units/systemd-report-basic.socket new file mode 100644 index 0000000000000..bfa4ea72568fe --- /dev/null +++ b/units/systemd-report-basic.socket @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. +[Unit] +Description=Report System Basic Facts Socket +DefaultDependencies=no +Before=sockets.target + +[Socket] +ListenStream=/run/systemd/report/io.systemd.Basic +FileDescriptorName=varlink +SocketMode=0666 +Accept=yes +MaxConnectionsPerSource=16 +RemoveOnStop=yes + +[Install] +WantedBy=sockets.target diff --git a/units/systemd-report-basic@.service.in b/units/systemd-report-basic@.service.in new file mode 100644 index 0000000000000..043324b5c3987 --- /dev/null +++ b/units/systemd-report-basic@.service.in @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. +[Unit] +Description=Report System Basic Facts + +DefaultDependencies=no +Conflicts=shutdown.target +Before=shutdown.target + +[Service] +CapabilityBoundingSet= +DeviceAllow= +DynamicUser=yes +LockPersonality=yes +MemoryDenyWriteExecute=yes +PrivateDevices=yes +PrivateIPC=yes +PrivateNetwork=yes +PrivateTmp=disconnected +ProtectControlGroups=yes +ProtectHome=yes +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectSystem=strict +RestrictAddressFamilies=AF_UNIX +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +SystemCallArchitectures=native +SystemCallErrorNumber=EPERM +SystemCallFilter=@system-service + +ExecStart={{LIBEXECDIR}}/systemd-report-basic diff --git a/units/systemd-report-cgroup.socket b/units/systemd-report-cgroup.socket new file mode 100644 index 0000000000000..39a867cd40c85 --- /dev/null +++ b/units/systemd-report-cgroup.socket @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. + +[Unit] +Description=CGroup Report Varlink Socket +DefaultDependencies=no +Before=sockets.target shutdown.target +Conflicts=shutdown.target + +[Socket] +ListenStream=/run/systemd/report/io.systemd.CGroup +FileDescriptorName=varlink +SocketMode=0666 +Accept=yes +MaxConnectionsPerSource=16 +RemoveOnStop=yes + +[Install] +WantedBy=sockets.target diff --git a/units/systemd-report-cgroup@.service.in b/units/systemd-report-cgroup@.service.in new file mode 100644 index 0000000000000..6f18c647dd248 --- /dev/null +++ b/units/systemd-report-cgroup@.service.in @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. + +[Unit] +Description=CGroup Report Service +DefaultDependencies=no +Conflicts=shutdown.target +Before=shutdown.target + +[Service] +CapabilityBoundingSet= +DeviceAllow= +DynamicUser=yes +IPAddressDeny=any +LockPersonality=yes +MemoryDenyWriteExecute=yes +PrivateDevices=yes +PrivateIPC=yes +PrivateNetwork=yes +PrivateTmp=disconnected +ProtectControlGroups=yes +ProtectHome=yes +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectSystem=strict +RestrictAddressFamilies=AF_UNIX +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +RuntimeMaxSec=1min +SystemCallArchitectures=native +SystemCallErrorNumber=EPERM +SystemCallFilter=@system-service +ExecStart={{LIBEXECDIR}}/systemd-report-cgroup diff --git a/units/systemd-sysext-initrd.service b/units/systemd-sysext-initrd.service index 4a411bb65e0ef..2d9fb59cf2900 100644 --- a/units/systemd-sysext-initrd.service +++ b/units/systemd-sysext-initrd.service @@ -8,7 +8,7 @@ # (at your option) any later version. [Unit] -Description=Merge System Extension Images into /usr/ and /opt/ +Description=Merge System Extension Images into /usr/ and /opt/ of the initrd Documentation=man:systemd-sysext-initrd.service(8) ConditionCapability=CAP_SYS_ADMIN @@ -18,6 +18,7 @@ ConditionDirectoryNotEmpty=|/var/lib/extensions ConditionDirectoryNotEmpty=|/.extra/sysext ConditionDirectoryNotEmpty=|/.extra/global_sysext ConditionPathExists=/etc/initrd-release +ConditionKernelCommandLine=!rd.systemd.sysext=0 DefaultDependencies=no Before=local-fs-pre.target cryptsetup-pre.target systemd-tmpfiles-setup.service diff --git a/units/systemd-sysext-sysroot.service b/units/systemd-sysext-sysroot.service new file mode 100644 index 0000000000000..d68c70da69127 --- /dev/null +++ b/units/systemd-sysext-sysroot.service @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. + +[Unit] +Description=Merge System Extension Images into /sysroot/usr/ and /sysroot/opt/ +Documentation=man:systemd-sysext-sysroot.service(8) + +ConditionCapability=CAP_SYS_ADMIN +ConditionDirectoryNotEmpty=|/sysroot/etc/extensions +ConditionDirectoryNotEmpty=|/sysroot/var/lib/extensions +ConditionPathExists=/etc/initrd-release +ConditionKernelCommandLine=!systemd.sysext=0 + +DefaultDependencies=no +Conflicts=shutdown.target +Before=initrd-root-fs.target shutdown.target +Wants=modprobe@loop.service modprobe@dm_mod.service +After=modprobe@loop.service modprobe@dm_mod.service sysroot.mount sysroot-usr.mount systemd-volatile-root.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=systemd-sysext --root=/sysroot refresh + +[Install] +WantedBy=initrd.target diff --git a/units/systemd-sysext.service b/units/systemd-sysext.service index 672faa946ffce..3246ea7fb7b76 100644 --- a/units/systemd-sysext.service +++ b/units/systemd-sysext.service @@ -15,7 +15,9 @@ ConditionCapability=CAP_SYS_ADMIN ConditionDirectoryNotEmpty=|/etc/extensions ConditionDirectoryNotEmpty=|/run/extensions ConditionDirectoryNotEmpty=|/var/lib/extensions +ConditionDirectoryNotEmpty=|/var/lib/extensions.mutable ConditionPathExists=!/etc/initrd-release +ConditionKernelCommandLine=!systemd.sysext=0 DefaultDependencies=no After=local-fs.target diff --git a/units/systemd-tpm2-clear.service.in b/units/systemd-tpm2-clear.service.in index a47d99ac8e70d..501846180c974 100644 --- a/units/systemd-tpm2-clear.service.in +++ b/units/systemd-tpm2-clear.service.in @@ -22,7 +22,7 @@ ConditionPathExists=/sys/class/tpm/tpm0/ppi/request # derive here from the fact that UKIs are used. Because if they do they are OK # with our SRK initialization and our PCR measurements, and hence should also # be OK with our TPM resets. -ConditionSecurity=measured-uki +ConditionSecurity=measured-os [Service] Type=oneshot diff --git a/units/systemd-tpm2-setup-early.service.in b/units/systemd-tpm2-setup-early.service.in index 7fdb99b53f36a..bafb675196855 100644 --- a/units/systemd-tpm2-setup-early.service.in +++ b/units/systemd-tpm2-setup-early.service.in @@ -12,9 +12,9 @@ Description=Early TPM SRK Setup Documentation=man:systemd-tpm2-setup.service(8) DefaultDependencies=no Conflicts=shutdown.target -After=tpm2.target systemd-pcrphase-initrd.service +After=tpm2.target systemd-pcrphase-initrd.service systemd-pcrosseparator.service Before=sysinit.target shutdown.target -ConditionSecurity=measured-uki +ConditionSecurity=measured-os ConditionPathExists=!/run/systemd/tpm2-srk-public-key.pem [Service] @@ -22,5 +22,9 @@ Type=oneshot RemainAfterExit=yes ExecStart={{LIBEXECDIR}}/systemd-tpm2-setup --early=yes --graceful -# The tool returns 76 if the TPM cannot be accessed due to an authorization failure and we can't generate an SRK. -SuccessExitStatus=76 +# The tool returns EX_PROTOCOL if the TPM cannot be accessed due to an authorization failure and we can't generate an SRK. +SuccessExitStatus=PROTOCOL +# The tool returns EX_UNAVAILABLE if some key functionality (e.g. NvPCR) support is not available in the TPM device. +SuccessExitStatus=UNAVAILABLE +# The tool returns EX_CANTCREAT if the NV index space is exhausted. +SuccessExitStatus=CANTCREAT diff --git a/units/systemd-tpm2-setup.service.in b/units/systemd-tpm2-setup.service.in index 34404a24cb5e1..4593211c1ef8b 100644 --- a/units/systemd-tpm2-setup.service.in +++ b/units/systemd-tpm2-setup.service.in @@ -15,7 +15,7 @@ Conflicts=shutdown.target After=tpm2.target systemd-tpm2-setup-early.service systemd-remount-fs.service Before=sysinit.target shutdown.target RequiresMountsFor=/var/lib/systemd -ConditionSecurity=measured-uki +ConditionSecurity=measured-os ConditionPathExists=!/etc/initrd-release [Service] @@ -23,5 +23,9 @@ Type=oneshot RemainAfterExit=yes ExecStart={{LIBEXECDIR}}/systemd-tpm2-setup --graceful -# The tool returns 76 if the TPM cannot be accessed due to an authorization failure and we can't generate an SRK. -SuccessExitStatus=76 +# The tool returns EX_PROTOCOL if the TPM cannot be accessed due to an authorization failure and we can't generate an SRK. +SuccessExitStatus=PROTOCOL +# The tool returns EX_UNAVAILABLE if some key functionality (e.g. NvPCR) support is not available in the TPM device. +SuccessExitStatus=UNAVAILABLE +# The tool returns EX_CANTCREAT if the NV index space is exhausted. +SuccessExitStatus=CANTCREAT diff --git a/units/systemd-tpm2-swtpm.service.in b/units/systemd-tpm2-swtpm.service.in new file mode 100644 index 0000000000000..10856f70d9e9f --- /dev/null +++ b/units/systemd-tpm2-swtpm.service.in @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd 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. + +[Unit] +Description=Fallback Software TPM +Documentation=man:systemd-tpm2-swtpm.service(8) +DefaultDependencies=no +After=systemd-sysusers.service +Wants=modprobe@tpm_vtpm_proxy.service +After=modprobe@tpm_vtpm_proxy.service +Before=tpm2.target sysinit.target + +[Service] +Type=notify +RuntimeDirectory=systemd/swtpm +ExecStart={{LIBEXECDIR}}/systemd-tpm2-swtpm +# Write out volatile state (so that we can read it back after the initrd transition +ExecStop=swtpm_ioctl --unix %t/systemd/swtpm/socket -v +# Initiate graceful shutdown +ExecStop=swtpm_ioctl --unix %t/systemd/swtpm/socket -s